@peerbit/document 9.12.17 → 9.13.1-0fddff8

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
@@ -1,10 +1,10 @@
1
1
  import { type AbstractType, field, serialize, variant } from "@dao-xyz/borsh";
2
2
  import type { PeerId, TypedEventTarget } from "@libp2p/interface";
3
+ import type { Multiaddr } from "@multiformats/multiaddr";
3
4
  import { Cache } from "@peerbit/cache";
4
5
  import {
5
6
  type MaybePromise,
6
7
  PublicSignKey,
7
- getPublicKeyFromPeerId,
8
8
  sha256Base64Sync,
9
9
  } from "@peerbit/crypto";
10
10
  import * as types from "@peerbit/document-interface";
@@ -26,8 +26,12 @@ import {
26
26
  type ReplicationDomain,
27
27
  SharedLog,
28
28
  } from "@peerbit/shared-log";
29
- import { DataMessage, SilentDelivery } from "@peerbit/stream-interface";
30
- import { AbortError, waitFor } from "@peerbit/time";
29
+ import {
30
+ DataMessage,
31
+ type PeerRefs,
32
+ SilentDelivery,
33
+ } from "@peerbit/stream-interface";
34
+ import { AbortError, TimeoutError, waitFor } from "@peerbit/time";
31
35
  import pDefer, { type DeferredPromise } from "p-defer";
32
36
  import { concat, fromString } from "uint8arrays";
33
37
  import { copySerialization } from "./borsh.js";
@@ -40,6 +44,8 @@ import { Prefetch } from "./prefetch.js";
40
44
  import type { ExtractArgs } from "./program.js";
41
45
  import { ResumableIterators } from "./resumable-iterator.js";
42
46
 
47
+ const WARNING_WHEN_ITERATING_FOR_MORE_THAN = 1e5;
48
+
43
49
  const logger = loggerFn({ module: "document-index" });
44
50
 
45
51
  type BufferedResult<T, I extends Record<string, any>> = {
@@ -49,6 +55,102 @@ type BufferedResult<T, I extends Record<string, any>> = {
49
55
  from: PublicSignKey;
50
56
  };
51
57
 
58
+ export type UpdateMergeStrategy<
59
+ T,
60
+ I,
61
+ Resolve extends boolean | undefined,
62
+ RT = ValueTypeFromRequest<Resolve, T, I>,
63
+ > =
64
+ | boolean
65
+ | {
66
+ filter?: (
67
+ evt: DocumentsChange<T, I>,
68
+ ) => MaybePromise<DocumentsChange<T, I> | void>;
69
+ };
70
+ export type UpdateCallbacks<
71
+ T,
72
+ I,
73
+ Resolve extends boolean | undefined,
74
+ RT = ValueTypeFromRequest<Resolve, T, I>,
75
+ > = {
76
+ /**
77
+ * Fires on raw DB change events (added/removed/updated).
78
+ * Use if you want low-level inspection of change streams.
79
+ */
80
+ onChange?: (change: DocumentsChange<T, I>) => void | Promise<void>;
81
+
82
+ /**
83
+ * Fires whenever the iterator yields a batch to the consumer.
84
+ * Good for external sync (e.g. React state).
85
+ */
86
+ onResults?: (
87
+ batch: RT[],
88
+ meta: { reason: "initial" | "next" | "join" | "change" },
89
+ ) => void | Promise<void>;
90
+ };
91
+
92
+ /**
93
+ * Unified update options for iterate()/search()/get() and hooks.
94
+ * If you pass `true`, defaults to `{ merge: "sorted" }`.
95
+ */
96
+ export type UpdateOptions<T, I, Resolve extends boolean | undefined> =
97
+ | boolean
98
+ | ({
99
+ /** Live update behavior. Only sorted merging is supported; optional filter can mutate/ignore events. */
100
+ merge?: UpdateMergeStrategy<T, I, Resolve>;
101
+ } & UpdateCallbacks<T, I, Resolve>);
102
+
103
+ export type JoiningTargets = {
104
+ /** Specific peers you care about */
105
+ peers?: Array<PublicSignKey | PeerId | string>; // string = hash or peer id
106
+
107
+ /** Multiaddrs you care about */
108
+ multiaddrs?: (string | Multiaddr)[];
109
+
110
+ /**
111
+ * From the previous cover set (what you "knew" about earlier).
112
+ * - "any": wait until at least 1 of the known peers is ready
113
+ * - "all": wait until all known peers are ready
114
+ * - number: wait until N known peers are ready
115
+ */
116
+ known?: "any" | "all" | number;
117
+ };
118
+
119
+ export type JoiningTimeoutPolicy = "proceed" | "error";
120
+
121
+ export type JoiningOnMissedResults = (evt: {
122
+ /** How many items should have preceded the current frontier. */
123
+ amount: number;
124
+
125
+ /** The peer whose arrival triggered the gap calculation. */
126
+ peer: PublicSignKey;
127
+ }) => void | Promise<void>;
128
+
129
+ export type LateResultsEvent = {
130
+ /** Count of items that should have appeared earlier than the current frontier */
131
+ amount: number;
132
+
133
+ /** If attributable, the peer that produced the late items */
134
+ peer?: PublicSignKey;
135
+ };
136
+
137
+ export type WaitBehavior =
138
+ | "block" // hold the *first* fetch until readiness condition is met or timeout
139
+ | "keep-open"; // return immediately; iterator stays open listening for late peers
140
+
141
+ export type WaitPolicy = {
142
+ timeout: number; // max time to wait
143
+ until?: "any"; // readiness condition, TODO more options like "cover" (to wait for this.log.watiForReplicators)
144
+ onTimeout?: "proceed" | "error"; // proceed = continue with whoever's ready
145
+ behavior?: WaitBehavior; // default: "keep-open"
146
+ };
147
+
148
+ export type ReachScope = {
149
+ /** who to consider for readiness */
150
+ eager?: boolean; // not yet matured
151
+ discover?: PublicSignKey[]; // wait for these peers to be ready, assumes they are already in the dialqueue or connected, but not actively subscribing yet
152
+ };
153
+
52
154
  export type RemoteQueryOptions<Q, R, D> = RPCRequestAllOptions<Q, R> & {
53
155
  replicate?: boolean;
54
156
  minAge?: number;
@@ -61,12 +163,15 @@ export type RemoteQueryOptions<Q, R, D> = RPCRequestAllOptions<Q, R> & {
61
163
  | {
62
164
  range: CoverRange<number | bigint>;
63
165
  };
64
- eager?: boolean; // whether to query newly joined peers before they have matured
65
- joining?:
66
- | boolean
67
- | { waitFor?: number; onMissedResults?: (evt: { amount: number }) => void }; // whether to query peers that are joining the network
166
+ /** WHO can answer? How do we grow the candidate set? */
167
+ reach?: ReachScope;
168
+ /** WHEN are we allowed to proceed? Quorum semantics over a chosen group. */
169
+ wait?: WaitPolicy;
170
+
171
+ onLateResults?: (evt: LateResultsEvent) => void | Promise<void>;
68
172
  };
69
- export type QueryOptions<R, D, Resolve extends boolean | undefined> = {
173
+
174
+ export type QueryOptions<T, I, D, Resolve extends boolean | undefined> = {
70
175
  remote?:
71
176
  | boolean
72
177
  | RemoteQueryOptions<
@@ -77,9 +182,16 @@ export type QueryOptions<R, D, Resolve extends boolean | undefined> = {
77
182
  local?: boolean;
78
183
  resolve?: Resolve;
79
184
  signal?: AbortSignal;
185
+ updates?: UpdateOptions<T, I, Resolve>;
186
+ /**
187
+ * Controls iterator liveness after batches are consumed.
188
+ * - 'onEmpty' (default): close when no more results
189
+ * - 'manual': keep open until iterator.close() or program close; good for live updates
190
+ */
191
+ closePolicy?: "onEmpty" | "manual";
80
192
  };
81
193
 
82
- export type GetOptions<R, D, Resolve extends boolean | undefined> = {
194
+ export type GetOptions<T, I, D, Resolve extends boolean | undefined> = {
83
195
  remote?:
84
196
  | boolean
85
197
  | RemoteQueryOptions<
@@ -94,10 +206,11 @@ export type GetOptions<R, D, Resolve extends boolean | undefined> = {
94
206
  };
95
207
 
96
208
  export type SearchOptions<
97
- R,
209
+ T,
210
+ I,
98
211
  D,
99
212
  Resolve extends boolean | undefined,
100
- > = QueryOptions<R, D, Resolve>;
213
+ > = QueryOptions<T, I, D, Resolve>;
101
214
 
102
215
  type Transformer<T, I> = (obj: T, context: types.Context) => MaybePromise<I>;
103
216
 
@@ -108,13 +221,15 @@ export type ResultsIterator<T> = {
108
221
  all: () => Promise<T[]>;
109
222
  pending: () => number | undefined;
110
223
  first: () => Promise<T | undefined>;
224
+ [Symbol.asyncIterator]: () => AsyncIterator<T>;
111
225
  };
112
226
 
113
227
  type QueryDetailedOptions<
114
228
  T,
229
+ I,
115
230
  D,
116
231
  Resolve extends boolean | undefined,
117
- > = QueryOptions<T, D, Resolve> & {
232
+ > = QueryOptions<T, I, D, Resolve> & {
118
233
  onResponse?: (
119
234
  response: types.AbstractSearchResult,
120
235
  from: PublicSignKey,
@@ -122,6 +237,7 @@ type QueryDetailedOptions<
122
237
  remote?: {
123
238
  from?: string[]; // if specified, only query these peers
124
239
  };
240
+ fetchFirstForRemote?: Set<string>;
125
241
  };
126
242
 
127
243
  type QueryLike = {
@@ -130,7 +246,7 @@ type QueryLike = {
130
246
  };
131
247
 
132
248
  type ExtractResolveFromOptions<O> =
133
- O extends QueryOptions<any, any, infer X>
249
+ O extends QueryOptions<any, any, any, infer X>
134
250
  ? X extends boolean // if X is a boolean (true or false)
135
251
  ? X
136
252
  : true // else default to true
@@ -138,7 +254,7 @@ type ExtractResolveFromOptions<O> =
138
254
 
139
255
  const coerceQuery = <Resolve extends boolean | undefined>(
140
256
  query: types.SearchRequest | types.SearchRequestIndexed | QueryLike,
141
- options?: QueryOptions<any, any, Resolve>,
257
+ options?: QueryOptions<any, any, any, Resolve>,
142
258
  ) => {
143
259
  let replicate =
144
260
  typeof options?.remote !== "boolean" ? options?.remote?.replicate : false;
@@ -180,7 +296,7 @@ const introduceEntries = async <
180
296
  documentType: AbstractType<T>,
181
297
  indexedType: AbstractType<I>,
182
298
  sync: (request: R, response: types.Results<any>) => Promise<void>,
183
- options?: QueryDetailedOptions<T, D, any>,
299
+ options?: QueryDetailedOptions<T, I, D, any>,
184
300
  ): Promise<
185
301
  RPCResponse<types.Results<types.ResultTypeFromRequest<R, T, I>>>[]
186
302
  > => {
@@ -815,18 +931,18 @@ export class DocumentIndex<
815
931
  return dropped;
816
932
  }
817
933
 
818
- public async get<Options extends GetOptions<T, D, true | undefined>>(
934
+ public async get<Options extends GetOptions<T, I, D, true | undefined>>(
819
935
  key: indexerTypes.Ideable | indexerTypes.IdKey,
820
936
  options?: Options,
821
937
  ): Promise<WithIndexedContext<T, I>>;
822
938
 
823
- public async get<Options extends GetOptions<T, D, false>>(
939
+ public async get<Options extends GetOptions<T, I, D, false>>(
824
940
  key: indexerTypes.Ideable | indexerTypes.IdKey,
825
941
  options?: Options,
826
942
  ): Promise<WithContext<I>>;
827
943
 
828
944
  public async get<
829
- Options extends GetOptions<T, D, Resolve>,
945
+ Options extends GetOptions<T, I, D, Resolve>,
830
946
  Resolve extends boolean | undefined = ExtractResolveFromOptions<Options>,
831
947
  >(key: indexerTypes.Ideable | indexerTypes.IdKey, options?: Options) {
832
948
  let deferred:
@@ -879,16 +995,15 @@ export class DocumentIndex<
879
995
  ? { ...options.remote }
880
996
  : {};
881
997
  if (baseRemote) {
882
- const prevJoining = baseRemote.joining;
998
+ const waitPolicy = baseRemote.wait;
883
999
  if (
884
- !prevJoining ||
885
- prevJoining === true ||
886
- (typeof prevJoining === "object" &&
887
- (prevJoining.waitFor || 0) < options.waitFor)
1000
+ !waitPolicy ||
1001
+ (typeof waitPolicy === "object" &&
1002
+ (waitPolicy.timeout || 0) < options.waitFor)
888
1003
  ) {
889
- baseRemote.joining = {
890
- ...(typeof prevJoining === "object" ? prevJoining : {}),
891
- waitFor: options.waitFor,
1004
+ baseRemote.wait = {
1005
+ ...(typeof waitPolicy === "object" ? waitPolicy : {}),
1006
+ timeout: options.waitFor,
892
1007
  };
893
1008
  }
894
1009
  }
@@ -897,10 +1012,11 @@ export class DocumentIndex<
897
1012
  let joinListener: (() => void) | undefined;
898
1013
  if (baseRemote) {
899
1014
  joinListener = this.attachJoinListener({
1015
+ eager: baseRemote.reach?.eager,
900
1016
  onPeer: async (pk) => {
901
1017
  if (cleanedUp) return;
902
1018
  const hash = pk.hashcode();
903
- const requeryOptions: QueryOptions<T, D, Resolve> = {
1019
+ const requeryOptions: QueryOptions<T, I, D, Resolve> = {
904
1020
  ...(options as any),
905
1021
  remote: {
906
1022
  ...(baseRemote || {}),
@@ -1005,14 +1121,14 @@ export class DocumentIndex<
1005
1121
  }
1006
1122
 
1007
1123
  public async getDetailed<
1008
- Options extends QueryOptions<T, D, Resolve>,
1124
+ Options extends QueryOptions<T, I, D, Resolve>,
1009
1125
  Resolve extends boolean | undefined = ExtractResolveFromOptions<Options>,
1010
1126
  RT extends types.Result = Resolve extends true
1011
1127
  ? types.ResultValue<WithIndexedContext<T, I>>
1012
1128
  : types.ResultIndexedValue<WithContext<I>>,
1013
1129
  >(
1014
1130
  key: indexerTypes.IdKey | indexerTypes.IdPrimitive,
1015
- options?: QueryOptions<T, D, Resolve>,
1131
+ options?: QueryOptions<T, I, D, Resolve>,
1016
1132
  ): Promise<types.Results<RT>[] | undefined> {
1017
1133
  let coercedOptions = options;
1018
1134
  if (options?.remote && typeof options.remote !== "boolean") {
@@ -1049,7 +1165,7 @@ export class DocumentIndex<
1049
1165
  new indexerTypes.ByteMatchQuery({ key: this.indexBy, value: key }),
1050
1166
  ],
1051
1167
  }),
1052
- coercedOptions,
1168
+ coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
1053
1169
  );
1054
1170
  } else {
1055
1171
  const indexableKey = indexerTypes.toIdeable(key);
@@ -1068,7 +1184,7 @@ export class DocumentIndex<
1068
1184
  }),
1069
1185
  ],
1070
1186
  }),
1071
- coercedOptions,
1187
+ coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
1072
1188
  );
1073
1189
  } else if (typeof indexableKey === "string") {
1074
1190
  results = await this.queryCommence(
@@ -1080,7 +1196,7 @@ export class DocumentIndex<
1080
1196
  }),
1081
1197
  ],
1082
1198
  }),
1083
- coercedOptions,
1199
+ coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
1084
1200
  );
1085
1201
  } else if (indexableKey instanceof Uint8Array) {
1086
1202
  results = await this.queryCommence(
@@ -1092,7 +1208,7 @@ export class DocumentIndex<
1092
1208
  }),
1093
1209
  ],
1094
1210
  }),
1095
- coercedOptions,
1211
+ coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
1096
1212
  );
1097
1213
  }
1098
1214
  }
@@ -1364,10 +1480,131 @@ export class DocumentIndex<
1364
1480
  }
1365
1481
  }
1366
1482
 
1483
+ private async waitForCoverReady(params: {
1484
+ domain?: { args?: ExtractArgs<D> } | { range: CoverRange<number | bigint> };
1485
+ eager?: boolean;
1486
+ settle: "any";
1487
+ timeout: number;
1488
+ signal?: AbortSignal;
1489
+ onTimeout?: "proceed" | "error";
1490
+ }) {
1491
+ const {
1492
+ domain,
1493
+ eager,
1494
+ settle,
1495
+ timeout,
1496
+ signal,
1497
+ onTimeout = "proceed",
1498
+ } = params;
1499
+
1500
+ if (settle !== "any") {
1501
+ return;
1502
+ }
1503
+
1504
+ const properties =
1505
+ domain && "range" in domain
1506
+ ? { range: domain.range }
1507
+ : { args: domain?.args };
1508
+ const selfHash = this.node.identity.publicKey.hashcode();
1509
+
1510
+ const ready = async () => {
1511
+ const cover = await this._log.getCover(properties, { eager });
1512
+ return cover.some((hash) => hash !== selfHash);
1513
+ };
1514
+
1515
+ if (await ready()) {
1516
+ return;
1517
+ }
1518
+
1519
+ const deferred = pDefer<void>();
1520
+ let settled = false;
1521
+ let cleaned = false;
1522
+ let timer: ReturnType<typeof setTimeout> | undefined;
1523
+ let checking = false;
1524
+
1525
+ const cleanup = () => {
1526
+ if (cleaned) {
1527
+ return;
1528
+ }
1529
+ cleaned = true;
1530
+ this._log.events.removeEventListener("replicator:join", onEvent);
1531
+ this._log.events.removeEventListener("replication:change", onEvent);
1532
+ this._log.events.removeEventListener("replicator:mature", onEvent);
1533
+ signal?.removeEventListener("abort", onAbort);
1534
+ if (timer != null) {
1535
+ clearTimeout(timer);
1536
+ timer = undefined;
1537
+ }
1538
+ };
1539
+
1540
+ const resolve = () => {
1541
+ if (settled) {
1542
+ return;
1543
+ }
1544
+ settled = true;
1545
+ cleanup();
1546
+ deferred.resolve();
1547
+ };
1548
+
1549
+ const reject = (error: Error) => {
1550
+ if (settled) {
1551
+ return;
1552
+ }
1553
+ settled = true;
1554
+ cleanup();
1555
+ deferred.reject(error);
1556
+ };
1557
+
1558
+ const onAbort = () => reject(new AbortError());
1559
+
1560
+ const onEvent = async () => {
1561
+ if (checking) {
1562
+ return;
1563
+ }
1564
+ checking = true;
1565
+ try {
1566
+ if (await ready()) {
1567
+ resolve();
1568
+ }
1569
+ } catch (error) {
1570
+ reject(error instanceof Error ? error : new Error(String(error)));
1571
+ } finally {
1572
+ checking = false;
1573
+ }
1574
+ };
1575
+
1576
+ if (signal) {
1577
+ signal.addEventListener("abort", onAbort);
1578
+ }
1579
+
1580
+ if (timeout > 0) {
1581
+ timer = setTimeout(() => {
1582
+ if (onTimeout === "error") {
1583
+ reject(
1584
+ new TimeoutError("Timeout waiting for participating replicator"),
1585
+ );
1586
+ } else {
1587
+ resolve();
1588
+ }
1589
+ }, timeout);
1590
+ }
1591
+
1592
+ this._log.events.addEventListener("replicator:join", onEvent);
1593
+ this._log.events.addEventListener("replication:change", onEvent);
1594
+ this._log.events.addEventListener("replicator:mature", onEvent);
1595
+
1596
+ try {
1597
+ await deferred.promise;
1598
+ } finally {
1599
+ cleanup();
1600
+ }
1601
+ }
1602
+
1367
1603
  // Utility: attach a join listener that waits until a peer is a replicator,
1368
1604
  // then invokes the provided callback. Returns a detach function.
1369
1605
  private attachJoinListener(params: {
1370
1606
  signal?: AbortSignal;
1607
+ eager?: boolean;
1371
1608
  onPeer: (pk: PublicSignKey) => Promise<void> | void;
1372
1609
  }): () => void {
1373
1610
  const active = new Set<string>();
@@ -1382,6 +1619,7 @@ export class DocumentIndex<
1382
1619
  await this._log
1383
1620
  .waitForReplicator(pk, {
1384
1621
  signal: params.signal,
1622
+ eager: params.eager,
1385
1623
  })
1386
1624
  .catch(() => undefined);
1387
1625
  if (params.signal?.aborted) return;
@@ -1419,7 +1657,8 @@ export class DocumentIndex<
1419
1657
  RT extends types.Result = types.ResultTypeFromRequest<R, T, I>,
1420
1658
  >(
1421
1659
  queryRequest: R,
1422
- options?: QueryDetailedOptions<T, D, boolean | undefined>,
1660
+ options?: QueryDetailedOptions<T, I, D, boolean | undefined>,
1661
+ fetchFirstForRemote?: Set<string>,
1423
1662
  ): Promise<types.Results<RT>[]> {
1424
1663
  const local = typeof options?.local === "boolean" ? options?.local : true;
1425
1664
  let remote:
@@ -1477,8 +1716,8 @@ export class DocumentIndex<
1477
1716
  ? options?.remote?.from
1478
1717
  : await this._log.getCover(remote.domain ?? { args: undefined }, {
1479
1718
  roleAge: remote.minAge,
1480
- eager: remote.eager,
1481
- reachableOnly: !!remote.joining, // when we want to merge joining we can ignore pending to be online peers and instead consider them once they become online
1719
+ eager: remote.reach?.eager,
1720
+ 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
1482
1721
  });
1483
1722
 
1484
1723
  if (replicatorGroups) {
@@ -1508,6 +1747,13 @@ export class DocumentIndex<
1508
1747
  if (hash === this.node.identity.publicKey.hashcode()) {
1509
1748
  return false;
1510
1749
  }
1750
+
1751
+ if (fetchFirstForRemote?.has(hash)) {
1752
+ // we already fetched this one for remote, no need to do it again
1753
+ return false;
1754
+ }
1755
+ fetchFirstForRemote?.add(hash);
1756
+
1511
1757
  const resultAlready = this._prefetch?.accumulator.consume(
1512
1758
  queryRequest,
1513
1759
  hash,
@@ -1637,11 +1883,11 @@ export class DocumentIndex<
1637
1883
 
1638
1884
  public search(
1639
1885
  queryRequest: QueryLike,
1640
- options?: SearchOptions<T, D, true>,
1886
+ options?: SearchOptions<T, I, D, true>,
1641
1887
  ): Promise<ValueTypeFromRequest<true, T, I>[]>;
1642
1888
  public search(
1643
1889
  queryRequest: QueryLike,
1644
- options?: SearchOptions<T, D, false>,
1890
+ options?: SearchOptions<T, I, D, false>,
1645
1891
  ): Promise<ValueTypeFromRequest<false, T, I>[]>;
1646
1892
 
1647
1893
  /**
@@ -1652,11 +1898,11 @@ export class DocumentIndex<
1652
1898
  */
1653
1899
  public async search<
1654
1900
  R extends types.SearchRequest | types.SearchRequestIndexed | QueryLike,
1655
- O extends SearchOptions<T, D, Resolve>,
1656
- Resolve extends boolean | undefined = ExtractResolveFromOptions<O>,
1901
+ O extends SearchOptions<T, I, D, Resolve>,
1902
+ Resolve extends boolean = ExtractResolveFromOptions<O>,
1657
1903
  >(
1658
1904
  queryRequest: R,
1659
- options?: SearchOptions<T, D, Resolve>,
1905
+ options?: O,
1660
1906
  ): Promise<ValueTypeFromRequest<Resolve, T, I>[]> {
1661
1907
  // Set fetch to search size, or max value (default to max u32 (4294967295))
1662
1908
  const coercedRequest: types.SearchRequest | types.SearchRequestIndexed =
@@ -1664,7 +1910,7 @@ export class DocumentIndex<
1664
1910
  coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
1665
1911
 
1666
1912
  // So that the iterator is pre-fetching the right amount of entries
1667
- const iterator = this.iterate(coercedRequest, options);
1913
+ const iterator = this.iterate<Resolve>(coercedRequest, options);
1668
1914
 
1669
1915
  // So that this call will not do any remote requests
1670
1916
  const allResults: ValueTypeFromRequest<Resolve, T, I>[] = [];
@@ -1729,11 +1975,11 @@ export class DocumentIndex<
1729
1975
 
1730
1976
  public iterate(
1731
1977
  query?: QueryLike,
1732
- options?: QueryOptions<T, D, undefined>,
1978
+ options?: QueryOptions<T, I, D, undefined>,
1733
1979
  ): ResultsIterator<ValueTypeFromRequest<true, T, I>>;
1734
1980
  public iterate<Resolve extends boolean>(
1735
1981
  query?: QueryLike,
1736
- options?: QueryOptions<T, D, Resolve>,
1982
+ options?: QueryOptions<T, I, D, Resolve>,
1737
1983
  ): ResultsIterator<ValueTypeFromRequest<Resolve, T, I>>;
1738
1984
 
1739
1985
  /**
@@ -1744,11 +1990,11 @@ export class DocumentIndex<
1744
1990
  */
1745
1991
  public iterate<
1746
1992
  R extends types.SearchRequest | types.SearchRequestIndexed | QueryLike,
1747
- O extends SearchOptions<T, D, Resolve>,
1993
+ O extends SearchOptions<T, I, D, Resolve>,
1748
1994
  Resolve extends boolean | undefined = ExtractResolveFromOptions<O>,
1749
1995
  >(
1750
1996
  queryRequest?: R,
1751
- options?: QueryOptions<T, D, Resolve>,
1997
+ options?: QueryOptions<T, I, D, Resolve>,
1752
1998
  ): ResultsIterator<ValueTypeFromRequest<Resolve, T, I>> {
1753
1999
  if (
1754
2000
  queryRequest instanceof types.SearchRequest &&
@@ -1803,6 +2049,7 @@ export class DocumentIndex<
1803
2049
  const visited = new Set<string | number | bigint>();
1804
2050
 
1805
2051
  let done = false;
2052
+ let drain = false; // if true, close on empty once (overrides manual)
1806
2053
  let first = false;
1807
2054
 
1808
2055
  // TODO handle join/leave while iterating
@@ -1827,147 +2074,186 @@ export class DocumentIndex<
1827
2074
  return [...peerBufferMap.values()].map((x) => x.buffer).flat();
1828
2075
  };
1829
2076
 
1830
- let maybeSetDone: () => void;
1831
- let unsetDone: () => void;
1832
- let cleanup = () => {};
1833
-
1834
- if (typeof options?.remote === "object" && options.remote.joining) {
1835
- let t0 = +new Date();
1836
- let waitForTime =
1837
- typeof options.remote.joining === "boolean"
1838
- ? DEFAULT_TIMEOUT
1839
- : (options.remote.joining.waitFor ?? DEFAULT_TIMEOUT);
1840
- let setDoneIfTimeout = false;
1841
- maybeSetDone = () => {
1842
- if (t0 + waitForTime < +new Date()) {
1843
- cleanup();
1844
- done = true;
1845
- } else {
1846
- setDoneIfTimeout = true;
1847
- }
1848
- };
1849
- unsetDone = () => {
1850
- setDoneIfTimeout = false;
1851
- done = false;
1852
- };
1853
- let timeout = setTimeout(() => {
1854
- if (setDoneIfTimeout) {
1855
- cleanup();
1856
- done = true;
1857
- }
1858
- }, waitForTime);
2077
+ let maybeSetDone = () => {
2078
+ cleanup();
2079
+ done = true;
2080
+ };
2081
+ let unsetDone = () => {
2082
+ cleanup();
2083
+ done = false;
2084
+ };
2085
+ let cleanup = () => {
2086
+ this.clearResultsQueue(queryRequestCoerced);
2087
+ };
1859
2088
 
1860
- cleanup = () => {
1861
- this.clearResultsQueue(queryRequestCoerced);
1862
- clearTimeout(timeout);
1863
- };
1864
- } else {
1865
- maybeSetDone = () => {
1866
- cleanup();
1867
- done = true;
1868
- };
1869
- unsetDone = () => {
1870
- cleanup();
1871
- done = false;
1872
- };
1873
- cleanup = () => {
1874
- this.clearResultsQueue(queryRequestCoerced);
1875
- };
2089
+ let warmupPromise: Promise<any> | undefined = undefined;
2090
+
2091
+ if (typeof options?.remote === "object") {
2092
+ let waitForTime: number | undefined = undefined;
2093
+ if (options.remote.wait) {
2094
+ let t0 = +new Date();
2095
+
2096
+ waitForTime =
2097
+ typeof options.remote.wait === "boolean"
2098
+ ? DEFAULT_TIMEOUT
2099
+ : (options.remote.wait.timeout ?? DEFAULT_TIMEOUT);
2100
+ let setDoneIfTimeout = false;
2101
+ maybeSetDone = () => {
2102
+ if (t0 + waitForTime! < +new Date()) {
2103
+ cleanup();
2104
+ done = true;
2105
+ } else {
2106
+ setDoneIfTimeout = true;
2107
+ }
2108
+ };
2109
+ unsetDone = () => {
2110
+ setDoneIfTimeout = false;
2111
+ done = false;
2112
+ };
2113
+ let timeout = setTimeout(() => {
2114
+ if (setDoneIfTimeout) {
2115
+ cleanup();
2116
+ done = true;
2117
+ }
2118
+ }, waitForTime);
2119
+
2120
+ cleanup = () => {
2121
+ this.clearResultsQueue(queryRequestCoerced);
2122
+ clearTimeout(timeout);
2123
+ };
2124
+ }
2125
+
2126
+ if (options.remote.reach?.discover) {
2127
+ warmupPromise = this.waitFor(options.remote.reach.discover, {
2128
+ signal: controller.signal,
2129
+ seek: "present",
2130
+ timeout: waitForTime ?? DEFAULT_TIMEOUT,
2131
+ });
2132
+ options.remote.reach.eager = true; // include the results from the discovered peer even if it is not mature
2133
+ }
2134
+
2135
+ const waitPolicy =
2136
+ typeof options.remote.wait === "object"
2137
+ ? options.remote.wait
2138
+ : undefined;
2139
+ if (
2140
+ waitPolicy?.behavior === "block" &&
2141
+ (waitPolicy.until ?? "any") === "any"
2142
+ ) {
2143
+ const blockPromise = this.waitForCoverReady({
2144
+ domain: options.remote.domain,
2145
+ eager: options.remote.reach?.eager,
2146
+ settle: "any",
2147
+ timeout: waitPolicy.timeout ?? DEFAULT_TIMEOUT,
2148
+ signal: controller.signal,
2149
+ onTimeout: waitPolicy.onTimeout,
2150
+ });
2151
+ warmupPromise = warmupPromise
2152
+ ? Promise.all([warmupPromise, blockPromise]).then(() => undefined)
2153
+ : blockPromise;
2154
+ }
1876
2155
  }
1877
2156
 
1878
2157
  const fetchFirst = async (
1879
2158
  n: number,
1880
- fetchOptions?: { from?: string[] },
2159
+ fetchOptions?: { from?: string[]; fetchedFirstForRemote?: Set<string> },
1881
2160
  ): Promise<boolean> => {
2161
+ await warmupPromise;
1882
2162
  let hasMore = false;
1883
2163
 
1884
2164
  queryRequestCoerced.fetch = n;
1885
- await this.queryCommence(queryRequestCoerced, {
1886
- local: fetchOptions?.from != null ? false : options?.local,
1887
- remote:
1888
- options?.remote !== false
1889
- ? {
1890
- ...(typeof options?.remote === "object" ? options.remote : {}),
1891
- from: fetchOptions?.from,
1892
- }
1893
- : false,
1894
- resolve,
1895
- onResponse: async (response, from) => {
1896
- if (!from) {
1897
- logger.error("Missing response from");
1898
- return;
1899
- }
1900
- if (response instanceof types.NoAccess) {
1901
- logger.error("Dont have access");
1902
- return;
1903
- } else if (response instanceof types.Results) {
1904
- const results = response as types.Results<
1905
- types.ResultTypeFromRequest<R, T, I>
1906
- >;
1907
-
1908
- if (results.kept === 0n && results.results.length === 0) {
2165
+ await this.queryCommence(
2166
+ queryRequestCoerced,
2167
+ {
2168
+ local: fetchOptions?.from != null ? false : options?.local,
2169
+ remote:
2170
+ options?.remote !== false
2171
+ ? {
2172
+ ...(typeof options?.remote === "object"
2173
+ ? options.remote
2174
+ : {}),
2175
+ from: fetchOptions?.from,
2176
+ }
2177
+ : false,
2178
+ resolve,
2179
+ onResponse: async (response, from) => {
2180
+ if (!from) {
2181
+ logger.error("Missing response from");
1909
2182
  return;
1910
2183
  }
2184
+ if (response instanceof types.NoAccess) {
2185
+ logger.error("Dont have access");
2186
+ return;
2187
+ } else if (response instanceof types.Results) {
2188
+ const results = response as types.Results<
2189
+ types.ResultTypeFromRequest<R, T, I>
2190
+ >;
1911
2191
 
1912
- if (results.kept > 0n) {
1913
- hasMore = true;
1914
- }
1915
- const buffer: BufferedResult<
1916
- types.ResultTypeFromRequest<R, T, I> | I,
1917
- I
1918
- >[] = [];
1919
-
1920
- for (const result of results.results) {
1921
- if (result instanceof types.ResultValue) {
1922
- const indexKey = indexerTypes.toId(
1923
- this.indexByResolver(result.value),
1924
- ).primitive;
1925
- if (visited.has(indexKey)) {
1926
- continue;
1927
- }
1928
- visited.add(indexKey);
1929
-
1930
- buffer.push({
1931
- value: result.value as types.ResultTypeFromRequest<R, T, I>,
1932
- context: result.context,
1933
- from,
1934
- indexed: await this.resolveIndexed<R>(
1935
- result,
1936
- results.results,
1937
- ),
1938
- });
1939
- } else {
1940
- const indexKey = indexerTypes.toId(
1941
- this.indexByResolver(result.value),
1942
- ).primitive;
2192
+ if (results.kept === 0n && results.results.length === 0) {
2193
+ return;
2194
+ }
1943
2195
 
1944
- if (visited.has(indexKey)) {
1945
- continue;
2196
+ if (results.kept > 0n) {
2197
+ hasMore = true;
2198
+ }
2199
+ const buffer: BufferedResult<
2200
+ types.ResultTypeFromRequest<R, T, I> | I,
2201
+ I
2202
+ >[] = [];
2203
+
2204
+ for (const result of results.results) {
2205
+ if (result instanceof types.ResultValue) {
2206
+ const indexKey = indexerTypes.toId(
2207
+ this.indexByResolver(result.value),
2208
+ ).primitive;
2209
+ if (visited.has(indexKey)) {
2210
+ continue;
2211
+ }
2212
+ visited.add(indexKey);
2213
+
2214
+ buffer.push({
2215
+ value: result.value as types.ResultTypeFromRequest<R, T, I>,
2216
+ context: result.context,
2217
+ from,
2218
+ indexed: await this.resolveIndexed<R>(
2219
+ result,
2220
+ results.results,
2221
+ ),
2222
+ });
2223
+ } else {
2224
+ const indexKey = indexerTypes.toId(
2225
+ this.indexByResolver(result.value),
2226
+ ).primitive;
2227
+
2228
+ if (visited.has(indexKey)) {
2229
+ continue;
2230
+ }
2231
+ visited.add(indexKey);
2232
+ buffer.push({
2233
+ value: result.value,
2234
+ context: result.context,
2235
+ from,
2236
+ indexed: coerceWithContext(
2237
+ result.indexed || result.value,
2238
+ result.context,
2239
+ ),
2240
+ });
1946
2241
  }
1947
- visited.add(indexKey);
1948
- buffer.push({
1949
- value: result.value,
1950
- context: result.context,
1951
- from,
1952
- indexed: coerceWithContext(
1953
- result.indexed || result.value,
1954
- result.context,
1955
- ),
1956
- });
1957
2242
  }
1958
- }
1959
2243
 
1960
- peerBufferMap.set(from.hashcode(), {
1961
- buffer,
1962
- kept: Number(response.kept),
1963
- });
1964
- } else {
1965
- throw new Error(
1966
- "Unsupported result type: " + response?.constructor?.name,
1967
- );
1968
- }
2244
+ peerBufferMap.set(from.hashcode(), {
2245
+ buffer,
2246
+ kept: Number(response.kept),
2247
+ });
2248
+ } else {
2249
+ throw new Error(
2250
+ "Unsupported result type: " + response?.constructor?.name,
2251
+ );
2252
+ }
2253
+ },
1969
2254
  },
1970
- });
2255
+ fetchOptions?.fetchedFirstForRemote,
2256
+ );
1971
2257
 
1972
2258
  if (!hasMore) {
1973
2259
  maybeSetDone();
@@ -2015,7 +2301,10 @@ export class DocumentIndex<
2015
2301
  amount: n - buffer.buffer.length,
2016
2302
  });
2017
2303
  // Fetch locally?
2018
- if (peer === this.node.identity.publicKey.hashcode()) {
2304
+ if (
2305
+ peer === this.node.identity.publicKey.hashcode() &&
2306
+ this._resumableIterators.has(queryRequestCoerced.idString)
2307
+ ) {
2019
2308
  promises.push(
2020
2309
  this.processQuery(
2021
2310
  collectRequest,
@@ -2116,7 +2405,7 @@ export class DocumentIndex<
2116
2405
  this.documentType,
2117
2406
  this.indexedType,
2118
2407
  this._sync,
2119
- options,
2408
+ options as QueryDetailedOptions<T, I, D, any>,
2120
2409
  )
2121
2410
  .then(async (responses) => {
2122
2411
  return Promise.all(
@@ -2229,11 +2518,10 @@ export class DocumentIndex<
2229
2518
  ),
2230
2519
  );
2231
2520
 
2232
- const pendingMoreResults = n < results.length;
2233
-
2234
2521
  lastValueInOrder = results[0] || lastValueInOrder;
2235
-
2522
+ const pendingMoreResults = n < results.length; // check if there are more results to fetch, before splicing
2236
2523
  const batch = results.splice(0, n);
2524
+ const hasMore = !fetchedAll || pendingMoreResults;
2237
2525
 
2238
2526
  for (const result of batch) {
2239
2527
  const arr = peerBufferMap.get(result.from.hashcode());
@@ -2247,7 +2535,6 @@ export class DocumentIndex<
2247
2535
  }
2248
2536
  }
2249
2537
 
2250
- const hasMore = !fetchedAll || pendingMoreResults;
2251
2538
  if (hasMore) {
2252
2539
  unsetDone();
2253
2540
  } else {
@@ -2281,6 +2568,8 @@ export class DocumentIndex<
2281
2568
  ) as ValueTypeFromRequest<Resolve, T, I>[];
2282
2569
  }
2283
2570
 
2571
+ // no extra queued-first/last in simplified API
2572
+
2284
2573
  return dedup(coercedBatch, this.indexByResolver);
2285
2574
  };
2286
2575
 
@@ -2329,26 +2618,141 @@ export class DocumentIndex<
2329
2618
 
2330
2619
  let joinListener: (() => void) | undefined;
2331
2620
 
2621
+ let fetchedFirstForRemote: Set<string> | undefined = undefined;
2622
+
2332
2623
  let updateDeferred: ReturnType<typeof pDefer> | undefined;
2333
2624
  const signalUpdate = () => updateDeferred?.resolve();
2334
- const waitForUpdate = () =>
2625
+ const _waitForUpdate = () =>
2335
2626
  updateDeferred ? updateDeferred.promise : Promise.resolve();
2336
2627
 
2337
- if (typeof options?.remote === "object" && options?.remote.joining) {
2628
+ // ---------------- Live updates wiring (sorted-only with optional filter) ----------------
2629
+ const normalizeUpdatesOption = (
2630
+ u?: UpdateOptions<T, I, Resolve>,
2631
+ ):
2632
+ | {
2633
+ merge?:
2634
+ | {
2635
+ filter?: (
2636
+ evt: DocumentsChange<T, I>,
2637
+ ) => MaybePromise<DocumentsChange<T, I> | void>;
2638
+ }
2639
+ | undefined;
2640
+ }
2641
+ | undefined => {
2642
+ if (u == null || u === false) return undefined;
2643
+ if (u === true)
2644
+ return {
2645
+ merge: {
2646
+ filter: (evt) => evt,
2647
+ },
2648
+ };
2649
+ if (typeof u === "object") {
2650
+ return {
2651
+ merge: u.merge
2652
+ ? {
2653
+ filter:
2654
+ typeof u.merge === "object" ? u.merge.filter : (evt) => evt,
2655
+ }
2656
+ : {},
2657
+ };
2658
+ }
2659
+ return undefined;
2660
+ };
2661
+
2662
+ const mergePolicy = normalizeUpdatesOption(options?.updates);
2663
+ const hasLiveUpdates = mergePolicy !== undefined;
2664
+
2665
+ // sorted-only mode: no per-queue handling
2666
+
2667
+ // If live updates enabled, ensure deferred exists so awaiting paths can block until changes
2668
+ if (hasLiveUpdates && !updateDeferred) {
2669
+ updateDeferred = pDefer<void>();
2670
+ }
2671
+
2672
+ let updatesCleanup: (() => void) | undefined;
2673
+ if (hasLiveUpdates) {
2674
+ const localHash = this.node.identity.publicKey.hashcode();
2675
+ if (mergePolicy?.merge) {
2676
+ // Ensure local buffer exists for sorted merging
2677
+ if (!peerBufferMap.has(localHash)) {
2678
+ peerBufferMap.set(localHash, { kept: 0, buffer: [] });
2679
+ }
2680
+ }
2681
+
2682
+ const onChange = async (evt: CustomEvent<DocumentsChange<T, I>>) => {
2683
+ // Optional filter to mutate/suppress change events
2684
+ let filtered: DocumentsChange<T, I> | void = evt.detail;
2685
+ if (mergePolicy?.merge?.filter) {
2686
+ filtered = await mergePolicy.merge?.filter(evt.detail);
2687
+ }
2688
+ if (filtered) {
2689
+ // Remove entries that were deleted from all pending structures
2690
+ if (filtered.removed?.length) {
2691
+ // Remove from peer buffers
2692
+ for (const [_peer, entry] of peerBufferMap) {
2693
+ entry.buffer = entry.buffer.filter((x) => {
2694
+ const id = indexerTypes.toId(
2695
+ this.indexByResolver(x.indexed),
2696
+ ).primitive;
2697
+ return !filtered!.removed!.some(
2698
+ (r) =>
2699
+ indexerTypes.toId(this.indexByResolver(r.__indexed))
2700
+ .primitive === id,
2701
+ );
2702
+ });
2703
+ }
2704
+ // no non-sorted queues in simplified mode
2705
+ }
2706
+
2707
+ // Add new entries per strategy (sorted-only)
2708
+ if (filtered.added?.length) {
2709
+ const buf = peerBufferMap.get(localHash)!;
2710
+ for (const added of filtered.added) {
2711
+ const id = indexerTypes.toId(
2712
+ this.indexByResolver(added.__indexed),
2713
+ ).primitive;
2714
+ if (visited.has(id)) continue; // already presented
2715
+ visited.add(id);
2716
+ buf.buffer.push({
2717
+ value: (resolve ? added : added.__indexed) as any,
2718
+ context: added.__context,
2719
+ from: this.node.identity.publicKey,
2720
+ indexed: coerceWithContext(added.__indexed, added.__context),
2721
+ });
2722
+ }
2723
+ buf.kept = buf.buffer.length;
2724
+ }
2725
+ }
2726
+ typeof options?.updates === "object" &&
2727
+ options?.updates?.onChange?.(evt.detail);
2728
+ signalUpdate();
2729
+ };
2730
+
2731
+ this.documentEvents.addEventListener("change", onChange);
2732
+ updatesCleanup = () => {
2733
+ this.documentEvents.removeEventListener("change", onChange);
2734
+ };
2735
+ const cleanupDefaultUpdates = cleanup;
2736
+ cleanup = () => {
2737
+ updatesCleanup?.();
2738
+ return cleanupDefaultUpdates();
2739
+ };
2740
+ }
2741
+
2742
+ if (typeof options?.remote === "object" && options?.remote.wait) {
2338
2743
  // was used to account for missed results when a peer joins; omitted in this minimal handler
2339
2744
 
2340
2745
  updateDeferred = pDefer<void>();
2341
2746
 
2342
2747
  // derive optional onMissedResults callback if provided
2343
2748
  let onMissedResults =
2344
- typeof options?.remote?.joining === "object" &&
2345
- typeof options?.remote.joining.onMissedResults === "function"
2346
- ? options.remote.joining.onMissedResults
2749
+ typeof options?.remote?.wait === "object" &&
2750
+ typeof options?.remote.onLateResults === "function"
2751
+ ? options.remote.onLateResults
2347
2752
  : undefined;
2348
2753
 
2349
2754
  const waitForTime =
2350
- typeof options.remote.joining === "object" &&
2351
- options.remote.joining.waitFor;
2755
+ typeof options.remote.wait === "object" && options.remote.wait.timeout;
2352
2756
 
2353
2757
  const prevMaybeSetDone = maybeSetDone;
2354
2758
  maybeSetDone = () => {
@@ -2362,15 +2766,22 @@ export class DocumentIndex<
2362
2766
  signalUpdate();
2363
2767
  }, waitForTime);
2364
2768
  controller.signal.addEventListener("abort", () => signalUpdate());
2365
-
2769
+ fetchedFirstForRemote = new Set<string>();
2366
2770
  joinListener = this.attachJoinListener({
2367
2771
  signal: controller.signal,
2772
+ eager: options.remote.reach?.eager,
2368
2773
  onPeer: async (pk) => {
2369
2774
  if (done) return;
2370
2775
  const hash = pk.hashcode();
2776
+ await fetchPromise; // ensure fetches in flight are done
2371
2777
  if (peerBufferMap.has(hash)) return;
2778
+ if (fetchedFirstForRemote!.has(hash)) return;
2372
2779
  if (totalFetchedCounter > 0) {
2373
- await fetchFirst(totalFetchedCounter, { from: [hash] });
2780
+ fetchPromise = fetchFirst(totalFetchedCounter, {
2781
+ from: [hash],
2782
+ fetchedFirstForRemote,
2783
+ });
2784
+ await fetchPromise;
2374
2785
  if (onMissedResults) {
2375
2786
  const pending = peerBufferMap.get(hash)?.buffer;
2376
2787
  if (pending && pending.length > 0) {
@@ -2409,6 +2820,29 @@ export class DocumentIndex<
2409
2820
  };
2410
2821
  }
2411
2822
 
2823
+ if (options?.closePolicy === "manual") {
2824
+ let prevMaybeSetDone = maybeSetDone;
2825
+ maybeSetDone = () => {
2826
+ if (drain) {
2827
+ prevMaybeSetDone();
2828
+ }
2829
+ };
2830
+ }
2831
+ const remoteWaitActive =
2832
+ typeof options?.remote === "object" && !!options.remote.wait;
2833
+
2834
+ const waitForUpdateAndResetDeferred = async () => {
2835
+ if (remoteWaitActive) {
2836
+ // wait until: join fetch adds results, cleanup runs, or the join-wait times out
2837
+ await _waitForUpdate();
2838
+
2839
+ // re-arm the deferred for the next cycle (only if joining is enabled and we're not done)
2840
+ if (updateDeferred && !doneFn()) {
2841
+ updateDeferred = pDefer<void>();
2842
+ }
2843
+ }
2844
+ };
2845
+
2412
2846
  return {
2413
2847
  close,
2414
2848
  next,
@@ -2421,26 +2855,24 @@ export class DocumentIndex<
2421
2855
  return kept; // TODO this should be more accurate
2422
2856
  },
2423
2857
  all: async () => {
2858
+ drain = true;
2424
2859
  let result: ValueTypeFromRequest<Resolve, T, I>[] = [];
2425
2860
  let c = 0;
2426
2861
  while (doneFn() !== true) {
2427
- c++;
2428
2862
  let batch = await next(100);
2429
- if (c > 100) {
2430
- break;
2863
+ c += batch.length;
2864
+ if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
2865
+ logger.warn(
2866
+ "Iterating for more than " +
2867
+ WARNING_WHEN_ITERATING_FOR_MORE_THAN +
2868
+ " results",
2869
+ );
2431
2870
  }
2432
2871
  if (batch.length > 0) {
2433
2872
  result.push(...batch);
2434
2873
  continue;
2435
2874
  }
2436
-
2437
- // wait until: join fetch adds results, cleanup runs, or the join-wait times out
2438
- await waitForUpdate();
2439
-
2440
- // re-arm the deferred for the next cycle (only if joining is enabled and we’re not done)
2441
- if (updateDeferred && !doneFn()) {
2442
- updateDeferred = pDefer<void>();
2443
- }
2875
+ await waitForUpdateAndResetDeferred();
2444
2876
  }
2445
2877
  cleanupAndDone();
2446
2878
  return result;
@@ -2453,6 +2885,26 @@ export class DocumentIndex<
2453
2885
  cleanupAndDone();
2454
2886
  return batch[0];
2455
2887
  },
2888
+ [Symbol.asyncIterator]: async function* () {
2889
+ drain = true;
2890
+ let c = 0;
2891
+ while (doneFn() !== true) {
2892
+ const batch = await next(100);
2893
+ c += batch.length;
2894
+ if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
2895
+ logger.warn(
2896
+ "Iterating for more than " +
2897
+ WARNING_WHEN_ITERATING_FOR_MORE_THAN +
2898
+ " results",
2899
+ );
2900
+ }
2901
+ for (const entry of batch) {
2902
+ yield entry;
2903
+ }
2904
+ await waitForUpdateAndResetDeferred();
2905
+ }
2906
+ cleanupAndDone();
2907
+ },
2456
2908
  };
2457
2909
  }
2458
2910
 
@@ -2568,26 +3020,15 @@ export class DocumentIndex<
2568
3020
  }
2569
3021
 
2570
3022
  public async waitFor(
2571
- other:
2572
- | PublicSignKey
2573
- | PeerId
2574
- | string
2575
- | (PublicSignKey | string | PeerId)[],
2576
- options?: { signal?: AbortSignal; timeout?: number },
2577
- ): Promise<void> {
2578
- await super.waitFor(other, options);
2579
- const ids = Array.isArray(other) ? other : [other];
2580
- const expectedHashes = new Set(
2581
- ids.map((x) =>
2582
- typeof x === "string"
2583
- ? x
2584
- : x instanceof PublicSignKey
2585
- ? x.hashcode()
2586
- : getPublicKeyFromPeerId(x).hashcode(),
2587
- ),
2588
- );
2589
-
2590
- for (const key of expectedHashes) {
3023
+ other: PeerRefs,
3024
+ options?: {
3025
+ seek?: "any" | "present";
3026
+ signal?: AbortSignal;
3027
+ timeout?: number;
3028
+ },
3029
+ ): Promise<string[]> {
3030
+ const hashes = await super.waitFor(other, options);
3031
+ for (const key of hashes) {
2591
3032
  await waitFor(
2592
3033
  async () =>
2593
3034
  (await this._log.replicationIndex.count({ query: { hash: key } })) >
@@ -2595,5 +3036,6 @@ export class DocumentIndex<
2595
3036
  options,
2596
3037
  );
2597
3038
  }
3039
+ return hashes;
2598
3040
  }
2599
3041
  }