@peerbit/document 9.11.7 → 9.11.8-863fe8e

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,5 +1,5 @@
1
1
  import { type AbstractType, field, serialize, variant } from "@dao-xyz/borsh";
2
- import type { PeerId } from "@libp2p/interface";
2
+ import type { PeerId, TypedEventTarget } from "@libp2p/interface";
3
3
  import { Cache } from "@peerbit/cache";
4
4
  import {
5
5
  type MaybePromise,
@@ -28,9 +28,11 @@ import {
28
28
  } from "@peerbit/shared-log";
29
29
  import { DataMessage, SilentDelivery } from "@peerbit/stream-interface";
30
30
  import { AbortError, waitFor } from "@peerbit/time";
31
+ import pDefer from "p-defer";
31
32
  import { concat, fromString } from "uint8arrays";
32
33
  import { copySerialization } from "./borsh.js";
33
34
  import { MAX_BATCH_SIZE } from "./constants.js";
35
+ import type { DocumentEvents, DocumentsChange } from "./events.js";
34
36
  import type { QueryPredictor } from "./most-common-query-predictor.js";
35
37
  import MostCommonQueryPredictor from "./most-common-query-predictor.js";
36
38
  import { type Operation, isPutOperation } from "./operation.js";
@@ -76,6 +78,21 @@ export type QueryOptions<R, D, Resolve extends boolean | undefined> = {
76
78
  resolve?: Resolve;
77
79
  signal?: AbortSignal;
78
80
  };
81
+
82
+ export type GetOptions<R, D, Resolve extends boolean | undefined> = {
83
+ remote?:
84
+ | boolean
85
+ | RemoteQueryOptions<
86
+ types.AbstractSearchRequest,
87
+ types.AbstractSearchResult,
88
+ D
89
+ >;
90
+ local?: boolean;
91
+ resolve?: Resolve;
92
+ signal?: AbortSignal;
93
+ waitFor?: number; // how long to wait for a non-empty result set
94
+ };
95
+
79
96
  export type SearchOptions<
80
97
  R,
81
98
  D,
@@ -228,6 +245,8 @@ function isSubclassOf(
228
245
  return false;
229
246
  }
230
247
 
248
+ const DEFAULT_TIMEOUT = 1e4;
249
+
231
250
  const DEFAULT_INDEX_BY = "id";
232
251
 
233
252
  export type CanSearch = (
@@ -298,6 +317,7 @@ export type OpenOptions<
298
317
  I,
299
318
  D extends ReplicationDomain<any, Operation, any>,
300
319
  > = {
320
+ documentEvents: TypedEventTarget<DocumentEvents<T, I>>;
301
321
  documentType: AbstractType<T>;
302
322
  dbType: AbstractType<types.IDocumentStore<T>>;
303
323
  log: SharedLog<Operation, D, any>;
@@ -399,6 +419,9 @@ export class DocumentIndex<
399
419
  private _maybeOpen: (value: T & Program) => Promise<T & Program>;
400
420
  private canSearch?: CanSearch;
401
421
  private canRead?: CanRead<I>;
422
+
423
+ private documentEvents: TypedEventTarget<DocumentEvents<T, I>>;
424
+
402
425
  private _joinListener?: (e: { detail: PublicSignKey }) => Promise<void>;
403
426
 
404
427
  private _resultQueue: Map<
@@ -455,7 +478,7 @@ export class DocumentIndex<
455
478
  this.indexedTypeIsDocumentType =
456
479
  !properties.transform?.type ||
457
480
  properties.transform?.type === properties.documentType;
458
-
481
+ this.documentEvents = properties.documentEvents;
459
482
  this.compatibility = properties.compatibility;
460
483
  this.canRead = properties.canRead;
461
484
  this.canSearch = properties.canSearch;
@@ -791,26 +814,58 @@ export class DocumentIndex<
791
814
  return dropped;
792
815
  }
793
816
 
794
- public async get<Options extends QueryOptions<T, D, true | undefined>>(
817
+ public async get<Options extends GetOptions<T, D, true | undefined>>(
795
818
  key: indexerTypes.Ideable | indexerTypes.IdKey,
796
819
  options?: Options,
797
820
  ): Promise<WithIndexedContext<T, I>>;
798
821
 
799
- public async get<Options extends QueryOptions<T, D, false>>(
822
+ public async get<Options extends GetOptions<T, D, false>>(
800
823
  key: indexerTypes.Ideable | indexerTypes.IdKey,
801
824
  options?: Options,
802
825
  ): Promise<WithContext<I>>;
803
826
 
804
827
  public async get<
805
- Options extends QueryOptions<T, D, Resolve>,
828
+ Options extends GetOptions<T, D, Resolve>,
806
829
  Resolve extends boolean | undefined = ExtractResolveFromOptions<Options>,
807
830
  >(key: indexerTypes.Ideable | indexerTypes.IdKey, options?: Options) {
808
- const result = (
809
- await this.getDetailed(
810
- key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key),
811
- options,
812
- )
813
- )?.[0]?.results[0];
831
+ let idKey =
832
+ key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key);
833
+ const result = (await this.getDetailed(idKey, options))?.[0]?.results[0];
834
+
835
+ // if no results, and we have remote joining options, we wait for the timout and if there are joining peers we re-query
836
+ if (!result) {
837
+ if (options?.waitFor) {
838
+ let deferred = pDefer<WithIndexedContext<T, I> | WithContext<I>>();
839
+
840
+ const listener = (evt: CustomEvent<DocumentsChange<T, I>>) => {
841
+ for (const added of evt.detail.added) {
842
+ const id = this.indexByResolver(added.__indexed);
843
+ if (id === idKey.primitive) {
844
+ deferred.resolve(added);
845
+ }
846
+ }
847
+ };
848
+
849
+ this.documentEvents.addEventListener("change", listener);
850
+
851
+ let cleanup = () => {
852
+ this.documentEvents.removeEventListener("change", listener);
853
+ clearTimeout(timeout);
854
+ this.events.removeEventListener("close", resolveUndefined);
855
+ };
856
+
857
+ let resolveUndefined = () => {
858
+ cleanup();
859
+ deferred.resolve(undefined);
860
+ };
861
+
862
+ let timeout = setTimeout(resolveUndefined, options.waitFor);
863
+ this.events.addEventListener("close", resolveUndefined);
864
+
865
+ return deferred.promise;
866
+ }
867
+ return undefined;
868
+ }
814
869
  return result?.value;
815
870
  }
816
871
 
@@ -820,6 +875,13 @@ export class DocumentIndex<
820
875
  await iterator.close();
821
876
  return one[0];
822
877
  }
878
+
879
+ public async getFromHash(hash: string) {
880
+ const iterator = this.index.iterate({ query: { hash } });
881
+ const one = await iterator.next(1);
882
+ await iterator.close();
883
+ return one[0];
884
+ }
823
885
  public async put(
824
886
  value: T,
825
887
  id: indexerTypes.IdKey,
@@ -1243,10 +1305,10 @@ export class DocumentIndex<
1243
1305
  }
1244
1306
  }
1245
1307
 
1246
- async processCloseIteratorRequest(
1308
+ processCloseIteratorRequest(
1247
1309
  query: types.CloseIteratorRequest,
1248
1310
  publicKey: PublicSignKey,
1249
- ): Promise<void> {
1311
+ ): void {
1250
1312
  const queueData = this._resultQueue.get(query.idString);
1251
1313
  if (queueData && !queueData.from.equals(publicKey)) {
1252
1314
  logger.info("Ignoring close iterator request from different peer");
@@ -1326,6 +1388,7 @@ export class DocumentIndex<
1326
1388
  : await this._log.getCover(remote.domain ?? { args: undefined }, {
1327
1389
  roleAge: remote.minAge,
1328
1390
  eager: remote.eager,
1391
+ 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
1329
1392
  });
1330
1393
 
1331
1394
  if (replicatorGroups) {
@@ -1682,8 +1745,8 @@ export class DocumentIndex<
1682
1745
  let t0 = +new Date();
1683
1746
  let waitForTime =
1684
1747
  typeof options.remote.joining === "boolean"
1685
- ? 1e4
1686
- : (options.remote.joining.waitFor ?? 1e4);
1748
+ ? DEFAULT_TIMEOUT
1749
+ : (options.remote.joining.waitFor ?? DEFAULT_TIMEOUT);
1687
1750
  let setDoneIfTimeout = false;
1688
1751
  maybeSetDone = () => {
1689
1752
  if (t0 + waitForTime < +new Date()) {
@@ -2128,15 +2191,24 @@ export class DocumentIndex<
2128
2191
  return dedup(coercedBatch, this.indexByResolver);
2129
2192
  };
2130
2193
 
2131
- let close = async () => {
2194
+ let cleanupAndDone = () => {
2132
2195
  cleanup();
2133
- done = true;
2134
2196
  controller.abort(new AbortError("Iterator closed"));
2197
+ this.prefetch?.accumulator.clear(queryRequestCoerced);
2198
+ this.processCloseIteratorRequest(
2199
+ queryRequestCoerced,
2200
+ this.node.identity.publicKey,
2201
+ );
2202
+ done = true;
2203
+ };
2135
2204
 
2205
+ let close = async () => {
2206
+ cleanupAndDone();
2207
+
2208
+ // send close to remote
2136
2209
  const closeRequest = new types.CloseIteratorRequest({
2137
2210
  id: queryRequestCoerced.id,
2138
2211
  });
2139
- this.prefetch?.accumulator.clear(queryRequestCoerced);
2140
2212
  const promises: Promise<any>[] = [];
2141
2213
 
2142
2214
  for (const [peer, buffer] of peerBufferMap) {
@@ -2144,15 +2216,7 @@ export class DocumentIndex<
2144
2216
  peerBufferMap.delete(peer);
2145
2217
  continue;
2146
2218
  }
2147
- // Fetch locally?
2148
- if (peer === this.node.identity.publicKey.hashcode()) {
2149
- promises.push(
2150
- this.processCloseIteratorRequest(
2151
- closeRequest,
2152
- this.node.identity.publicKey,
2153
- ),
2154
- );
2155
- } else {
2219
+ if (peer !== this.node.identity.publicKey.hashcode()) {
2156
2220
  // Close remote
2157
2221
  promises.push(
2158
2222
  this._query.send(closeRequest, {
@@ -2172,6 +2236,11 @@ export class DocumentIndex<
2172
2236
 
2173
2237
  let joinListener: ((e: { detail: PublicSignKey }) => void) | undefined;
2174
2238
 
2239
+ let updateDeferred: ReturnType<typeof pDefer> | undefined;
2240
+ const signalUpdate = () => updateDeferred?.resolve();
2241
+ const waitForUpdate = () =>
2242
+ updateDeferred ? updateDeferred.promise : Promise.resolve();
2243
+
2175
2244
  if (typeof options?.remote === "object" && options?.remote.joining) {
2176
2245
  let onMissedResults =
2177
2246
  typeof options?.remote?.joining === "object" &&
@@ -2179,7 +2248,38 @@ export class DocumentIndex<
2179
2248
  ? options.remote.joining.onMissedResults
2180
2249
  : undefined;
2181
2250
 
2251
+ updateDeferred = pDefer<void>();
2252
+
2253
+ const waitForTime =
2254
+ typeof options.remote.joining === "object" &&
2255
+ options.remote.joining.waitFor;
2256
+
2257
+ const prevMaybeSetDone = maybeSetDone;
2258
+ maybeSetDone = () => {
2259
+ prevMaybeSetDone();
2260
+ if (done) signalUpdate(); // break deferred waits
2261
+ };
2262
+
2263
+ let joinTimeoutId =
2264
+ waitForTime &&
2265
+ setTimeout(() => {
2266
+ signalUpdate();
2267
+ }, waitForTime);
2268
+ controller.signal.addEventListener("abort", () => signalUpdate());
2269
+
2270
+ let activeJoins = new Set<string>();
2271
+
2182
2272
  joinListener = async (e: { detail: PublicSignKey }) => {
2273
+ if (done) return;
2274
+ const pk = e.detail;
2275
+ const hash = pk.hashcode();
2276
+
2277
+ if (hash === this.node.identity.publicKey.hashcode()) return;
2278
+ if (peerBufferMap.has(hash)) return;
2279
+ if (activeJoins.has(hash)) return;
2280
+
2281
+ activeJoins.add(hash);
2282
+
2183
2283
  if (totalFetchedCounter > 0) {
2184
2284
  // wait for the node to become a replicator, then so query
2185
2285
  await this._log
@@ -2237,9 +2337,13 @@ export class DocumentIndex<
2237
2337
  }
2238
2338
  }
2239
2339
  }
2340
+ signalUpdate();
2240
2341
  })
2241
2342
  .catch(() => {
2242
2343
  /* TODO error handling */
2344
+ })
2345
+ .finally(() => {
2346
+ activeJoins.delete(hash);
2243
2347
  });
2244
2348
  }
2245
2349
  };
@@ -2247,6 +2351,9 @@ export class DocumentIndex<
2247
2351
  const cleanupDefault = cleanup;
2248
2352
  cleanup = () => {
2249
2353
  this._query.events.removeEventListener("join", joinListener!);
2354
+ joinTimeoutId && clearTimeout(joinTimeoutId);
2355
+ updateDeferred?.resolve();
2356
+ updateDeferred = undefined;
2250
2357
  return cleanupDefault();
2251
2358
  };
2252
2359
  }
@@ -2264,10 +2371,27 @@ export class DocumentIndex<
2264
2371
  },
2265
2372
  all: async () => {
2266
2373
  let result: ValueTypeFromRequest<Resolve, T, I>[] = [];
2374
+ let c = 0;
2267
2375
  while (doneFn() !== true) {
2376
+ c++;
2268
2377
  let batch = await next(100);
2269
- result.push(...batch);
2378
+ if (c > 100) {
2379
+ break;
2380
+ }
2381
+ if (batch.length > 0) {
2382
+ result.push(...batch);
2383
+ continue;
2384
+ }
2385
+
2386
+ // wait until: join fetch adds results, cleanup runs, or the join-wait times out
2387
+ await waitForUpdate();
2388
+
2389
+ // re-arm the deferred for the next cycle (only if joining is enabled and we’re not done)
2390
+ if (updateDeferred && !doneFn()) {
2391
+ updateDeferred = pDefer<void>();
2392
+ }
2270
2393
  }
2394
+ cleanupAndDone();
2271
2395
  return result;
2272
2396
  },
2273
2397
  };