@peerbit/document 10.0.4 → 10.1.0

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.
Files changed (46) hide show
  1. package/dist/benchmark/index.js +114 -59
  2. package/dist/benchmark/index.js.map +1 -1
  3. package/dist/benchmark/iterate-replicate-2.js +117 -63
  4. package/dist/benchmark/iterate-replicate-2.js.map +1 -1
  5. package/dist/benchmark/iterate-replicate.js +106 -56
  6. package/dist/benchmark/iterate-replicate.js.map +1 -1
  7. package/dist/benchmark/memory/child.js +114 -59
  8. package/dist/benchmark/memory/child.js.map +1 -1
  9. package/dist/benchmark/replication.js +106 -52
  10. package/dist/benchmark/replication.js.map +1 -1
  11. package/dist/src/domain.d.ts.map +1 -1
  12. package/dist/src/domain.js +1 -3
  13. package/dist/src/domain.js.map +1 -1
  14. package/dist/src/events.d.ts +1 -1
  15. package/dist/src/events.d.ts.map +1 -1
  16. package/dist/src/index.d.ts +1 -1
  17. package/dist/src/index.d.ts.map +1 -1
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/most-common-query-predictor.d.ts +3 -3
  20. package/dist/src/most-common-query-predictor.d.ts.map +1 -1
  21. package/dist/src/most-common-query-predictor.js.map +1 -1
  22. package/dist/src/operation.js +175 -81
  23. package/dist/src/operation.js.map +1 -1
  24. package/dist/src/prefetch.d.ts +2 -2
  25. package/dist/src/prefetch.d.ts.map +1 -1
  26. package/dist/src/prefetch.js.map +1 -1
  27. package/dist/src/program.d.ts +2 -2
  28. package/dist/src/program.d.ts.map +1 -1
  29. package/dist/src/program.js +550 -508
  30. package/dist/src/program.js.map +1 -1
  31. package/dist/src/resumable-iterator.d.ts.map +1 -1
  32. package/dist/src/resumable-iterator.js +44 -0
  33. package/dist/src/resumable-iterator.js.map +1 -1
  34. package/dist/src/search.d.ts +14 -10
  35. package/dist/src/search.d.ts.map +1 -1
  36. package/dist/src/search.js +2477 -2120
  37. package/dist/src/search.js.map +1 -1
  38. package/package.json +21 -19
  39. package/src/domain.ts +1 -3
  40. package/src/events.ts +1 -1
  41. package/src/index.ts +1 -0
  42. package/src/most-common-query-predictor.ts +19 -5
  43. package/src/prefetch.ts +12 -3
  44. package/src/program.ts +7 -5
  45. package/src/resumable-iterator.ts +44 -0
  46. package/src/search.ts +564 -196
@@ -1,11 +1,36 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
6
27
  };
7
- var __metadata = (this && this.__metadata) || function (k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
9
34
  };
10
35
  import { field, serialize, variant } from "@dao-xyz/borsh";
11
36
  import { Cache } from "@peerbit/cache";
@@ -30,9 +55,14 @@ import { isPutOperation } from "./operation.js";
30
55
  import { Prefetch } from "./prefetch.js";
31
56
  import { ResumableIterators } from "./resumable-iterator.js";
32
57
  const WARNING_WHEN_ITERATING_FOR_MORE_THAN = 1e5;
33
- const logger = loggerFn({
34
- module: "document-index",
35
- });
58
+ const logger = loggerFn("peerbit:program:document:search");
59
+ const warn = logger.newScope("warn");
60
+ const documentIndexLogger = loggerFn("peerbit:document:index");
61
+ const indexLifecycleLogger = documentIndexLogger.newScope("lifecycle");
62
+ const indexRpcLogger = documentIndexLogger.newScope("rpc");
63
+ const indexCacheLogger = documentIndexLogger.newScope("cache");
64
+ const indexPrefetchLogger = documentIndexLogger.newScope("prefetch");
65
+ const indexIteratorLogger = documentIndexLogger.newScope("iterate");
36
66
  const coerceQuery = (query, options, compatibility) => {
37
67
  const replicate = typeof options?.remote !== "boolean" ? options?.remote?.replicate : false;
38
68
  const shouldResolve = options?.resolve !== false;
@@ -163,6 +193,7 @@ function isSubclassOf(SubClass, SuperClass) {
163
193
  return false;
164
194
  }
165
195
  const DEFAULT_TIMEOUT = 1e4;
196
+ const DEFAULT_KEEP_REMOTE_ITERATOR_TIMEOUT = 3e5;
166
197
  const DISCOVER_TIMEOUT_FALLBACK = 500;
167
198
  const DEFAULT_INDEX_BY = "id";
168
199
  const isTransformerWithFunction = (options) => {
@@ -178,1036 +209,1289 @@ export const coerceWithIndexed = (value, indexed) => {
178
209
  valueWithContext.__indexed = indexed;
179
210
  return valueWithContext;
180
211
  };
181
- let DocumentIndex = class DocumentIndex extends Program {
182
- _query;
183
- // Original document representation
184
- documentType;
185
- // transform options
186
- transformer;
187
- // The indexed document wrapped in a context
188
- wrappedIndexedType;
189
- indexedType;
190
- // The database type, for recursive indexing
191
- dbType;
192
- indexedTypeIsDocumentType;
193
- // Index key
194
- indexBy;
195
- indexByResolver;
196
- index;
197
- _resumableIterators;
198
- _prefetch;
199
- includeIndexed = undefined;
200
- compatibility;
201
- // Transformation, indexer
202
- /* fields: IndexableFields<T, I>; */
203
- _valueEncoding;
204
- _sync;
205
- _log;
206
- _resolverProgramCache;
207
- _resolverCache;
208
- isProgramValued;
209
- _maybeOpen;
210
- canSearch;
211
- canRead;
212
- documentEvents;
213
- _joinListener;
214
- _resultQueue;
215
- iteratorKeepAliveTimers;
216
- constructor(properties) {
217
- super();
218
- this._query = properties?.query || new RPC();
219
- this.iteratorKeepAliveTimers = new Map();
220
- }
221
- get valueEncoding() {
222
- return this._valueEncoding;
223
- }
224
- get nestedProperties() {
225
- return {
226
- match: (obj) => obj instanceof this.dbType,
227
- iterate: async (obj, query) => obj.index.search(query),
228
- };
229
- }
230
- async open(properties) {
231
- this._log = properties.log;
232
- let prefectOptions = typeof properties.prefetch === "object"
233
- ? properties.prefetch
234
- : properties.prefetch
235
- ? {}
236
- : undefined;
237
- this._prefetch = prefectOptions
238
- ? {
239
- ...prefectOptions,
240
- predictor: prefectOptions.predictor || new MostCommonQueryPredictor(3),
241
- ttl: prefectOptions.ttl ?? 5e3,
242
- accumulator: prefectOptions.accumulator || new Prefetch(),
243
- }
244
- : undefined;
245
- this.documentType = properties.documentType;
246
- this.indexedTypeIsDocumentType =
247
- !properties.transform?.type ||
248
- properties.transform?.type === properties.documentType;
249
- this.documentEvents = properties.documentEvents;
250
- this.compatibility = properties.compatibility;
251
- this.canRead = properties.canRead;
252
- this.canSearch = properties.canSearch;
253
- this.includeIndexed = properties.includeIndexed;
254
- let IndexedClassWithContext = class IndexedClassWithContext {
255
- __context;
256
- constructor(value, context) {
257
- Object.assign(this, value);
258
- this.__context = context;
212
+ let DocumentIndex = (() => {
213
+ let _classDecorators = [variant("documents_index")];
214
+ let _classDescriptor;
215
+ let _classExtraInitializers = [];
216
+ let _classThis;
217
+ let _classSuper = Program;
218
+ let __query_decorators;
219
+ let __query_initializers = [];
220
+ let __query_extraInitializers = [];
221
+ var DocumentIndex = class extends _classSuper {
222
+ static { _classThis = this; }
223
+ static {
224
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
225
+ __query_decorators = [field({ type: RPC })];
226
+ __esDecorate(null, null, __query_decorators, { kind: "field", name: "_query", static: false, private: false, access: { has: obj => "_query" in obj, get: obj => obj._query, set: (obj, value) => { obj._query = value; } }, metadata: _metadata }, __query_initializers, __query_extraInitializers);
227
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
228
+ DocumentIndex = _classThis = _classDescriptor.value;
229
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
230
+ __runInitializers(_classThis, _classExtraInitializers);
231
+ }
232
+ _query = __runInitializers(this, __query_initializers, void 0);
233
+ // Original document representation
234
+ documentType = __runInitializers(this, __query_extraInitializers);
235
+ // transform options
236
+ transformer;
237
+ // The indexed document wrapped in a context
238
+ wrappedIndexedType;
239
+ indexedType;
240
+ // The database type, for recursive indexing
241
+ dbType;
242
+ indexedTypeIsDocumentType;
243
+ // Index key
244
+ indexBy;
245
+ indexByResolver;
246
+ index;
247
+ _resumableIterators;
248
+ _prefetch;
249
+ includeIndexed = undefined;
250
+ compatibility;
251
+ // Transformation, indexer
252
+ /* fields: IndexableFields<T, I>; */
253
+ _valueEncoding;
254
+ _sync;
255
+ _log;
256
+ _resolverProgramCache;
257
+ _resolverCache;
258
+ isProgramValued;
259
+ _maybeOpen;
260
+ canSearch;
261
+ canRead;
262
+ documentEvents;
263
+ _joinListener;
264
+ _resultQueue;
265
+ iteratorKeepAliveTimers;
266
+ constructor(properties) {
267
+ super();
268
+ this._query = properties?.query || new RPC();
269
+ this.iteratorKeepAliveTimers = new Map();
270
+ }
271
+ get valueEncoding() {
272
+ return this._valueEncoding;
273
+ }
274
+ ensurePrefetchAccumulator() {
275
+ if (!this._prefetch) {
276
+ this._prefetch = {
277
+ accumulator: new Prefetch(),
278
+ ttl: 5e3,
279
+ };
280
+ return;
259
281
  }
260
- };
261
- __decorate([
262
- field({ type: types.Context }),
263
- __metadata("design:type", types.Context)
264
- ], IndexedClassWithContext.prototype, "__context", void 0);
265
- IndexedClassWithContext = __decorate([
266
- variant(0),
267
- __metadata("design:paramtypes", [Object, types.Context])
268
- ], IndexedClassWithContext);
269
- // copy all prototype values from indexedType to IndexedClassWithContext
270
- this.indexedType = (properties.transform?.type || properties.documentType);
271
- copySerialization(this.indexedType, IndexedClassWithContext);
272
- this.wrappedIndexedType = IndexedClassWithContext;
273
- // if this.type is a class that extends Program we want to do special functionality
274
- this.isProgramValued = isSubclassOf(this.documentType, Program);
275
- this.dbType = properties.dbType;
276
- this._resultQueue = new Map();
277
- this._sync = (request, results) => {
278
- let rq;
279
- let rs;
280
- if (request instanceof types.PredictedSearchRequest) {
281
- // TODO is this codepath even reachable?
282
- throw new Error("Unexpected PredictedSearchRequest in sync operation");
282
+ if (!this._prefetch.accumulator) {
283
+ this._prefetch.accumulator = new Prefetch();
283
284
  }
284
- else {
285
- rq = request;
286
- rs = results;
285
+ }
286
+ async wrapPushResults(matches, resolve) {
287
+ if (!matches.length)
288
+ return [];
289
+ const results = [];
290
+ for (const match of matches) {
291
+ if (resolve) {
292
+ const doc = match;
293
+ const indexedValue = await this.transformer(doc, doc.__context);
294
+ const wrappedIndexed = coerceWithContext(indexedValue, doc.__context);
295
+ results.push(new types.ResultValue({
296
+ context: doc.__context,
297
+ value: doc,
298
+ source: serialize(doc),
299
+ indexed: wrappedIndexed,
300
+ }));
301
+ }
302
+ else {
303
+ const indexed = match;
304
+ const head = await this._log.log.get(indexed.__context.head);
305
+ results.push(new types.ResultIndexedValue({
306
+ context: indexed.__context,
307
+ source: serialize(indexed),
308
+ indexed: indexed,
309
+ entries: head ? [head] : [],
310
+ }));
311
+ }
287
312
  }
288
- return properties.replicate(rq, rs);
289
- };
290
- const transformOptions = properties.transform;
291
- this.transformer = transformOptions
292
- ? isTransformerWithFunction(transformOptions)
293
- ? (obj, context) => transformOptions.transform(obj, context)
294
- : transformOptions.type
295
- ? (obj, context) => new transformOptions.type(obj, context)
296
- : (obj) => obj
297
- : (obj) => obj; // TODO types
298
- const maybeArr = properties.indexBy || DEFAULT_INDEX_BY;
299
- this.indexBy = Array.isArray(maybeArr) ? maybeArr : [maybeArr];
300
- this.indexByResolver = (obj) => indexerTypes.extractFieldValue(obj, this.indexBy);
301
- this._valueEncoding = BORSH_ENCODING(this.documentType);
302
- this._resolverCache =
303
- properties.cache?.resolver === 0
304
- ? undefined
305
- : new Cache({ max: properties.cache?.resolver ?? 100 }); // TODO choose limit better by default (adaptive)
306
- this.index =
307
- (await (await this.node.indexer.scope(sha256Base64Sync(concat([this._log.log.id, fromString("/document-index")])))).init({
308
- indexBy: this.indexBy,
309
- schema: this.wrappedIndexedType,
310
- nested: this.nestedProperties,
311
- /* maxBatchSize: MAX_BATCH_SIZE */
312
- })) || new HashmapIndex();
313
- if (properties.cache?.query) {
314
- this.index = new CachedIndex(this.index, properties.cache.query);
313
+ return results;
315
314
  }
316
- this._resumableIterators = new ResumableIterators(this.index);
317
- this._maybeOpen = properties.maybeOpen;
318
- if (this.isProgramValued) {
319
- this._resolverProgramCache = new Map();
315
+ async drainQueuedResults(queueEntries, resolve) {
316
+ if (!queueEntries.length) {
317
+ return [];
318
+ }
319
+ const drained = queueEntries.splice(0);
320
+ const results = [];
321
+ for (const entry of drained) {
322
+ const indexedUnwrapped = Object.assign(Object.create(this.indexedType.prototype), entry.value);
323
+ if (resolve) {
324
+ const value = await this.resolveDocument({
325
+ indexed: entry.value,
326
+ head: entry.value.__context.head,
327
+ });
328
+ if (!value)
329
+ continue;
330
+ results.push(new types.ResultValue({
331
+ context: entry.value.__context,
332
+ value: value.value,
333
+ source: serialize(value.value),
334
+ indexed: indexedUnwrapped,
335
+ }));
336
+ }
337
+ else {
338
+ const head = await this._log.log.get(entry.value.__context.head);
339
+ results.push(new types.ResultIndexedValue({
340
+ context: entry.value.__context,
341
+ source: serialize(indexedUnwrapped),
342
+ indexed: indexedUnwrapped,
343
+ entries: head ? [head] : [],
344
+ }));
345
+ }
346
+ }
347
+ return results;
320
348
  }
321
- if (this.prefetch?.predictor) {
322
- const predictor = this.prefetch.predictor;
323
- this._joinListener = async (e) => {
324
- // on join we emit predicted search results before peers query us (to save latency but for the price of errornous bandwidth usage)
325
- if ((await this._log.isReplicating()) === false) {
326
- return;
349
+ handleDocumentChange = async (event) => {
350
+ const added = event.detail.added;
351
+ if (!added.length) {
352
+ return;
353
+ }
354
+ for (const [_iteratorId, queue] of this._resultQueue) {
355
+ if (!queue.pushMode ||
356
+ queue.pushMode !== types.PushUpdatesMode.STREAM ||
357
+ queue.pushInFlight) {
358
+ continue;
327
359
  }
328
- // TODO
329
- // it only makes sense for use to return predicted results if the peer is to choose us as a replicator
330
- // so we need to calculate the cover set from the peers perspective
331
- // create an iterator and send the peer the results
332
- let request = predictor.predictedQuery(e.detail);
333
- if (!request) {
334
- return;
360
+ if (!(queue.fromQuery instanceof types.IterationRequest)) {
361
+ continue;
335
362
  }
336
- const results = await this.handleSearchRequest(request, {
337
- from: e.detail,
338
- });
339
- if (results instanceof types.AbstractSearchResult) {
340
- // start a resumable iterator for the peer
341
- const query = new types.PredictedSearchRequest({
342
- id: request.id,
343
- request,
344
- results,
363
+ queue.pushInFlight = true;
364
+ try {
365
+ const resolveFlag = queue.resolveResults ??
366
+ resolvesDocuments(queue.fromQuery);
367
+ const batches = [];
368
+ const queued = await this.drainQueuedResults(queue.queue, resolveFlag);
369
+ if (queued.length) {
370
+ batches.push(...queued);
371
+ }
372
+ // TODO drain only up to the changed document instead of flushing the entire queue
373
+ const matches = await this.updateResults([], { added }, {
374
+ query: queue.fromQuery.query,
375
+ sort: queue.fromQuery.sort,
376
+ }, resolveFlag);
377
+ if (matches.length) {
378
+ const wrapped = await this.wrapPushResults(matches, resolveFlag);
379
+ if (wrapped.length) {
380
+ batches.push(...wrapped);
381
+ }
382
+ }
383
+ if (!batches.length) {
384
+ continue;
385
+ }
386
+ const pushMessage = new types.PredictedSearchRequest({
387
+ id: queue.fromQuery.id,
388
+ request: queue.fromQuery,
389
+ results: new types.Results({
390
+ results: batches,
391
+ kept: 0n,
392
+ }),
345
393
  });
346
- await this._query.send(query, {
347
- mode: new SilentDelivery({ to: [e.detail], redundancy: 1 }),
394
+ await this._query.send(pushMessage, {
395
+ mode: new SilentDelivery({
396
+ to: [queue.from],
397
+ redundancy: 1,
398
+ }),
348
399
  });
349
400
  }
401
+ catch (error) {
402
+ logger.error("Failed to push iterator update", error);
403
+ }
404
+ finally {
405
+ queue.pushInFlight = false;
406
+ }
407
+ }
408
+ };
409
+ get nestedProperties() {
410
+ return {
411
+ match: (obj) => obj instanceof this.dbType,
412
+ iterate: async (obj, query) => obj.index.search(query),
350
413
  };
351
- // we do this before _query.open so that we can receive the join event, even immediate ones
352
- this._query.events.addEventListener("join", this._joinListener);
353
414
  }
354
- await this._query.open({
355
- topic: sha256Base64Sync(concat([this._log.log.id, fromString("/document")])),
356
- responseHandler: this.queryRPCResponseHandler.bind(this),
357
- responseType: types.AbstractSearchResult,
358
- queryType: types.AbstractSearchRequest,
359
- });
360
- }
361
- get prefetch() {
362
- return this._prefetch;
363
- }
364
- async queryRPCResponseHandler(query, ctx) {
365
- if (!ctx.from) {
366
- logger.info("Receieved query without from");
367
- return;
368
- }
369
- if (query instanceof types.PredictedSearchRequest) {
370
- // put results in a waiting cache so that we eventually in the future will query a matching thing, we already have results available
371
- this._prefetch?.accumulator.add({
372
- message: ctx.message,
373
- response: query,
374
- from: ctx.from,
375
- }, ctx.from.hashcode());
376
- return;
377
- }
378
- if (this.prefetch?.predictor &&
379
- (query instanceof types.SearchRequest ||
380
- query instanceof types.SearchRequestIndexed ||
381
- query instanceof types.IterationRequest)) {
382
- const { ignore } = this.prefetch.predictor.onRequest(query, {
383
- from: ctx.from,
415
+ async open(properties) {
416
+ this._log = properties.log;
417
+ // Allow reopening with partial options (tests override the index transform)
418
+ const previousEvents = this.documentEvents;
419
+ this.documentEvents =
420
+ properties.documentEvents ?? previousEvents ?? this.events;
421
+ this.compatibility =
422
+ properties.compatibility !== undefined
423
+ ? properties.compatibility
424
+ : this.compatibility;
425
+ let prefectOptions = typeof properties.prefetch === "object"
426
+ ? properties.prefetch
427
+ : properties.prefetch
428
+ ? {}
429
+ : undefined;
430
+ this._prefetch = prefectOptions
431
+ ? {
432
+ ...prefectOptions,
433
+ predictor: prefectOptions.predictor || new MostCommonQueryPredictor(3),
434
+ ttl: prefectOptions.ttl ?? 5e3,
435
+ accumulator: prefectOptions.accumulator || new Prefetch(),
436
+ }
437
+ : undefined;
438
+ this.documentType = properties.documentType;
439
+ this.indexedTypeIsDocumentType =
440
+ !properties.transform?.type ||
441
+ properties.transform?.type === properties.documentType;
442
+ this.canRead = properties.canRead;
443
+ this.canSearch = properties.canSearch;
444
+ this.includeIndexed = properties.includeIndexed;
445
+ let IndexedClassWithContext = (() => {
446
+ let _classDecorators = [variant(0)];
447
+ let _classDescriptor;
448
+ let _classExtraInitializers = [];
449
+ let _classThis;
450
+ let ___context_decorators;
451
+ let ___context_initializers = [];
452
+ let ___context_extraInitializers = [];
453
+ var IndexedClassWithContext = class {
454
+ static { _classThis = this; }
455
+ static {
456
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
457
+ ___context_decorators = [field({ type: types.Context })];
458
+ __esDecorate(null, null, ___context_decorators, { kind: "field", name: "__context", static: false, private: false, access: { has: obj => "__context" in obj, get: obj => obj.__context, set: (obj, value) => { obj.__context = value; } }, metadata: _metadata }, ___context_initializers, ___context_extraInitializers);
459
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
460
+ IndexedClassWithContext = _classThis = _classDescriptor.value;
461
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
462
+ __runInitializers(_classThis, _classExtraInitializers);
463
+ }
464
+ __context = __runInitializers(this, ___context_initializers, void 0);
465
+ constructor(value, context) {
466
+ __runInitializers(this, ___context_extraInitializers);
467
+ Object.assign(this, value);
468
+ this.__context = context;
469
+ }
470
+ };
471
+ return IndexedClassWithContext = _classThis;
472
+ })();
473
+ // copy all prototype values from indexedType to IndexedClassWithContext
474
+ this.indexedType = (properties.transform?.type || properties.documentType);
475
+ copySerialization(this.indexedType, IndexedClassWithContext);
476
+ this.wrappedIndexedType = IndexedClassWithContext;
477
+ // if this.type is a class that extends Program we want to do special functionality
478
+ this.isProgramValued = isSubclassOf(this.documentType, Program);
479
+ this.dbType = properties.dbType;
480
+ this._resultQueue = new Map();
481
+ const replicateFn = properties.replicate ?? this._sync ?? (() => Promise.resolve());
482
+ this._sync = (request, results) => {
483
+ let rq;
484
+ let rs;
485
+ if (request instanceof types.PredictedSearchRequest) {
486
+ // TODO is this codepath even reachable?
487
+ throw new Error("Unexpected PredictedSearchRequest in sync operation");
488
+ }
489
+ else {
490
+ rq = request;
491
+ rs = results;
492
+ }
493
+ return replicateFn(rq, rs);
494
+ };
495
+ const transformOptions = properties.transform;
496
+ this.transformer = transformOptions
497
+ ? isTransformerWithFunction(transformOptions)
498
+ ? (obj, context) => transformOptions.transform(obj, context)
499
+ : transformOptions.type
500
+ ? (obj, context) => new transformOptions.type(obj, context)
501
+ : (obj) => obj
502
+ : (obj) => obj; // TODO types
503
+ const maybeArr = properties.indexBy || DEFAULT_INDEX_BY;
504
+ this.indexBy = Array.isArray(maybeArr) ? maybeArr : [maybeArr];
505
+ this.indexByResolver = (obj) => indexerTypes.extractFieldValue(obj, this.indexBy);
506
+ this._valueEncoding = BORSH_ENCODING(this.documentType);
507
+ this._resolverCache =
508
+ properties.cache?.resolver === 0
509
+ ? undefined
510
+ : new Cache({ max: properties.cache?.resolver ?? 100 }); // TODO choose limit better by default (adaptive)
511
+ this.index =
512
+ (await (await this.node.indexer.scope(sha256Base64Sync(concat([this._log.log.id, fromString("/document-index")])))).init({
513
+ indexBy: this.indexBy,
514
+ schema: this.wrappedIndexedType,
515
+ nested: this.nestedProperties,
516
+ /* maxBatchSize: MAX_BATCH_SIZE */
517
+ })) || new HashmapIndex();
518
+ if (properties.cache?.query) {
519
+ this.index = new CachedIndex(this.index, properties.cache.query);
520
+ }
521
+ indexLifecycleLogger("opened document index", {
522
+ peer: this.node.identity.publicKey.hashcode(),
523
+ indexBy: this.indexBy,
524
+ includeIndexed: this.includeIndexed === true,
525
+ cacheResolver: Boolean(this._resolverCache),
526
+ prefetch: Boolean(this.prefetch),
384
527
  });
385
- if (ignore) {
386
- if (this.prefetch.strict) {
387
- return;
528
+ this._resumableIterators = new ResumableIterators(this.index);
529
+ this._maybeOpen = properties.maybeOpen;
530
+ if (this.isProgramValued) {
531
+ this._resolverProgramCache = new Map();
532
+ }
533
+ if (this.prefetch?.predictor) {
534
+ indexPrefetchLogger("prefetch predictor enabled", {
535
+ peer: this.node.identity.publicKey.hashcode(),
536
+ strict: Boolean(this.prefetch?.strict),
537
+ });
538
+ const predictor = this.prefetch.predictor;
539
+ this._joinListener = async (e) => {
540
+ // on join we emit predicted search results before peers query us (to save latency but for the price of errornous bandwidth usage)
541
+ if ((await this._log.isReplicating()) === false) {
542
+ return;
543
+ }
544
+ indexPrefetchLogger("peer join triggered predictor", {
545
+ target: e.detail.hashcode(),
546
+ });
547
+ // TODO
548
+ // it only makes sense for use to return predicted results if the peer is to choose us as a replicator
549
+ // so we need to calculate the cover set from the peers perspective
550
+ // create an iterator and send the peer the results
551
+ let request = predictor.predictedQuery(e.detail);
552
+ if (!request) {
553
+ indexPrefetchLogger("predictor had no cached query", {
554
+ target: e.detail.hashcode(),
555
+ });
556
+ return;
557
+ }
558
+ indexPrefetchLogger("sending predicted results", {
559
+ target: e.detail.hashcode(),
560
+ request: request.idString,
561
+ });
562
+ const results = await this.handleSearchRequest(request, {
563
+ from: e.detail,
564
+ });
565
+ if (results instanceof types.AbstractSearchResult) {
566
+ // start a resumable iterator for the peer
567
+ const query = new types.PredictedSearchRequest({
568
+ id: request.id,
569
+ request,
570
+ results,
571
+ });
572
+ await this._query.send(query, {
573
+ mode: new SilentDelivery({ to: [e.detail], redundancy: 1 }),
574
+ });
575
+ }
576
+ };
577
+ // we do this before _query.open so that we can receive the join event, even immediate ones
578
+ if (this._joinListener) {
579
+ this._query.events.addEventListener("join", this._joinListener);
388
580
  }
389
581
  }
582
+ await this._query.open({
583
+ topic: sha256Base64Sync(concat([this._log.log.id, fromString("/document")])),
584
+ responseHandler: this.queryRPCResponseHandler.bind(this),
585
+ responseType: types.AbstractSearchResult,
586
+ queryType: types.AbstractSearchRequest,
587
+ });
588
+ if (this.handleDocumentChange) {
589
+ this.documentEvents.addEventListener("change", this.handleDocumentChange);
590
+ }
390
591
  }
391
- return this.handleSearchRequest(query, {
392
- from: ctx.from,
393
- });
394
- }
395
- async handleSearchRequest(query, ctx) {
396
- if (this.canSearch &&
397
- (query instanceof types.SearchRequest ||
398
- query instanceof types.IterationRequest ||
399
- query instanceof types.CollectNextRequest) &&
400
- !(await this.canSearch(query, ctx.from))) {
401
- return new types.NoAccess();
592
+ get prefetch() {
593
+ return this._prefetch;
402
594
  }
403
- if (query instanceof types.CloseIteratorRequest) {
404
- this.processCloseIteratorRequest(query, ctx.from);
595
+ async queryRPCResponseHandler(query, ctx) {
596
+ if (!ctx.from) {
597
+ logger("Receieved query without from");
598
+ return;
599
+ }
600
+ indexRpcLogger("received request", {
601
+ type: query.constructor.name,
602
+ from: ctx.from.hashcode(),
603
+ id: query.idString,
604
+ });
605
+ if (query instanceof types.PredictedSearchRequest) {
606
+ // put results in a waiting cache so that we eventually in the future will query a matching thing, we already have results available
607
+ this._prefetch?.accumulator.add({
608
+ message: ctx.message,
609
+ response: query,
610
+ from: ctx.from,
611
+ }, ctx.from.hashcode());
612
+ indexPrefetchLogger("cached predicted results", {
613
+ from: ctx.from.hashcode(),
614
+ request: query.idString,
615
+ });
616
+ return;
617
+ }
618
+ if (this.prefetch?.predictor &&
619
+ (query instanceof types.SearchRequest ||
620
+ query instanceof types.SearchRequestIndexed ||
621
+ query instanceof types.IterationRequest)) {
622
+ const { ignore } = this.prefetch.predictor.onRequest(query, {
623
+ from: ctx.from,
624
+ });
625
+ if (ignore) {
626
+ indexPrefetchLogger("predictor ignored request", {
627
+ from: ctx.from.hashcode(),
628
+ request: query.idString,
629
+ strict: Boolean(this.prefetch?.strict),
630
+ });
631
+ if (this.prefetch.strict) {
632
+ return;
633
+ }
634
+ }
635
+ }
636
+ try {
637
+ const out = await this.handleSearchRequest(query, {
638
+ from: ctx.from,
639
+ });
640
+ return out;
641
+ }
642
+ catch (error) {
643
+ throw error;
644
+ }
405
645
  }
406
- else {
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;
414
- const results = await this.processQuery(query, ctx.from, false, {
415
- canRead: this.canRead,
646
+ async handleSearchRequest(query, ctx) {
647
+ indexRpcLogger("handling query", {
648
+ type: query.constructor.name,
649
+ id: query.idString,
650
+ from: ctx.from.hashcode(),
416
651
  });
417
- if (shouldIncludedIndexedResults) {
418
- let resultsWithIndexed = results.results;
419
- let fromLength = results.results.length;
420
- for (let i = 0; i < fromLength; i++) {
421
- let result = results.results[i];
422
- resultsWithIndexed.push(new types.ResultIndexedValue({
423
- source: serialize(result.indexed),
424
- indexed: result.indexed,
425
- context: result.context,
426
- entries: [],
427
- }));
652
+ if (this.canSearch &&
653
+ (query instanceof types.SearchRequest ||
654
+ query instanceof types.IterationRequest ||
655
+ query instanceof types.CollectNextRequest) &&
656
+ !(await this.canSearch(query, ctx.from))) {
657
+ indexRpcLogger("denied query", {
658
+ id: query.idString,
659
+ from: ctx.from.hashcode(),
660
+ });
661
+ return new types.NoAccess();
662
+ }
663
+ if (query instanceof types.CloseIteratorRequest) {
664
+ this.processCloseIteratorRequest(query, ctx.from);
665
+ }
666
+ else {
667
+ const fromQueued = query instanceof types.CollectNextRequest
668
+ ? this._resultQueue.get(query.idString)?.fromQuery
669
+ : undefined;
670
+ const queryResolvesDocuments = query instanceof types.CollectNextRequest
671
+ ? resolvesDocuments(fromQueued)
672
+ : resolvesDocuments(query);
673
+ const shouldIncludedIndexedResults = this.includeIndexed && queryResolvesDocuments;
674
+ const results = await this.processQuery(query, ctx.from, false, {
675
+ canRead: this.canRead,
676
+ });
677
+ indexRpcLogger("query results ready", {
678
+ id: query.idString,
679
+ from: ctx.from.hashcode(),
680
+ count: results.results.length,
681
+ kept: results.kept,
682
+ includeIndexed: shouldIncludedIndexedResults,
683
+ });
684
+ if (shouldIncludedIndexedResults) {
685
+ let resultsWithIndexed = results.results;
686
+ let fromLength = results.results.length;
687
+ for (let i = 0; i < fromLength; i++) {
688
+ let result = results.results[i];
689
+ resultsWithIndexed.push(new types.ResultIndexedValue({
690
+ source: serialize(result.indexed),
691
+ indexed: result.indexed,
692
+ context: result.context,
693
+ entries: [],
694
+ }));
695
+ }
696
+ return new types.Results({
697
+ // Even if results might have length 0, respond, because then we now at least there are no matching results
698
+ results: resultsWithIndexed,
699
+ kept: results.kept,
700
+ });
428
701
  }
429
702
  return new types.Results({
430
703
  // Even if results might have length 0, respond, because then we now at least there are no matching results
431
- results: resultsWithIndexed,
704
+ results: results.results,
432
705
  kept: results.kept,
433
706
  });
434
707
  }
435
- return new types.Results({
436
- // Even if results might have length 0, respond, because then we now at least there are no matching results
437
- results: results.results,
438
- kept: results.kept,
439
- });
440
708
  }
441
- }
442
- async afterOpen() {
443
- if (this.isProgramValued) {
444
- // re-open the program cache
445
- for (const { id, value } of await this.index.iterate().all()) {
446
- const programValue = await this.resolveDocument({
447
- indexed: value,
448
- head: value.__context.head,
449
- });
450
- if (!programValue) {
451
- logger.error("Missing program value after re-opening the document index. Hash: " +
452
- value.__context.head);
453
- continue;
709
+ async afterOpen() {
710
+ if (this.isProgramValued) {
711
+ // re-open the program cache
712
+ for (const { id, value } of await this.index.iterate().all()) {
713
+ const programValue = await this.resolveDocument({
714
+ indexed: value,
715
+ head: value.__context.head,
716
+ });
717
+ if (!programValue) {
718
+ logger.error("Missing program value after re-opening the document index. Hash: " +
719
+ value.__context.head);
720
+ continue;
721
+ }
722
+ programValue.value = await this._maybeOpen(programValue.value);
723
+ this._resolverProgramCache.set(id.primitive, programValue.value);
454
724
  }
455
- programValue.value = await this._maybeOpen(programValue.value);
456
- this._resolverProgramCache.set(id.primitive, programValue.value);
457
725
  }
726
+ return super.afterOpen();
458
727
  }
459
- return super.afterOpen();
460
- }
461
- async getPending(cursorId) {
462
- const queue = this._resultQueue.get(cursorId);
463
- if (queue) {
464
- return queue.queue.length + queue.keptInIndex;
465
- }
466
- return this._resumableIterators.getPending(cursorId);
467
- }
468
- get hasPending() {
469
- if (this._resultQueue.size > 0) {
470
- return true;
471
- }
472
- return false;
473
- }
474
- async close(from) {
475
- const closed = await super.close(from);
476
- if (closed) {
477
- this._query.events.removeEventListener("join", this._joinListener);
478
- this.clearAllResultQueues();
479
- await this.index.stop?.();
728
+ async getPending(cursorId) {
729
+ const queue = this._resultQueue.get(cursorId);
730
+ if (queue) {
731
+ return queue.queue.length + queue.keptInIndex;
732
+ }
733
+ return this._resumableIterators.getPending(cursorId);
480
734
  }
481
- return closed;
482
- }
483
- async drop(from) {
484
- const dropped = await super.drop(from);
485
- if (dropped) {
486
- this.clearAllResultQueues();
487
- await this.index.drop?.();
488
- await this.index.stop?.();
735
+ get hasPending() {
736
+ if (this._resultQueue.size > 0) {
737
+ return true;
738
+ }
739
+ return false;
489
740
  }
490
- return dropped;
491
- }
492
- async get(key, options) {
493
- let deferred;
494
- // Normalize the id key early so listeners can use it
495
- let idKey = key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key);
496
- if (options?.waitFor) {
497
- // add change listener before query because we might get a concurrent change that matches the query,
498
- // that will not be included in the query result
499
- deferred = pDefer();
500
- const listener = (evt) => {
501
- for (const added of evt.detail.added) {
502
- const id = indexerTypes.toId(this.indexByResolver(added.__indexed)).primitive;
503
- if (id === idKey.primitive) {
504
- deferred.resolve(added);
741
+ async close(from) {
742
+ const closed = await super.close(from);
743
+ if (closed) {
744
+ if (this._joinListener) {
745
+ this._query.events.removeEventListener("join", this._joinListener);
746
+ }
747
+ if (this.handleDocumentChange) {
748
+ this.documentEvents.removeEventListener("change", this.handleDocumentChange);
749
+ }
750
+ this.clearAllResultQueues();
751
+ await this.index.stop?.();
752
+ }
753
+ return closed;
754
+ }
755
+ async drop(from) {
756
+ const dropped = await super.drop(from);
757
+ if (dropped) {
758
+ this.documentEvents.removeEventListener("change", this.handleDocumentChange);
759
+ this.clearAllResultQueues();
760
+ await this.index.drop?.();
761
+ await this.index.stop?.();
762
+ }
763
+ return dropped;
764
+ }
765
+ async get(key, options) {
766
+ let deferred;
767
+ // Normalize the id key early so listeners can use it
768
+ let idKey = key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key);
769
+ if (options?.waitFor) {
770
+ // add change listener before query because we might get a concurrent change that matches the query,
771
+ // that will not be included in the query result
772
+ deferred = pDefer();
773
+ const listener = (evt) => {
774
+ for (const added of evt.detail.added) {
775
+ const id = indexerTypes.toId(this.indexByResolver(added.__indexed)).primitive;
776
+ if (id === idKey.primitive) {
777
+ deferred.resolve(added);
778
+ }
779
+ }
780
+ };
781
+ let cleanedUp = false;
782
+ let cleanup = () => {
783
+ if (cleanedUp)
784
+ return;
785
+ cleanedUp = true;
786
+ this.documentEvents.removeEventListener("change", listener);
787
+ clearTimeout(timeout);
788
+ this.events.removeEventListener("close", resolveUndefined);
789
+ joinListener?.();
790
+ };
791
+ let resolveUndefined = () => {
792
+ deferred.resolve(undefined);
793
+ };
794
+ let timeout = setTimeout(resolveUndefined, options.waitFor);
795
+ this.events.addEventListener("close", resolveUndefined);
796
+ this.documentEvents.addEventListener("change", listener);
797
+ deferred.promise.then(cleanup);
798
+ // Prepare remote options without mutating caller options
799
+ const baseRemote = options?.remote === false
800
+ ? undefined
801
+ : typeof options?.remote === "object"
802
+ ? { ...options.remote }
803
+ : {};
804
+ if (baseRemote) {
805
+ const waitPolicy = baseRemote.wait;
806
+ if (!waitPolicy ||
807
+ (typeof waitPolicy === "object" &&
808
+ (waitPolicy.timeout || 0) < options.waitFor)) {
809
+ baseRemote.wait = {
810
+ ...(typeof waitPolicy === "object" ? waitPolicy : {}),
811
+ timeout: options.waitFor,
812
+ };
505
813
  }
506
814
  }
507
- };
508
- let cleanedUp = false;
509
- let cleanup = () => {
510
- if (cleanedUp)
511
- return;
512
- cleanedUp = true;
513
- this.documentEvents.removeEventListener("change", listener);
514
- clearTimeout(timeout);
515
- this.events.removeEventListener("close", resolveUndefined);
516
- joinListener?.();
517
- };
518
- let resolveUndefined = () => {
519
- deferred.resolve(undefined);
520
- };
521
- let timeout = setTimeout(resolveUndefined, options.waitFor);
522
- this.events.addEventListener("close", resolveUndefined);
523
- this.documentEvents.addEventListener("change", listener);
524
- deferred.promise.then(cleanup);
525
- // Prepare remote options without mutating caller options
526
- const baseRemote = options?.remote === false
527
- ? undefined
528
- : typeof options?.remote === "object"
529
- ? { ...options.remote }
530
- : {};
531
- if (baseRemote) {
532
- const waitPolicy = baseRemote.wait;
533
- if (!waitPolicy ||
534
- (typeof waitPolicy === "object" &&
535
- (waitPolicy.timeout || 0) < options.waitFor)) {
536
- baseRemote.wait = {
537
- ...(typeof waitPolicy === "object" ? waitPolicy : {}),
538
- timeout: options.waitFor,
539
- };
815
+ // Re-query on peer joins (like iterate), scoped to the joining peer
816
+ let joinListener;
817
+ if (baseRemote) {
818
+ joinListener = this.createReplicatorJoinListener({
819
+ eager: baseRemote.reach?.eager,
820
+ onPeer: async (pk) => {
821
+ if (cleanedUp)
822
+ return;
823
+ const hash = pk.hashcode();
824
+ const requeryOptions = {
825
+ ...options,
826
+ remote: {
827
+ ...(baseRemote || {}),
828
+ from: [hash],
829
+ },
830
+ };
831
+ const re = await this.getDetailed(idKey, requeryOptions);
832
+ const first = re?.[0]?.results[0];
833
+ if (first) {
834
+ deferred.resolve(first.value);
835
+ }
836
+ },
837
+ });
540
838
  }
541
839
  }
542
- // Re-query on peer joins (like iterate), scoped to the joining peer
543
- let joinListener;
544
- if (baseRemote) {
545
- joinListener = this.attachJoinListener({
546
- eager: baseRemote.reach?.eager,
547
- onPeer: async (pk) => {
548
- if (cleanedUp)
549
- return;
550
- const hash = pk.hashcode();
551
- const requeryOptions = {
552
- ...options,
553
- remote: {
554
- ...(baseRemote || {}),
555
- from: [hash],
556
- },
557
- };
558
- const re = await this.getDetailed(idKey, requeryOptions);
559
- const first = re?.[0]?.results[0];
560
- if (first) {
561
- deferred.resolve(first.value);
562
- }
563
- },
564
- });
840
+ const result = (await this.getDetailed(idKey, options))?.[0]?.results[0];
841
+ // if no results, and we have remote joining options, we wait for the timout and if there are joining peers we re-query
842
+ if (!result) {
843
+ return deferred?.promise;
565
844
  }
566
- }
567
- const result = (await this.getDetailed(idKey, options))?.[0]?.results[0];
568
- // if no results, and we have remote joining options, we wait for the timout and if there are joining peers we re-query
569
- if (!result) {
570
- return deferred?.promise;
571
- }
572
- else if (deferred) {
573
- deferred.resolve(undefined);
574
- }
575
- return result?.value;
576
- }
577
- async getFromGid(gid) {
578
- const iterator = this.index.iterate({ query: { gid } });
579
- const one = await iterator.next(1);
580
- await iterator.close();
581
- return one[0];
582
- }
583
- async getFromHash(hash) {
584
- const iterator = this.index.iterate({ query: { hash } });
585
- const one = await iterator.next(1);
586
- await iterator.close();
587
- return one[0];
588
- }
589
- async put(value, id, entry, existing) {
590
- const existingDefined = existing === undefined ? await this.index.get(id) : existing;
591
- const context = new types.Context({
592
- created: existingDefined?.value.__context.created ||
593
- entry.meta.clock.timestamp.wallTime,
594
- modified: entry.meta.clock.timestamp.wallTime,
595
- head: entry.hash,
596
- gid: entry.meta.gid,
597
- size: entry.payload.byteLength,
598
- });
599
- return this.putWithContext(value, id, context);
600
- }
601
- async putWithContext(value, id, context) {
602
- const idString = id.primitive;
603
- if (this.isProgramValued /*
604
- TODO should we skip caching program value if they are not openend through this db?
605
- &&
606
- (value as Program).closed === false &&
607
- (value as Program).parents.includes(this._log) */) {
608
- // TODO make last condition more efficient if there are many docs
609
- this._resolverProgramCache.set(idString, value);
610
- }
611
- else {
612
- this._resolverCache?.add(idString, value);
613
- }
614
- const valueToIndex = await this.transformer(value, context);
615
- const wrappedValueToIndex = new this.wrappedIndexedType(valueToIndex, context);
616
- coerceWithIndexed(value, valueToIndex);
617
- coerceWithContext(value, context);
618
- await this.index.put(wrappedValueToIndex);
619
- return { context, indexable: valueToIndex };
620
- }
621
- del(key) {
622
- if (this.isProgramValued) {
623
- this._resolverProgramCache.delete(key.primitive);
624
- }
625
- else {
626
- this._resolverCache?.del(key.primitive);
627
- }
628
- return this.index.del({
629
- query: [indexerTypes.getMatcher(this.indexBy, key.key)],
630
- });
631
- }
632
- async getDetailed(key, options) {
633
- let coercedOptions = options;
634
- if (options?.remote && typeof options.remote !== "boolean") {
635
- coercedOptions = {
636
- ...options,
637
- remote: {
638
- ...options.remote,
639
- strategy: options.remote?.strategy ?? "fallback",
640
- },
641
- };
642
- }
643
- else if (options?.remote === undefined) {
644
- coercedOptions = {
645
- ...options,
646
- remote: {
647
- strategy: "fallback",
648
- },
649
- };
650
- }
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
- };
658
- const resolve = coercedOptions?.resolve || coercedOptions?.resolve == null;
659
- let requestClazz = resolve
660
- ? types.SearchRequest
661
- : types.SearchRequestIndexed;
662
- if (key instanceof Uint8Array) {
663
- const request = new requestClazz({
664
- query: [
665
- new indexerTypes.ByteMatchQuery({ key: this.indexBy, value: key }),
666
- ],
845
+ else if (deferred) {
846
+ deferred.resolve(undefined);
847
+ }
848
+ return result?.value;
849
+ }
850
+ async getFromGid(gid) {
851
+ const iterator = this.index.iterate({ query: { gid } });
852
+ const one = await iterator.next(1);
853
+ await iterator.close();
854
+ return one[0];
855
+ }
856
+ async getFromHash(hash) {
857
+ const iterator = this.index.iterate({ query: { hash } });
858
+ const one = await iterator.next(1);
859
+ await iterator.close();
860
+ return one[0];
861
+ }
862
+ async put(value, id, entry, existing) {
863
+ const existingDefined = existing === undefined ? await this.index.get(id) : existing;
864
+ const context = new types.Context({
865
+ created: existingDefined?.value.__context.created ||
866
+ entry.meta.clock.timestamp.wallTime,
867
+ modified: entry.meta.clock.timestamp.wallTime,
868
+ head: entry.hash,
869
+ gid: entry.meta.gid,
870
+ size: entry.payload.byteLength,
667
871
  });
668
- results = await runAndClose(request);
872
+ return this.putWithContext(value, id, context);
873
+ }
874
+ async putWithContext(value, id, context) {
875
+ const idString = id.primitive;
876
+ if (this.isProgramValued /*
877
+ TODO should we skip caching program value if they are not openend through this db?
878
+ &&
879
+ (value as Program).closed === false &&
880
+ (value as Program).parents.includes(this._log) */) {
881
+ // TODO make last condition more efficient if there are many docs
882
+ this._resolverProgramCache.set(idString, value);
883
+ indexCacheLogger("cache:set:program", { id: idString });
884
+ }
885
+ else {
886
+ if (this._resolverCache) {
887
+ this._resolverCache.add(idString, value);
888
+ indexCacheLogger("cache:set:value", { id: idString });
889
+ }
890
+ }
891
+ const valueToIndex = await this.transformer(value, context);
892
+ const wrappedValueToIndex = new this.wrappedIndexedType(valueToIndex, context);
893
+ coerceWithIndexed(value, valueToIndex);
894
+ coerceWithContext(value, context);
895
+ await this.index.put(wrappedValueToIndex);
896
+ return { context, indexable: valueToIndex };
669
897
  }
670
- else {
671
- const indexableKey = indexerTypes.toIdeable(key);
672
- if (typeof indexableKey === "number" ||
673
- typeof indexableKey === "bigint") {
674
- const request = new requestClazz({
675
- query: [
676
- new indexerTypes.IntegerCompare({
677
- key: this.indexBy,
678
- compare: indexerTypes.Compare.Equal,
679
- value: indexableKey,
680
- }),
681
- ],
682
- });
683
- results = await runAndClose(request);
898
+ del(key) {
899
+ if (this.isProgramValued) {
900
+ this._resolverProgramCache.delete(key.primitive);
901
+ indexCacheLogger("cache:del:program", { id: key.primitive });
684
902
  }
685
- else if (typeof indexableKey === "string") {
686
- const request = new requestClazz({
687
- query: [
688
- new indexerTypes.StringMatch({
689
- key: this.indexBy,
690
- value: indexableKey,
691
- }),
692
- ],
693
- });
694
- results = await runAndClose(request);
903
+ else {
904
+ if (this._resolverCache?.del(key.primitive)) {
905
+ indexCacheLogger("cache:del:value", { id: key.primitive });
906
+ }
695
907
  }
696
- else if (indexableKey instanceof Uint8Array) {
697
- const request = new requestClazz({
698
- query: [
699
- new indexerTypes.ByteMatchQuery({
700
- key: this.indexBy,
701
- value: indexableKey,
702
- }),
703
- ],
704
- });
705
- results = await runAndClose(request);
908
+ return this.index.del({
909
+ query: [indexerTypes.getMatcher(this.indexBy, key.key)],
910
+ });
911
+ }
912
+ async getDetailed(key, options) {
913
+ let coercedOptions = options;
914
+ if (options?.remote && typeof options.remote !== "boolean") {
915
+ coercedOptions = {
916
+ ...options,
917
+ remote: {
918
+ ...options.remote,
919
+ strategy: options.remote?.strategy ?? "fallback",
920
+ },
921
+ };
706
922
  }
707
- else if (indexableKey instanceof ArrayBuffer) {
923
+ else if (options?.remote === undefined) {
924
+ coercedOptions = {
925
+ ...options,
926
+ remote: {
927
+ strategy: "fallback",
928
+ },
929
+ };
930
+ }
931
+ let results;
932
+ const runAndClose = async (req) => {
933
+ const response = await this.queryCommence(req, coercedOptions);
934
+ this._resumableIterators.close({ idString: req.idString });
935
+ this.cancelIteratorKeepAlive(req.idString);
936
+ return response;
937
+ };
938
+ const resolve = coercedOptions?.resolve || coercedOptions?.resolve == null;
939
+ let requestClazz = resolve
940
+ ? types.SearchRequest
941
+ : types.SearchRequestIndexed;
942
+ if (key instanceof Uint8Array) {
708
943
  const request = new requestClazz({
709
944
  query: [
710
- new indexerTypes.ByteMatchQuery({
711
- key: this.indexBy,
712
- value: new Uint8Array(indexableKey),
713
- }),
945
+ new indexerTypes.ByteMatchQuery({ key: this.indexBy, value: key }),
714
946
  ],
715
947
  });
716
948
  results = await runAndClose(request);
717
949
  }
718
950
  else {
719
- throw new Error("Unsupported key type");
951
+ const indexableKey = indexerTypes.toIdeable(key);
952
+ if (typeof indexableKey === "number" ||
953
+ typeof indexableKey === "bigint") {
954
+ const request = new requestClazz({
955
+ query: [
956
+ new indexerTypes.IntegerCompare({
957
+ key: this.indexBy,
958
+ compare: indexerTypes.Compare.Equal,
959
+ value: indexableKey,
960
+ }),
961
+ ],
962
+ });
963
+ results = await runAndClose(request);
964
+ }
965
+ else if (typeof indexableKey === "string") {
966
+ const request = new requestClazz({
967
+ query: [
968
+ new indexerTypes.StringMatch({
969
+ key: this.indexBy,
970
+ value: indexableKey,
971
+ }),
972
+ ],
973
+ });
974
+ results = await runAndClose(request);
975
+ }
976
+ else if (indexableKey instanceof Uint8Array) {
977
+ const request = new requestClazz({
978
+ query: [
979
+ new indexerTypes.ByteMatchQuery({
980
+ key: this.indexBy,
981
+ value: indexableKey,
982
+ }),
983
+ ],
984
+ });
985
+ results = await runAndClose(request);
986
+ }
987
+ else if (indexableKey instanceof ArrayBuffer) {
988
+ const request = new requestClazz({
989
+ query: [
990
+ new indexerTypes.ByteMatchQuery({
991
+ key: this.indexBy,
992
+ value: new Uint8Array(indexableKey),
993
+ }),
994
+ ],
995
+ });
996
+ results = await runAndClose(request);
997
+ }
998
+ else {
999
+ throw new Error("Unsupported key type");
1000
+ }
720
1001
  }
721
- }
722
- // if we are to resolve the document we need to go through all results and replace the results with the resolved values
723
- const shouldResolve = resolve &&
724
- requestClazz === types.SearchRequestIndexed &&
725
- !this.indexedTypeIsDocumentType &&
726
- results;
727
- if (results) {
728
- for (const set of results) {
729
- let missingValues = false;
730
- for (let i = 0; i < set.results.length; i++) {
731
- let value = set.results[i];
732
- let resolved;
733
- if (shouldResolve) {
734
- resolved =
735
- value instanceof types.ResultIndexedValue
736
- ? (await this.resolveDocument({
737
- indexed: value.value,
738
- head: value.context.head,
739
- }))?.value
740
- : value.value;
741
- }
742
- else {
743
- resolved = value.value;
744
- }
745
- if (resolved) {
746
- let indexed = await this.resolveIndexed(set.results[i], set.results);
747
- let valueWithWindexed = coerceWithIndexed(resolved, indexed);
748
- set.results[i]._value = coerceWithContext(valueWithWindexed, set.results[i].context);
1002
+ // if we are to resolve the document we need to go through all results and replace the results with the resolved values
1003
+ const shouldResolve = resolve &&
1004
+ requestClazz === types.SearchRequestIndexed &&
1005
+ !this.indexedTypeIsDocumentType &&
1006
+ results;
1007
+ if (results) {
1008
+ for (const set of results) {
1009
+ let missingValues = false;
1010
+ for (let i = 0; i < set.results.length; i++) {
1011
+ let value = set.results[i];
1012
+ let resolved;
1013
+ if (shouldResolve) {
1014
+ resolved =
1015
+ value instanceof types.ResultIndexedValue
1016
+ ? (await this.resolveDocument({
1017
+ indexed: value.value,
1018
+ head: value.context.head,
1019
+ }))?.value
1020
+ : value.value;
1021
+ }
1022
+ else {
1023
+ resolved = value.value;
1024
+ }
1025
+ if (resolved) {
1026
+ let indexed = await this.resolveIndexed(set.results[i], set.results);
1027
+ let valueWithWindexed = coerceWithIndexed(resolved, indexed);
1028
+ set.results[i]._value = coerceWithContext(valueWithWindexed, set.results[i].context);
1029
+ }
1030
+ else {
1031
+ missingValues = true;
1032
+ }
749
1033
  }
750
- else {
751
- missingValues = true;
1034
+ if (missingValues) {
1035
+ set.results = set.results.filter((x) => !!x);
752
1036
  }
753
1037
  }
754
- if (missingValues) {
755
- set.results = set.results.filter((x) => !!x);
756
- }
757
1038
  }
1039
+ return results;
758
1040
  }
759
- return results;
760
- }
761
- getSize() {
762
- return this.index.getSize();
763
- }
764
- async resolveDocument(value) {
765
- const id = value.id ??
766
- indexerTypes.toId(this.indexByResolver(value.indexed)).primitive;
767
- const cached = this._resolverCache?.get(id) || this._resolverProgramCache?.get(id);
768
- if (cached != null) {
769
- return { value: cached };
770
- }
771
- if (this.indexedTypeIsDocumentType) {
772
- // cast value to T, i.e. convert the class but keep all properties except the __context
773
- const obj = Object.assign(Object.create(this.documentType.prototype), value.indexed);
774
- delete obj.__context;
775
- return { value: obj };
776
- }
777
- const head = await this._log.log.get(value.head);
778
- if (!head) {
779
- return undefined; // we could end up here if we recently pruned the document and other peers never persisted the entry
780
- // TODO update changes in index before removing entries from log entry storage
781
- }
782
- const payloadValue = await head.getPayloadValue();
783
- if (isPutOperation(payloadValue)) {
784
- return {
785
- value: this.valueEncoding.decoder(payloadValue.data),
786
- /* size: payloadValue.data.byteLength */
787
- };
788
- }
789
- throw new Error("Unexpected value type when getting document: " +
790
- payloadValue?.constructor?.name || typeof payloadValue);
791
- }
792
- async processQuery(query, from, isLocal, options) {
793
- // We do special case for querying the id as we can do it faster than iterating
794
- let prevQueued = isLocal
795
- ? undefined
796
- : this._resultQueue.get(query.idString);
797
- if (prevQueued && !from.equals(prevQueued.from)) {
798
- throw new Error("Different from in queued results");
799
- }
800
- let indexedResult = undefined;
801
- let fromQuery;
802
- let keepAliveRequest;
803
- if (query instanceof types.SearchRequest ||
804
- query instanceof types.SearchRequestIndexed ||
805
- query instanceof types.IterationRequest) {
806
- fromQuery = 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
- });
815
- }
816
- else if (query instanceof types.CollectNextRequest) {
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
- }
825
- indexedResult =
826
- prevQueued?.keptInIndex === 0
827
- ? []
828
- : await this._resumableIterators.next(query, {
829
- keepAlive: keepAliveRequest !== undefined,
830
- });
831
- }
832
- else {
833
- throw new Error("Unsupported");
1041
+ getSize() {
1042
+ return this.index.getSize();
834
1043
  }
835
- if (!isLocal && keepAliveRequest) {
836
- this.scheduleIteratorKeepAlive(query.idString, keepAliveRequest.keepAliveTtl);
837
- }
838
- let resultSize = 0;
839
- let toIterate = prevQueued
840
- ? [...prevQueued.queue, ...indexedResult]
841
- : indexedResult;
842
- if (prevQueued) {
843
- this._resultQueue.delete(query.idString);
844
- clearTimeout(prevQueued.timeout);
845
- prevQueued = undefined;
846
- }
847
- let kept = (await this._resumableIterators.getPending(query.idString)) ?? 0;
848
- if (!isLocal) {
849
- prevQueued = {
850
- from,
851
- queue: [],
852
- timeout: setTimeout(() => {
853
- this._resultQueue.delete(query.idString);
854
- }, 6e4),
855
- keptInIndex: kept,
856
- fromQuery: (fromQuery || query),
857
- };
858
- this._resultQueue.set(query.idString, prevQueued);
1044
+ async resolveDocument(value) {
1045
+ const id = value.id ??
1046
+ indexerTypes.toId(this.indexByResolver(value.indexed)).primitive;
1047
+ const cached = this._resolverCache?.get(id) || this._resolverProgramCache?.get(id);
1048
+ if (cached != null) {
1049
+ return { value: cached };
1050
+ }
1051
+ if (this.indexedTypeIsDocumentType) {
1052
+ // cast value to T, i.e. convert the class but keep all properties except the __context
1053
+ const obj = Object.assign(Object.create(this.documentType.prototype), value.indexed);
1054
+ delete obj.__context;
1055
+ return { value: obj };
1056
+ }
1057
+ const head = await this._log.log.get(value.head);
1058
+ if (!head) {
1059
+ return undefined; // we could end up here if we recently pruned the document and other peers never persisted the entry
1060
+ // TODO update changes in index before removing entries from log entry storage
1061
+ }
1062
+ const payloadValue = await head.getPayloadValue();
1063
+ if (isPutOperation(payloadValue)) {
1064
+ return {
1065
+ value: this.valueEncoding.decoder(payloadValue.data),
1066
+ /* size: payloadValue.data.byteLength */
1067
+ };
1068
+ }
1069
+ throw new Error("Unexpected value type when getting document: " +
1070
+ payloadValue?.constructor?.name || typeof payloadValue);
859
1071
  }
860
- const filteredResults = [];
861
- const resolveDocumentsFlag = resolvesDocuments(fromQuery);
862
- const replicateIndexFlag = replicatesIndex(fromQuery);
863
- for (const result of toIterate) {
864
- if (!isLocal) {
865
- resultSize += result.value.__context.size;
866
- if (resultSize > MAX_BATCH_SIZE) {
867
- prevQueued.queue.push(result);
868
- continue;
1072
+ async processQuery(query, from, isLocal, options) {
1073
+ // We do special case for querying the id as we can do it faster than iterating
1074
+ let prevQueued = isLocal
1075
+ ? undefined
1076
+ : this._resultQueue.get(query.idString);
1077
+ if (prevQueued && !from.equals(prevQueued.from)) {
1078
+ throw new Error("Different from in queued results");
1079
+ }
1080
+ let indexedResult = undefined;
1081
+ let fromQuery;
1082
+ let keepAliveRequest;
1083
+ if (query instanceof types.SearchRequest ||
1084
+ query instanceof types.SearchRequestIndexed ||
1085
+ query instanceof types.IterationRequest) {
1086
+ fromQuery = query;
1087
+ if (!isLocal &&
1088
+ query instanceof types.IterationRequest &&
1089
+ query.keepAliveTtl != null) {
1090
+ keepAliveRequest = query;
869
1091
  }
1092
+ indexedResult = await this._resumableIterators.iterateAndFetch(query, {
1093
+ keepAlive: keepAliveRequest !== undefined,
1094
+ });
870
1095
  }
871
- const indexedUnwrapped = Object.assign(Object.create(this.indexedType.prototype), result.value);
872
- if (options?.canRead &&
873
- !(await options.canRead(indexedUnwrapped, from))) {
874
- continue;
1096
+ else if (query instanceof types.CollectNextRequest) {
1097
+ const cachedRequest = prevQueued?.fromQuery ||
1098
+ this._resumableIterators.queues.get(query.idString)?.request;
1099
+ fromQuery = cachedRequest;
1100
+ if (!isLocal &&
1101
+ cachedRequest instanceof types.IterationRequest &&
1102
+ cachedRequest.keepAliveTtl != null) {
1103
+ keepAliveRequest = cachedRequest;
1104
+ }
1105
+ const hasResumable = this._resumableIterators.has(query.idString);
1106
+ indexedResult = hasResumable
1107
+ ? await this._resumableIterators.next(query, {
1108
+ keepAlive: keepAliveRequest !== undefined,
1109
+ })
1110
+ : [];
875
1111
  }
876
- if (resolveDocumentsFlag) {
877
- const value = await this.resolveDocument({
878
- indexed: result.value,
879
- head: result.value.__context.head,
880
- });
881
- if (!value) {
1112
+ else {
1113
+ throw new Error("Unsupported");
1114
+ }
1115
+ if (!isLocal && keepAliveRequest) {
1116
+ this.scheduleIteratorKeepAlive(query.idString, keepAliveRequest.keepAliveTtl);
1117
+ }
1118
+ let resultSize = 0;
1119
+ let toIterate = prevQueued
1120
+ ? [...prevQueued.queue, ...indexedResult]
1121
+ : indexedResult;
1122
+ if (prevQueued) {
1123
+ this._resultQueue.delete(query.idString);
1124
+ clearTimeout(prevQueued.timeout);
1125
+ prevQueued = undefined;
1126
+ }
1127
+ let kept = (await this._resumableIterators.getPending(query.idString)) ?? 0;
1128
+ if (!isLocal) {
1129
+ const resolveFlag = resolvesDocuments((fromQuery || query));
1130
+ prevQueued = {
1131
+ from,
1132
+ queue: [],
1133
+ timeout: setTimeout(() => {
1134
+ this._resultQueue.delete(query.idString);
1135
+ }, 6e4),
1136
+ keptInIndex: kept,
1137
+ fromQuery: (fromQuery || query),
1138
+ resolveResults: resolveFlag,
1139
+ };
1140
+ if (fromQuery instanceof types.IterationRequest &&
1141
+ fromQuery.pushUpdates) {
1142
+ prevQueued.pushMode = fromQuery.pushUpdates;
1143
+ }
1144
+ this._resultQueue.set(query.idString, prevQueued);
1145
+ }
1146
+ const filteredResults = [];
1147
+ const resolveDocumentsFlag = resolvesDocuments(fromQuery);
1148
+ const replicateIndexFlag = replicatesIndex(fromQuery);
1149
+ for (const result of toIterate) {
1150
+ if (!isLocal) {
1151
+ resultSize += result.value.__context.size;
1152
+ if (resultSize > MAX_BATCH_SIZE) {
1153
+ prevQueued.queue.push(result);
1154
+ continue;
1155
+ }
1156
+ }
1157
+ const indexedUnwrapped = Object.assign(Object.create(this.indexedType.prototype), result.value);
1158
+ if (options?.canRead &&
1159
+ !(await options.canRead(indexedUnwrapped, from))) {
882
1160
  continue;
883
1161
  }
884
- filteredResults.push(new types.ResultValue({
885
- context: result.value.__context,
886
- value: value.value,
887
- source: serialize(value.value),
888
- indexed: indexedUnwrapped,
889
- }));
1162
+ if (resolveDocumentsFlag) {
1163
+ const value = await this.resolveDocument({
1164
+ indexed: result.value,
1165
+ head: result.value.__context.head,
1166
+ });
1167
+ if (!value) {
1168
+ continue;
1169
+ }
1170
+ filteredResults.push(new types.ResultValue({
1171
+ context: result.value.__context,
1172
+ value: value.value,
1173
+ source: serialize(value.value),
1174
+ indexed: indexedUnwrapped,
1175
+ }));
1176
+ }
1177
+ else {
1178
+ const context = result.value.__context;
1179
+ const head = await this._log.log.get(context.head);
1180
+ // assume remote peer will start to replicate (TODO is this ideal?)
1181
+ if (replicateIndexFlag) {
1182
+ this._log.addPeersToGidPeerHistory(context.gid, [from.hashcode()]);
1183
+ }
1184
+ filteredResults.push(new types.ResultIndexedValue({
1185
+ context,
1186
+ source: serialize(indexedUnwrapped),
1187
+ indexed: indexedUnwrapped,
1188
+ entries: head ? [head] : [],
1189
+ }));
1190
+ }
890
1191
  }
891
- else {
892
- const context = result.value.__context;
893
- const head = await this._log.log.get(context.head);
894
- // assume remote peer will start to replicate (TODO is this ideal?)
895
- if (replicateIndexFlag) {
896
- this._log.addPeersToGidPeerHistory(context.gid, [from.hashcode()]);
897
- }
898
- filteredResults.push(new types.ResultIndexedValue({
899
- context,
900
- source: serialize(indexedUnwrapped),
901
- indexed: indexedUnwrapped,
902
- entries: head ? [head] : [],
903
- }));
1192
+ const results = new types.Results({
1193
+ results: filteredResults,
1194
+ kept: BigInt(kept + (prevQueued?.queue.length || 0)),
1195
+ });
1196
+ const keepAliveActive = keepAliveRequest !== undefined;
1197
+ const pushActive = fromQuery instanceof types.IterationRequest &&
1198
+ Boolean(fromQuery.pushUpdates);
1199
+ if (!isLocal && results.kept === 0n && !keepAliveActive && !pushActive) {
1200
+ this.clearResultsQueue(query);
904
1201
  }
1202
+ return results;
905
1203
  }
906
- const results = new types.Results({
907
- results: filteredResults,
908
- kept: BigInt(kept + (prevQueued?.queue.length || 0)),
909
- });
910
- if (!isLocal && results.kept === 0n) {
911
- this.clearResultsQueue(query);
912
- }
913
- return results;
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
- }
950
- clearResultsQueue(query) {
951
- const queue = this._resultQueue.get(query.idString);
952
- if (queue) {
953
- clearTimeout(queue.timeout);
954
- this._resultQueue.delete(query.idString);
955
- }
956
- }
957
- get countIteratorsInProgress() {
958
- return this._resumableIterators.queues.size;
959
- }
960
- clearAllResultQueues() {
961
- for (const [key, queue] of this._resultQueue) {
962
- clearTimeout(queue.timeout);
963
- this._resultQueue.delete(key);
964
- this.cancelIteratorKeepAlive(key);
965
- this._resumableIterators.close({ idString: key });
966
- }
967
- }
968
- async waitForCoverReady(params) {
969
- const { domain, eager, settle, timeout, signal, onTimeout = "proceed", } = params;
970
- if (settle !== "any") {
971
- return;
972
- }
973
- const properties = domain && "range" in domain
974
- ? { range: domain.range }
975
- : { args: domain?.args };
976
- const selfHash = this.node.identity.publicKey.hashcode();
977
- const ready = async () => {
978
- const cover = await this._log.getCover(properties, { eager });
979
- return cover.some((hash) => hash !== selfHash);
980
- };
981
- if (await ready()) {
982
- return;
1204
+ scheduleIteratorKeepAlive(idString, ttl) {
1205
+ if (ttl == null) {
1206
+ return;
1207
+ }
1208
+ const ttlNumber = Number(ttl);
1209
+ if (!Number.isFinite(ttlNumber) || ttlNumber <= 0) {
1210
+ return;
1211
+ }
1212
+ // Cap max timeout to 1 day (TODO make configurable?)
1213
+ const delay = Math.max(1, Math.min(ttlNumber, 86400000));
1214
+ this.cancelIteratorKeepAlive(idString);
1215
+ const timers = this.iteratorKeepAliveTimers ??
1216
+ (this.iteratorKeepAliveTimers = new Map());
1217
+ const timer = setTimeout(() => {
1218
+ timers.delete(idString);
1219
+ const queued = this._resultQueue.get(idString);
1220
+ if (queued) {
1221
+ clearTimeout(queued.timeout);
1222
+ this._resultQueue.delete(idString);
1223
+ }
1224
+ this._resumableIterators.close({ idString });
1225
+ }, delay);
1226
+ timers.set(idString, timer);
983
1227
  }
984
- const deferred = pDefer();
985
- let settled = false;
986
- let cleaned = false;
987
- let timer;
988
- let checking = false;
989
- const cleanup = () => {
990
- if (cleaned) {
1228
+ cancelIteratorKeepAlive(idString) {
1229
+ const timers = this.iteratorKeepAliveTimers;
1230
+ if (!timers) {
991
1231
  return;
992
1232
  }
993
- cleaned = true;
994
- this._log.events.removeEventListener("replicator:join", onEvent);
995
- this._log.events.removeEventListener("replication:change", onEvent);
996
- this._log.events.removeEventListener("replicator:mature", onEvent);
997
- signal?.removeEventListener("abort", onAbort);
998
- if (timer != null) {
1233
+ const timer = timers.get(idString);
1234
+ if (timer) {
999
1235
  clearTimeout(timer);
1000
- timer = undefined;
1236
+ timers.delete(idString);
1001
1237
  }
1002
- };
1003
- const resolve = () => {
1004
- if (settled) {
1005
- return;
1238
+ }
1239
+ clearResultsQueue(query) {
1240
+ const queue = this._resultQueue.get(query.idString);
1241
+ if (queue) {
1242
+ clearTimeout(queue.timeout);
1243
+ this._resultQueue.delete(query.idString);
1006
1244
  }
1007
- settled = true;
1008
- cleanup();
1009
- deferred.resolve();
1010
- };
1011
- const reject = (error) => {
1012
- if (settled) {
1245
+ }
1246
+ get countIteratorsInProgress() {
1247
+ return this._resumableIterators.queues.size;
1248
+ }
1249
+ clearAllResultQueues() {
1250
+ for (const [key, queue] of this._resultQueue) {
1251
+ clearTimeout(queue.timeout);
1252
+ this._resultQueue.delete(key);
1253
+ this.cancelIteratorKeepAlive(key);
1254
+ this._resumableIterators.close({ idString: key });
1255
+ }
1256
+ }
1257
+ async waitForCoverReady(params) {
1258
+ const { domain, eager, settle, timeout, signal, onTimeout = "proceed", } = params;
1259
+ if (settle !== "any") {
1013
1260
  return;
1014
1261
  }
1015
- settled = true;
1016
- cleanup();
1017
- deferred.reject(error);
1018
- };
1019
- const onAbort = () => reject(new AbortError());
1020
- const onEvent = async () => {
1021
- if (checking) {
1262
+ const properties = domain && "range" in domain
1263
+ ? { range: domain.range }
1264
+ : { args: domain?.args };
1265
+ const selfHash = this.node.identity.publicKey.hashcode();
1266
+ const ready = async () => {
1267
+ const cover = await this._log.getCover(properties, { eager });
1268
+ return cover.some((hash) => hash !== selfHash);
1269
+ };
1270
+ if (await ready()) {
1022
1271
  return;
1023
1272
  }
1024
- checking = true;
1025
- try {
1026
- if (await ready()) {
1027
- resolve();
1273
+ const deferred = pDefer();
1274
+ let settled = false;
1275
+ let cleaned = false;
1276
+ let timer;
1277
+ let checking = false;
1278
+ const cleanup = () => {
1279
+ if (cleaned) {
1280
+ return;
1281
+ }
1282
+ cleaned = true;
1283
+ this._log.events.removeEventListener("replicator:join", onEvent);
1284
+ this._log.events.removeEventListener("replication:change", onEvent);
1285
+ this._log.events.removeEventListener("replicator:mature", onEvent);
1286
+ signal?.removeEventListener("abort", onAbort);
1287
+ if (timer != null) {
1288
+ clearTimeout(timer);
1289
+ timer = undefined;
1028
1290
  }
1291
+ };
1292
+ const resolve = () => {
1293
+ if (settled) {
1294
+ return;
1295
+ }
1296
+ settled = true;
1297
+ cleanup();
1298
+ deferred.resolve();
1299
+ };
1300
+ const reject = (error) => {
1301
+ if (settled) {
1302
+ return;
1303
+ }
1304
+ settled = true;
1305
+ cleanup();
1306
+ deferred.reject(error);
1307
+ };
1308
+ const onAbort = () => reject(new AbortError());
1309
+ const onEvent = async () => {
1310
+ if (checking) {
1311
+ return;
1312
+ }
1313
+ checking = true;
1314
+ try {
1315
+ if (await ready()) {
1316
+ resolve();
1317
+ }
1318
+ }
1319
+ catch (error) {
1320
+ reject(error instanceof Error ? error : new Error(String(error)));
1321
+ }
1322
+ finally {
1323
+ checking = false;
1324
+ }
1325
+ };
1326
+ if (signal) {
1327
+ signal.addEventListener("abort", onAbort);
1029
1328
  }
1030
- catch (error) {
1031
- reject(error instanceof Error ? error : new Error(String(error)));
1329
+ if (timeout > 0) {
1330
+ timer = setTimeout(() => {
1331
+ if (onTimeout === "error") {
1332
+ reject(new TimeoutError("Timeout waiting for participating replicator"));
1333
+ }
1334
+ else {
1335
+ resolve();
1336
+ }
1337
+ }, timeout);
1338
+ }
1339
+ this._log.events.addEventListener("replicator:join", onEvent);
1340
+ this._log.events.addEventListener("replication:change", onEvent);
1341
+ this._log.events.addEventListener("replicator:mature", onEvent);
1342
+ try {
1343
+ await deferred.promise;
1032
1344
  }
1033
1345
  finally {
1034
- checking = false;
1346
+ cleanup();
1035
1347
  }
1036
- };
1037
- if (signal) {
1038
- signal.addEventListener("abort", onAbort);
1039
1348
  }
1040
- if (timeout > 0) {
1041
- timer = setTimeout(() => {
1042
- if (onTimeout === "error") {
1043
- reject(new TimeoutError("Timeout waiting for participating replicator"));
1349
+ // Utility: attach a join listener that waits until a peer is a replicator,
1350
+ // then invokes the provided callback. Returns a detach function.
1351
+ createReplicatorJoinListener(params) {
1352
+ const active = new Set();
1353
+ const listener = async (e) => {
1354
+ const pk = e.detail;
1355
+ const hash = pk.hashcode();
1356
+ if (hash === this.node.identity.publicKey.hashcode())
1357
+ return;
1358
+ if (params.signal?.aborted)
1359
+ return;
1360
+ if (active.has(hash))
1361
+ return;
1362
+ active.add(hash);
1363
+ try {
1364
+ const isReplicator = await this._log
1365
+ .waitForReplicator(pk, {
1366
+ signal: params.signal,
1367
+ eager: params.eager,
1368
+ })
1369
+ .then(() => true)
1370
+ .catch(() => false);
1371
+ if (!isReplicator || params.signal?.aborted)
1372
+ return;
1373
+ indexIteratorLogger.trace("peer joined as replicator", { peer: hash });
1374
+ await params.onPeer(pk);
1044
1375
  }
1045
- else {
1046
- resolve();
1376
+ finally {
1377
+ active.delete(hash);
1047
1378
  }
1048
- }, timeout);
1049
- }
1050
- this._log.events.addEventListener("replicator:join", onEvent);
1051
- this._log.events.addEventListener("replication:change", onEvent);
1052
- this._log.events.addEventListener("replicator:mature", onEvent);
1053
- try {
1054
- await deferred.promise;
1055
- }
1056
- finally {
1057
- cleanup();
1379
+ };
1380
+ this._query.events.addEventListener("join", listener);
1381
+ return () => this._query.events.removeEventListener("join", listener);
1058
1382
  }
1059
- }
1060
- // Utility: attach a join listener that waits until a peer is a replicator,
1061
- // then invokes the provided callback. Returns a detach function.
1062
- attachJoinListener(params) {
1063
- const active = new Set();
1064
- const listener = async (e) => {
1065
- const pk = e.detail;
1066
- const hash = pk.hashcode();
1067
- if (hash === this.node.identity.publicKey.hashcode())
1068
- return;
1069
- if (params.signal?.aborted)
1070
- return;
1071
- if (active.has(hash))
1383
+ processCloseIteratorRequest(query, publicKey) {
1384
+ indexIteratorLogger.trace("close request", {
1385
+ id: query.idString,
1386
+ from: publicKey.hashcode(),
1387
+ });
1388
+ const queueData = this._resultQueue.get(query.idString);
1389
+ if (queueData && !queueData.from.equals(publicKey)) {
1390
+ indexIteratorLogger.trace("Ignoring close iterator request from different peer");
1072
1391
  return;
1073
- active.add(hash);
1074
- try {
1075
- await this._log
1076
- .waitForReplicator(pk, {
1077
- signal: params.signal,
1078
- eager: params.eager,
1079
- })
1080
- .catch(() => undefined);
1081
- if (params.signal?.aborted)
1082
- return;
1083
- await params.onPeer(pk);
1084
1392
  }
1085
- finally {
1086
- active.delete(hash);
1087
- }
1088
- };
1089
- this._query.events.addEventListener("join", listener);
1090
- return () => this._query.events.removeEventListener("join", listener);
1091
- }
1092
- processCloseIteratorRequest(query, publicKey) {
1093
- const queueData = this._resultQueue.get(query.idString);
1094
- if (queueData && !queueData.from.equals(publicKey)) {
1095
- logger.info("Ignoring close iterator request from different peer");
1096
- return;
1097
- }
1098
- this.cancelIteratorKeepAlive(query.idString);
1099
- this.clearResultsQueue(query);
1100
- return this._resumableIterators.close(query);
1101
- }
1102
- /**
1103
- * Query and retrieve results with most details
1104
- * @param queryRequest
1105
- * @param options
1106
- * @returns
1107
- */
1108
- async queryCommence(queryRequest, options, fetchFirstForRemote) {
1109
- const local = typeof options?.local === "boolean" ? options?.local : true;
1110
- let remote = undefined;
1111
- if (typeof options?.remote === "boolean") {
1112
- if (options?.remote) {
1113
- remote = {};
1393
+ this.cancelIteratorKeepAlive(query.idString);
1394
+ this.clearResultsQueue(query);
1395
+ return this._resumableIterators.close(query);
1396
+ }
1397
+ /**
1398
+ * Query and retrieve results with most details
1399
+ * @param queryRequest
1400
+ * @param options
1401
+ * @returns
1402
+ */
1403
+ async queryCommence(queryRequest, options, fetchFirstForRemote) {
1404
+ const local = typeof options?.local === "boolean" ? options?.local : true;
1405
+ let remote = undefined;
1406
+ if (typeof options?.remote === "boolean") {
1407
+ if (options?.remote) {
1408
+ remote = {};
1409
+ }
1410
+ else {
1411
+ remote = undefined;
1412
+ }
1114
1413
  }
1115
1414
  else {
1116
- remote = undefined;
1117
- }
1118
- }
1119
- else {
1120
- remote = options?.remote || {};
1121
- }
1122
- if (remote && remote.priority == null) {
1123
- // give queries higher priority than other "normal" data activities
1124
- // 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
1125
- // this will lead to bad UX as you usually want to list/expore whats going on before doing any replication work
1126
- remote.priority = 1;
1127
- }
1128
- if (!local && !remote) {
1129
- throw new Error("Expecting either 'options.remote' or 'options.local' to be true");
1130
- }
1131
- const allResults = [];
1132
- if (local) {
1133
- const results = await this.processQuery(queryRequest, this.node.identity.publicKey, true);
1134
- if (results.results.length > 0) {
1135
- options?.onResponse &&
1136
- (await options.onResponse(results, this.node.identity.publicKey));
1137
- allResults.push(results);
1415
+ remote = options?.remote || {};
1416
+ }
1417
+ if (remote && remote.priority == null) {
1418
+ // give queries higher priority than other "normal" data activities
1419
+ // 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
1420
+ // this will lead to bad UX as you usually want to list/expore whats going on before doing any replication work
1421
+ remote.priority = 1;
1422
+ }
1423
+ if (!local && !remote) {
1424
+ throw new Error("Expecting either 'options.remote' or 'options.local' to be true");
1425
+ }
1426
+ const allResults = [];
1427
+ if (local) {
1428
+ const results = await this.processQuery(queryRequest, this.node.identity.publicKey, true);
1429
+ if (results.results.length > 0) {
1430
+ options?.onResponse &&
1431
+ (await options.onResponse(results, this.node.identity.publicKey));
1432
+ allResults.push(results);
1433
+ }
1138
1434
  }
1139
- }
1140
- let resolved = [];
1141
- if (remote && (remote.strategy !== "fallback" || allResults.length === 0)) {
1142
- if (queryRequest instanceof types.CloseIteratorRequest) {
1143
- // don't wait for responses
1144
- throw new Error("Unexpected");
1145
- }
1146
- const replicatorGroups = options?.remote?.from
1147
- ? options?.remote?.from
1148
- : await this._log.getCover(remote.domain ?? { args: undefined }, {
1149
- roleAge: remote.minAge,
1150
- eager: remote.reach?.eager,
1151
- 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
1152
- });
1153
- if (replicatorGroups) {
1154
- const responseHandler = async (results) => {
1155
- const resultInitialized = await introduceEntries(queryRequest, results, this.documentType, this.indexedType, this._sync, options);
1156
- for (const r of resultInitialized) {
1157
- resolved.push(r.response);
1158
- }
1159
- };
1160
- let extraPromises = undefined;
1161
- const groupHashes = replicatorGroups
1162
- .filter((hash) => {
1163
- if (hash === this.node.identity.publicKey.hashcode()) {
1164
- return false;
1165
- }
1166
- if (fetchFirstForRemote?.has(hash)) {
1167
- // we already fetched this one for remote, no need to do it again
1168
- return false;
1169
- }
1170
- fetchFirstForRemote?.add(hash);
1171
- const resultAlready = this._prefetch?.accumulator.consume(queryRequest, hash);
1172
- if (resultAlready) {
1173
- (extraPromises || (extraPromises = [])).push((async () => {
1174
- let from = await this.node.services.pubsub.getPublicKey(hash);
1175
- if (from) {
1176
- return responseHandler([
1177
- {
1178
- response: resultAlready.response.results,
1179
- from,
1180
- },
1181
- ]);
1182
- }
1183
- })());
1184
- return false;
1185
- }
1186
- return true;
1187
- })
1188
- .map((x) => [x]);
1189
- extraPromises && (await Promise.all(extraPromises));
1190
- let tearDown = undefined;
1191
- const search = this;
1192
- try {
1193
- groupHashes.length > 0 &&
1194
- (await queryAll(this._query, groupHashes, queryRequest, responseHandler, search._prefetch?.accumulator
1195
- ? {
1196
- ...remote,
1197
- responseInterceptor(fn) {
1198
- const listener = (evt) => {
1199
- const consumable = search._prefetch?.accumulator.consume(queryRequest, evt.detail.consumable.from.hashcode());
1200
- if (consumable) {
1201
- fn({
1202
- message: consumable.message,
1203
- response: consumable.response.results,
1204
- from: consumable.from,
1205
- });
1206
- }
1207
- };
1208
- for (const groups of groupHashes) {
1209
- for (const hash of groups) {
1210
- const consumable = search._prefetch?.accumulator.consume(queryRequest, hash);
1435
+ let resolved = [];
1436
+ if (remote && (remote.strategy !== "fallback" || allResults.length === 0)) {
1437
+ if (queryRequest instanceof types.CloseIteratorRequest) {
1438
+ // don't wait for responses
1439
+ throw new Error("Unexpected");
1440
+ }
1441
+ const replicatorGroups = options?.remote?.from
1442
+ ? options?.remote?.from
1443
+ : await this._log.getCover(remote.domain ?? { args: undefined }, {
1444
+ roleAge: remote.minAge,
1445
+ eager: remote.reach?.eager,
1446
+ 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
1447
+ });
1448
+ if (replicatorGroups) {
1449
+ const responseHandler = async (results) => {
1450
+ const resultInitialized = await introduceEntries(queryRequest, results, this.documentType, this.indexedType, this._sync, options);
1451
+ for (const r of resultInitialized) {
1452
+ resolved.push(r.response);
1453
+ }
1454
+ };
1455
+ let extraPromises = undefined;
1456
+ const groupHashes = replicatorGroups
1457
+ .filter((hash) => {
1458
+ if (hash === this.node.identity.publicKey.hashcode()) {
1459
+ return false;
1460
+ }
1461
+ if (fetchFirstForRemote?.has(hash)) {
1462
+ // we already fetched this one for remote, no need to do it again
1463
+ return false;
1464
+ }
1465
+ fetchFirstForRemote?.add(hash);
1466
+ const resultAlready = this._prefetch?.accumulator.consume(queryRequest, hash);
1467
+ if (resultAlready) {
1468
+ (extraPromises || (extraPromises = [])).push((async () => {
1469
+ let from = await this.node.services.pubsub.getPublicKey(hash);
1470
+ if (from) {
1471
+ return responseHandler([
1472
+ {
1473
+ response: resultAlready.response.results,
1474
+ from,
1475
+ },
1476
+ ]);
1477
+ }
1478
+ })());
1479
+ return false;
1480
+ }
1481
+ return true;
1482
+ })
1483
+ .map((x) => [x]);
1484
+ extraPromises && (await Promise.all(extraPromises));
1485
+ let tearDown = undefined;
1486
+ const search = this;
1487
+ try {
1488
+ groupHashes.length > 0 &&
1489
+ (await queryAll(this._query, groupHashes, queryRequest, responseHandler, search._prefetch?.accumulator
1490
+ ? {
1491
+ ...remote,
1492
+ responseInterceptor(fn) {
1493
+ const listener = (evt) => {
1494
+ const consumable = search._prefetch?.accumulator.consume(queryRequest, evt.detail.consumable.from.hashcode());
1211
1495
  if (consumable) {
1212
1496
  fn({
1213
1497
  message: consumable.message,
@@ -1215,514 +1499,543 @@ let DocumentIndex = class DocumentIndex extends Program {
1215
1499
  from: consumable.from,
1216
1500
  });
1217
1501
  }
1502
+ };
1503
+ for (const groups of groupHashes) {
1504
+ for (const hash of groups) {
1505
+ const consumable = search._prefetch?.accumulator.consume(queryRequest, hash);
1506
+ if (consumable) {
1507
+ fn({
1508
+ message: consumable.message,
1509
+ response: consumable.response.results,
1510
+ from: consumable.from,
1511
+ });
1512
+ }
1513
+ }
1218
1514
  }
1219
- }
1220
- search.prefetch?.accumulator.addEventListener("add", listener);
1221
- tearDown = () => {
1222
- search.prefetch?.accumulator.removeEventListener("add", listener);
1223
- };
1224
- },
1515
+ search.prefetch?.accumulator.addEventListener("add", listener);
1516
+ tearDown = () => {
1517
+ search.prefetch?.accumulator.removeEventListener("add", listener);
1518
+ };
1519
+ },
1520
+ }
1521
+ : remote));
1522
+ }
1523
+ catch (error) {
1524
+ if (error instanceof MissingResponsesError) {
1525
+ warn("Did not reciveve responses from all shard");
1526
+ if (remote?.throwOnMissing) {
1527
+ throw error;
1225
1528
  }
1226
- : remote));
1227
- }
1228
- catch (error) {
1229
- if (error instanceof MissingResponsesError) {
1230
- logger.warn("Did not reciveve responses from all shard");
1231
- if (remote?.throwOnMissing) {
1529
+ }
1530
+ else {
1232
1531
  throw error;
1233
1532
  }
1234
1533
  }
1235
- else {
1236
- throw error;
1534
+ finally {
1535
+ tearDown && tearDown();
1237
1536
  }
1238
1537
  }
1239
- finally {
1240
- tearDown && tearDown();
1241
- }
1242
- }
1243
- else {
1244
- // TODO send without direction out to the world? or just assume we can insert?
1245
- /* promises.push(
1246
- this._query
1247
- .request(queryRequest, remote)
1248
- .then((results) => introduceEntries(results, this.type, this._sync, options).then(x => x.map(y => y.response)))
1249
- ); */
1250
- /* throw new Error(
1251
- "Missing remote replicator info for performing distributed document query"
1252
- ); */
1253
- }
1254
- }
1255
- for (const r of resolved) {
1256
- if (r) {
1257
- if (r instanceof Array) {
1258
- allResults.push(...r);
1259
- }
1260
1538
  else {
1261
- allResults.push(r);
1539
+ // TODO send without direction out to the world? or just assume we can insert?
1540
+ /* promises.push(
1541
+ this._query
1542
+ .request(queryRequest, remote)
1543
+ .then((results) => introduceEntries(results, this.type, this._sync, options).then(x => x.map(y => y.response)))
1544
+ ); */
1545
+ /* throw new Error(
1546
+ "Missing remote replicator info for performing distributed document query"
1547
+ ); */
1262
1548
  }
1263
1549
  }
1264
- }
1265
- return allResults; // TODO types
1266
- }
1267
- /**
1268
- * Query and retrieve results
1269
- * @param queryRequest
1270
- * @param options
1271
- * @returns
1272
- */
1273
- async search(queryRequest, options) {
1274
- // Set fetch to search size, or max value (default to max u32 (4294967295))
1275
- const coercedRequest = coerceQuery(queryRequest, options, this.compatibility);
1276
- coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
1277
- // So that the iterator is pre-fetching the right amount of entries
1278
- const iterator = this.iterate(coercedRequest, options);
1279
- // So that this call will not do any remote requests
1280
- const allResults = [];
1281
- while (iterator.done() !== true &&
1282
- coercedRequest.fetch > allResults.length) {
1283
- // We might need to pull .next multiple time due to data message size limitations
1284
- for (const result of await iterator.next(coercedRequest.fetch - allResults.length)) {
1285
- allResults.push(result);
1286
- }
1287
- }
1288
- await iterator.close();
1289
- // Deduplicate and return values directly
1290
- return dedup(allResults, this.indexByResolver);
1291
- }
1292
- resolveIndexed(result, results) {
1293
- if (result instanceof types.ResultIndexedValue) {
1294
- return coerceWithContext(result.value, result.context);
1295
- }
1296
- let resolveIndexedDefault = async (result) => coerceWithContext(result.indexed ||
1297
- (await this.transformer(result.value, result.context)), result.context);
1298
- let resolveIndexed = this.includeIndexed
1299
- ? (result, results) => {
1300
- // look through the search results and see if we can find the indexed representation
1301
- for (const otherResult of results) {
1302
- if (otherResult instanceof types.ResultIndexedValue) {
1303
- if (otherResult.context.head === result.context.head) {
1304
- otherResult.init(this.indexedType);
1305
- return coerceWithContext(otherResult.value, otherResult.context);
1306
- }
1550
+ for (const r of resolved) {
1551
+ if (r) {
1552
+ if (r instanceof Array) {
1553
+ allResults.push(...r);
1554
+ }
1555
+ else {
1556
+ allResults.push(r);
1307
1557
  }
1308
1558
  }
1309
- return resolveIndexedDefault(result);
1310
1559
  }
1311
- : (result) => resolveIndexedDefault(result);
1312
- return resolveIndexed(result, results);
1313
- }
1314
- /**
1315
- * Query and retrieve documents in a iterator
1316
- * @param queryRequest
1317
- * @param optionsArg
1318
- * @returns
1319
- */
1320
- iterate(queryRequest, optionsArg) {
1321
- let options = optionsArg;
1322
- if (queryRequest instanceof types.SearchRequest &&
1323
- options?.resolve === false) {
1324
- throw new Error("Cannot use resolve=false with SearchRequest"); // TODO make this work
1325
- }
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;
1560
+ return allResults; // TODO types
1561
+ }
1562
+ /**
1563
+ * Query and retrieve results
1564
+ * @param queryRequest
1565
+ * @param options
1566
+ * @returns
1567
+ */
1568
+ async search(queryRequest, options) {
1569
+ // Set fetch to search size, or max value (default to max u32 (4294967295))
1570
+ const coercedRequest = coerceQuery(queryRequest, options, this.compatibility);
1571
+ coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
1572
+ // So that the iterator is pre-fetching the right amount of entries
1573
+ const iterator = this.iterate(coercedRequest, options);
1574
+ // So that this call will not do any remote requests
1575
+ const allResults = [];
1576
+ while (iterator.done() !== true &&
1577
+ coercedRequest.fetch > allResults.length) {
1578
+ // We might need to pull .next multiple time due to data message size limitations
1579
+ for (const result of await iterator.next(coercedRequest.fetch - allResults.length)) {
1580
+ allResults.push(result);
1339
1581
  }
1340
1582
  }
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
- }
1348
- let resolve = options?.resolve !== false;
1349
- if (!(queryRequestCoerced instanceof types.IterationRequest) &&
1350
- options?.remote &&
1351
- typeof options.remote !== "boolean" &&
1352
- (options.remote.replicate || pushUpdates) &&
1353
- options?.resolve !== false) {
1354
- if ((queryRequest instanceof types.SearchRequestIndexed === false &&
1355
- this.compatibility == null) ||
1356
- (this.compatibility != null && this.compatibility > 8)) {
1357
- queryRequestCoerced = new types.SearchRequestIndexed({
1358
- query: queryRequestCoerced.query,
1359
- fetch: queryRequestCoerced.fetch,
1360
- sort: queryRequestCoerced.sort,
1361
- });
1362
- resolve = true;
1363
- }
1364
- }
1365
- let replicate = options?.remote &&
1366
- typeof options.remote !== "boolean" &&
1367
- (options.remote.replicate || pushUpdates);
1368
- if (replicate &&
1369
- queryRequestCoerced instanceof types.SearchRequestIndexed) {
1370
- queryRequestCoerced.replicate = true;
1371
- }
1372
- let fetchPromise = undefined;
1373
- const peerBufferMap = new Map();
1374
- const visited = new Set();
1375
- let indexedPlaceholders;
1376
- const ensureIndexedPlaceholders = () => {
1377
- if (!indexedPlaceholders) {
1378
- indexedPlaceholders = new Map();
1379
- }
1380
- return indexedPlaceholders;
1381
- };
1382
- let done = false;
1383
- let drain = false; // if true, close on empty once (overrides manual)
1384
- let first = false;
1385
- // TODO handle join/leave while iterating
1386
- let controller = undefined;
1387
- const ensureController = () => {
1388
- if (!controller) {
1389
- return (controller = new AbortController());
1390
- }
1391
- return controller;
1392
- };
1393
- let totalFetchedCounter = 0;
1394
- let lastValueInOrder = undefined;
1395
- const peerBuffers = () => {
1396
- return [...peerBufferMap.values()].map((x) => x.buffer).flat();
1397
- };
1398
- let maybeSetDone = () => {
1399
- cleanup();
1400
- done = true;
1401
- };
1402
- let unsetDone = () => {
1403
- done = false;
1404
- };
1405
- let cleanup = () => {
1406
- this.clearResultsQueue(queryRequestCoerced);
1407
- };
1408
- let warmupPromise = undefined;
1409
- let discoveredTargetHashes;
1410
- if (typeof options?.remote === "object") {
1411
- let waitForTime = undefined;
1412
- if (options.remote.wait) {
1413
- let t0 = +new Date();
1414
- waitForTime =
1415
- typeof options.remote.wait === "boolean"
1416
- ? DEFAULT_TIMEOUT
1417
- : (options.remote.wait.timeout ?? DEFAULT_TIMEOUT);
1418
- let setDoneIfTimeout = false;
1419
- maybeSetDone = () => {
1420
- if (t0 + waitForTime < +new Date()) {
1421
- cleanup();
1422
- done = true;
1583
+ await iterator.close();
1584
+ // Deduplicate and return values directly
1585
+ return dedup(allResults, this.indexByResolver);
1586
+ }
1587
+ resolveIndexed(result, results) {
1588
+ if (result instanceof types.ResultIndexedValue) {
1589
+ return coerceWithContext(result.value, result.context);
1590
+ }
1591
+ let resolveIndexedDefault = async (result) => coerceWithContext(result.indexed ||
1592
+ (await this.transformer(result.value, result.context)), result.context);
1593
+ let resolveIndexed = this.includeIndexed
1594
+ ? (result, results) => {
1595
+ // look through the search results and see if we can find the indexed representation
1596
+ for (const otherResult of results) {
1597
+ if (otherResult instanceof types.ResultIndexedValue) {
1598
+ if (otherResult.context.head === result.context.head) {
1599
+ otherResult.init(this.indexedType);
1600
+ return coerceWithContext(otherResult.value, otherResult.context);
1601
+ }
1602
+ }
1423
1603
  }
1424
- else {
1425
- setDoneIfTimeout = true;
1604
+ return resolveIndexedDefault(result);
1605
+ }
1606
+ : (result) => resolveIndexedDefault(result);
1607
+ return resolveIndexed(result, results);
1608
+ }
1609
+ /**
1610
+ * Query and retrieve documents in a iterator
1611
+ * @param queryRequest
1612
+ * @param optionsArg
1613
+ * @returns
1614
+ */
1615
+ iterate(queryRequest, optionsArg) {
1616
+ let options = optionsArg;
1617
+ if (queryRequest instanceof types.SearchRequest &&
1618
+ options?.resolve === false) {
1619
+ throw new Error("Cannot use resolve=false with SearchRequest"); // TODO make this work
1620
+ }
1621
+ let queryRequestCoerced = coerceQuery(queryRequest ?? {}, options, this.compatibility);
1622
+ const self = this;
1623
+ function normalizeUpdatesOption(u) {
1624
+ const identityFilter = (evt) => evt;
1625
+ const buildMergePolicy = (merge, defaultEnabled) => {
1626
+ const effective = merge === undefined ? (defaultEnabled ? true : undefined) : merge;
1627
+ if (effective === undefined || effective === false) {
1628
+ return undefined;
1426
1629
  }
1427
- };
1428
- unsetDone = () => {
1429
- setDoneIfTimeout = false;
1430
- done = false;
1431
- };
1432
- let timeout = setTimeout(() => {
1433
- if (setDoneIfTimeout) {
1434
- cleanup();
1435
- done = true;
1630
+ if (effective === true) {
1631
+ return {
1632
+ merge: {
1633
+ filter: identityFilter,
1634
+ },
1635
+ };
1436
1636
  }
1437
- }, waitForTime);
1438
- cleanup = () => {
1439
- this.clearResultsQueue(queryRequestCoerced);
1440
- clearTimeout(timeout);
1637
+ return {
1638
+ merge: {
1639
+ filter: effective.filter ?? identityFilter,
1640
+ },
1641
+ };
1441
1642
  };
1442
- }
1443
- if (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, {
1447
- signal: ensureController().signal,
1448
- seek: "present",
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;
1643
+ if (u == null || u === false) {
1644
+ return {};
1645
+ }
1646
+ if (u === true) {
1647
+ return {
1648
+ mergePolicy: buildMergePolicy(true, true),
1649
+ push: undefined,
1650
+ };
1651
+ }
1652
+ if (typeof u === "string") {
1653
+ if (u === "remote") {
1654
+ self.ensurePrefetchAccumulator();
1655
+ return { push: types.PushUpdatesMode.STREAM };
1458
1656
  }
1459
- throw error;
1460
- });
1461
- const prior = warmupPromise ?? Promise.resolve();
1462
- warmupPromise = prior.then(() => discoverPromise);
1463
- options.remote.reach.eager = true; // include the results from the discovered peer even if it is not mature
1464
- }
1465
- const waitPolicy = typeof options.remote.wait === "object"
1466
- ? options.remote.wait
1467
- : undefined;
1468
- if (waitPolicy?.behavior === "block" &&
1469
- (waitPolicy.until ?? "any") === "any") {
1470
- const blockPromise = this.waitForCoverReady({
1471
- domain: options.remote.domain,
1472
- eager: options.remote.reach?.eager,
1473
- settle: "any",
1474
- timeout: waitPolicy.timeout ?? DEFAULT_TIMEOUT,
1475
- signal: ensureController().signal,
1476
- onTimeout: waitPolicy.onTimeout,
1477
- });
1478
- warmupPromise = warmupPromise
1479
- ? Promise.all([warmupPromise, blockPromise]).then(() => undefined)
1480
- : blockPromise;
1481
- }
1482
- }
1483
- const fetchFirst = async (n, fetchOptions) => {
1484
- await warmupPromise;
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;
1495
- queryRequestCoerced.fetch = n;
1496
- await this.queryCommence(queryRequestCoerced, {
1497
- local: fetchOptions?.from != null ? false : options?.local,
1498
- remote: options?.remote !== false && !skipRemoteDueToDiscovery
1499
- ? {
1500
- ...(typeof options?.remote === "object"
1501
- ? options.remote
1502
- : {}),
1503
- from: fetchOptions?.from ?? initialRemoteTargets,
1657
+ if (u === "local") {
1658
+ return {
1659
+ mergePolicy: buildMergePolicy(true, true),
1660
+ push: undefined,
1661
+ };
1504
1662
  }
1505
- : false,
1506
- resolve,
1507
- onResponse: async (response, from) => {
1508
- if (!from) {
1509
- logger.error("Missing response from");
1510
- return;
1663
+ if (u === "all") {
1664
+ self.ensurePrefetchAccumulator();
1665
+ return {
1666
+ mergePolicy: buildMergePolicy(true, true),
1667
+ push: types.PushUpdatesMode.STREAM,
1668
+ };
1511
1669
  }
1512
- if (response instanceof types.NoAccess) {
1513
- logger.error("Dont have access");
1514
- return;
1670
+ }
1671
+ if (typeof u === "object") {
1672
+ const hasMergeProp = Object.prototype.hasOwnProperty.call(u, "merge");
1673
+ const mergeValue = hasMergeProp ? u.merge : undefined;
1674
+ if (u.push) {
1675
+ self.ensurePrefetchAccumulator();
1515
1676
  }
1516
- else if (response instanceof types.Results) {
1517
- const results = response;
1518
- const existingBuffer = peerBufferMap.get(from.hashcode());
1519
- const buffer = existingBuffer?.buffer || [];
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
- }
1527
- return;
1528
- }
1529
- if (results.kept > 0n) {
1530
- hasMore = true;
1677
+ const callbacks = u.notify || u.onBatch
1678
+ ? {
1679
+ notify: u.notify,
1680
+ onBatch: u.onBatch,
1531
1681
  }
1532
- for (const result of results.results) {
1533
- const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
1534
- if (result instanceof types.ResultValue) {
1535
- const existingIndexed = indexedPlaceholders?.get(indexKey);
1536
- if (existingIndexed) {
1537
- existingIndexed.value =
1538
- result.value;
1539
- existingIndexed.context = result.context;
1540
- existingIndexed.from = from;
1541
- existingIndexed.indexed = await this.resolveIndexed(result, results.results);
1542
- indexedPlaceholders?.delete(indexKey);
1543
- continue;
1544
- }
1545
- if (visited.has(indexKey)) {
1546
- continue;
1547
- }
1548
- visited.add(indexKey);
1549
- buffer.push({
1550
- value: result.value,
1551
- context: result.context,
1552
- from,
1553
- indexed: await this.resolveIndexed(result, results.results),
1554
- });
1555
- }
1556
- else {
1557
- if (visited.has(indexKey) &&
1558
- !indexedPlaceholders?.has(indexKey)) {
1559
- continue;
1560
- }
1561
- visited.add(indexKey);
1562
- const indexed = coerceWithContext(result.indexed || result.value, result.context);
1563
- const placeholder = {
1564
- value: result.value,
1565
- context: result.context,
1566
- from,
1567
- indexed,
1568
- };
1569
- buffer.push(placeholder);
1570
- ensureIndexedPlaceholders().set(indexKey, placeholder);
1571
- }
1572
- }
1573
- peerBufferMap.set(from.hashcode(), {
1574
- buffer,
1575
- kept: Number(response.kept),
1576
- });
1577
- }
1578
- else {
1579
- throw new Error("Unsupported result type: " + response?.constructor?.name);
1682
+ : undefined;
1683
+ return {
1684
+ mergePolicy: buildMergePolicy(mergeValue, !hasMergeProp || mergeValue === undefined),
1685
+ push: typeof u.push === "number"
1686
+ ? u.push
1687
+ : u.push
1688
+ ? types.PushUpdatesMode.STREAM
1689
+ : undefined,
1690
+ callbacks,
1691
+ };
1692
+ }
1693
+ return {};
1694
+ }
1695
+ const { mergePolicy, push: pushUpdates, callbacks: updateCallbacksRaw, } = normalizeUpdatesOption(options?.updates);
1696
+ const hasLiveUpdates = mergePolicy !== undefined;
1697
+ const originalRemote = options?.remote;
1698
+ let remoteOptions = typeof originalRemote === "boolean"
1699
+ ? originalRemote
1700
+ : originalRemote
1701
+ ? { ...originalRemote }
1702
+ : undefined;
1703
+ if (pushUpdates && remoteOptions !== false) {
1704
+ if (typeof remoteOptions === "object") {
1705
+ if (remoteOptions.replicate !== true) {
1706
+ remoteOptions.replicate = true;
1580
1707
  }
1581
- },
1582
- }, fetchOptions?.fetchedFirstForRemote);
1583
- if (!hasMore) {
1584
- maybeSetDone();
1708
+ }
1709
+ else if (remoteOptions === undefined || remoteOptions === true) {
1710
+ remoteOptions = { replicate: true };
1711
+ }
1585
1712
  }
1586
- return !hasMore;
1587
- };
1588
- const fetchAtLeast = async (n) => {
1589
- if (done && first) {
1590
- return;
1713
+ if (remoteOptions !== originalRemote) {
1714
+ options = Object.assign({}, options, { remote: remoteOptions });
1715
+ }
1716
+ let resolve = options?.resolve !== false;
1717
+ if (!(queryRequestCoerced instanceof types.IterationRequest) &&
1718
+ options?.remote &&
1719
+ typeof options.remote !== "boolean" &&
1720
+ (options.remote.replicate || pushUpdates) &&
1721
+ options?.resolve !== false) {
1722
+ if ((queryRequest instanceof types.SearchRequestIndexed === false &&
1723
+ this.compatibility == null) ||
1724
+ (this.compatibility != null && this.compatibility > 8)) {
1725
+ queryRequestCoerced = new types.SearchRequestIndexed({
1726
+ query: queryRequestCoerced.query,
1727
+ fetch: queryRequestCoerced.fetch,
1728
+ sort: queryRequestCoerced.sort,
1729
+ });
1730
+ resolve = true;
1731
+ }
1591
1732
  }
1592
- if (this.closed) {
1593
- throw new ClosedError();
1594
- }
1595
- await fetchPromise;
1596
- totalFetchedCounter += n;
1597
- if (!first) {
1598
- first = true;
1599
- fetchPromise = fetchFirst(n);
1600
- return fetchPromise;
1601
- }
1602
- const promises = [];
1603
- let resultsLeft = 0;
1604
- for (const [peer, buffer] of peerBufferMap) {
1605
- if (buffer.buffer.length < n) {
1606
- const hasExistingRemoteResults = buffer.kept > 0;
1607
- if (!hasExistingRemoteResults && !keepRemoteAlive) {
1608
- if (peerBufferMap.get(peer)?.buffer.length === 0) {
1609
- peerBufferMap.delete(peer); // No more results
1610
- }
1611
- continue;
1733
+ let replicate = options?.remote &&
1734
+ typeof options.remote !== "boolean" &&
1735
+ (options.remote.replicate || pushUpdates);
1736
+ if (replicate &&
1737
+ queryRequestCoerced instanceof types.SearchRequestIndexed) {
1738
+ queryRequestCoerced.replicate = true;
1739
+ }
1740
+ indexIteratorLogger.trace("Iterate with options", {
1741
+ query: queryRequestCoerced,
1742
+ options,
1743
+ });
1744
+ let fetchPromise = undefined;
1745
+ const peerBufferMap = new Map();
1746
+ const visited = new Set();
1747
+ let indexedPlaceholders;
1748
+ const ensureIndexedPlaceholders = () => {
1749
+ if (!indexedPlaceholders) {
1750
+ indexedPlaceholders = new Map();
1751
+ }
1752
+ return indexedPlaceholders;
1753
+ };
1754
+ let done = false;
1755
+ let drain = false; // if true, close on empty once (overrides manual)
1756
+ let first = false;
1757
+ // TODO handle join/leave while iterating
1758
+ let controller = undefined;
1759
+ const ensureController = () => {
1760
+ if (!controller) {
1761
+ return (controller = new AbortController());
1762
+ }
1763
+ return controller;
1764
+ };
1765
+ let totalFetchedCounter = 0;
1766
+ let lastValueInOrder = undefined;
1767
+ let lastDeliveredIndexed;
1768
+ const peerBuffers = () => {
1769
+ return [...peerBufferMap.values()].map((x) => x.buffer).flat();
1770
+ };
1771
+ const toIndexedForOrdering = (value) => {
1772
+ const candidate = value;
1773
+ if (candidate && typeof candidate === "object") {
1774
+ if ("__indexed" in candidate && candidate.__indexed) {
1775
+ return coerceWithContext(candidate.__indexed, candidate.__context);
1612
1776
  }
1613
- // TODO buffer more than deleted?
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;
1777
+ if ("__context" in candidate) {
1778
+ return candidate;
1619
1779
  }
1620
- const collectRequest = new types.CollectNextRequest({
1621
- id: queryRequestCoerced.id,
1622
- amount,
1780
+ }
1781
+ return undefined;
1782
+ };
1783
+ const updateLastDelivered = (batch) => {
1784
+ if (!batch.length) {
1785
+ return;
1786
+ }
1787
+ const indexed = toIndexedForOrdering(batch[batch.length - 1]);
1788
+ if (indexed) {
1789
+ lastDeliveredIndexed = indexed;
1790
+ }
1791
+ };
1792
+ const compareIndexed = (a, b) => {
1793
+ return indexerTypes.extractSortCompare(a, b, queryRequestCoerced.sort);
1794
+ };
1795
+ const isLateResult = (indexed) => {
1796
+ if (!lastDeliveredIndexed) {
1797
+ return false;
1798
+ }
1799
+ return compareIndexed(indexed, lastDeliveredIndexed) < 0;
1800
+ };
1801
+ let maybeSetDone = () => {
1802
+ cleanup();
1803
+ done = true;
1804
+ };
1805
+ let unsetDone = () => {
1806
+ done = false;
1807
+ };
1808
+ let cleanup = () => {
1809
+ this.clearResultsQueue(queryRequestCoerced);
1810
+ };
1811
+ let warmupPromise = undefined;
1812
+ let discoveredTargetHashes;
1813
+ if (typeof options?.remote === "object") {
1814
+ let waitForTime = undefined;
1815
+ if (options.remote.wait) {
1816
+ let t0 = +new Date();
1817
+ waitForTime =
1818
+ typeof options.remote.wait === "boolean"
1819
+ ? DEFAULT_TIMEOUT
1820
+ : (options.remote.wait.timeout ?? DEFAULT_TIMEOUT);
1821
+ let setDoneIfTimeout = false;
1822
+ maybeSetDone = () => {
1823
+ if (t0 + waitForTime < +new Date()) {
1824
+ cleanup();
1825
+ done = true;
1826
+ }
1827
+ else {
1828
+ setDoneIfTimeout = true;
1829
+ }
1830
+ };
1831
+ unsetDone = () => {
1832
+ setDoneIfTimeout = false;
1833
+ done = false;
1834
+ };
1835
+ let timeout = setTimeout(() => {
1836
+ if (setDoneIfTimeout) {
1837
+ cleanup();
1838
+ done = true;
1839
+ }
1840
+ }, waitForTime);
1841
+ cleanup = () => {
1842
+ this.clearResultsQueue(queryRequestCoerced);
1843
+ clearTimeout(timeout);
1844
+ };
1845
+ }
1846
+ if (options.remote.reach?.discover) {
1847
+ const discoverTimeout = waitForTime ??
1848
+ (options.remote.wait ? DEFAULT_TIMEOUT : DISCOVER_TIMEOUT_FALLBACK);
1849
+ const discoverPromise = this.waitFor(options.remote.reach.discover, {
1850
+ signal: ensureController().signal,
1851
+ seek: "present",
1852
+ timeout: discoverTimeout,
1853
+ })
1854
+ .then((hashes) => {
1855
+ discoveredTargetHashes = hashes;
1856
+ })
1857
+ .catch((error) => {
1858
+ if (error instanceof TimeoutError || error instanceof AbortError) {
1859
+ discoveredTargetHashes = [];
1860
+ return;
1861
+ }
1862
+ throw error;
1863
+ });
1864
+ const prior = warmupPromise ?? Promise.resolve();
1865
+ warmupPromise = prior.then(() => discoverPromise);
1866
+ options.remote.reach.eager = true; // include the results from the discovered peer even if it is not mature
1867
+ }
1868
+ const waitPolicy = typeof options.remote.wait === "object"
1869
+ ? options.remote.wait
1870
+ : undefined;
1871
+ if (waitPolicy?.behavior === "block" &&
1872
+ (waitPolicy.until ?? "any") === "any") {
1873
+ const blockPromise = this.waitForCoverReady({
1874
+ domain: options.remote.domain,
1875
+ eager: options.remote.reach?.eager,
1876
+ settle: "any",
1877
+ timeout: waitPolicy.timeout ?? DEFAULT_TIMEOUT,
1878
+ signal: ensureController().signal,
1879
+ onTimeout: waitPolicy.onTimeout,
1623
1880
  });
1624
- // Fetch locally?
1625
- if (peer === this.node.identity.publicKey.hashcode()) {
1626
- if (!this._resumableIterators.has(queryRequestCoerced.idString)) {
1627
- continue; // no more results
1881
+ warmupPromise = warmupPromise
1882
+ ? Promise.all([warmupPromise, blockPromise]).then(() => undefined)
1883
+ : blockPromise;
1884
+ }
1885
+ }
1886
+ const fetchFirst = async (n, fetchOptions) => {
1887
+ await warmupPromise;
1888
+ let hasMore = false;
1889
+ const discoverTargets = typeof options?.remote === "object"
1890
+ ? options.remote.reach?.discover
1891
+ : undefined;
1892
+ const initialRemoteTargets = discoveredTargetHashes !== undefined
1893
+ ? discoveredTargetHashes
1894
+ : discoverTargets?.map((pk) => pk.hashcode().toString());
1895
+ const skipRemoteDueToDiscovery = typeof options?.remote === "object" &&
1896
+ options.remote.reach?.discover &&
1897
+ discoveredTargetHashes?.length === 0;
1898
+ queryRequestCoerced.fetch = n;
1899
+ await this.queryCommence(queryRequestCoerced, {
1900
+ local: fetchOptions?.from != null ? false : options?.local,
1901
+ remote: options?.remote !== false && !skipRemoteDueToDiscovery
1902
+ ? {
1903
+ ...(typeof options?.remote === "object"
1904
+ ? options.remote
1905
+ : {}),
1906
+ from: fetchOptions?.from ?? initialRemoteTargets,
1907
+ }
1908
+ : false,
1909
+ resolve,
1910
+ onResponse: async (response, from) => {
1911
+ if (!from) {
1912
+ logger.error("Missing response from");
1913
+ return;
1914
+ }
1915
+ if (response instanceof types.NoAccess) {
1916
+ logger.error("Dont have access");
1917
+ return;
1628
1918
  }
1629
- promises.push(this.processQuery(collectRequest, this.node.identity.publicKey, true)
1630
- .then(async (results) => {
1631
- resultsLeft += Number(results.kept);
1632
- if (results.results.length === 0) {
1633
- if (!keepRemoteAlive &&
1634
- peerBufferMap.get(peer)?.buffer.length === 0) {
1635
- peerBufferMap.delete(peer); // No more results
1919
+ else if (response instanceof types.Results) {
1920
+ const results = response;
1921
+ const existingBuffer = peerBufferMap.get(from.hashcode());
1922
+ const buffer = existingBuffer?.buffer || [];
1923
+ if (results.kept === 0n && results.results.length === 0) {
1924
+ if (keepRemoteAlive) {
1925
+ peerBufferMap.set(from.hashcode(), {
1926
+ buffer,
1927
+ kept: 0,
1928
+ });
1636
1929
  }
1930
+ return;
1637
1931
  }
1638
- else {
1639
- const peerBuffer = peerBufferMap.get(peer);
1640
- if (!peerBuffer) {
1641
- return;
1642
- }
1643
- peerBuffer.kept = Number(results.kept);
1644
- for (const result of results.results) {
1645
- const keyPrimitive = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
1646
- if (result instanceof types.ResultValue) {
1647
- const existingIndexed = indexedPlaceholders?.get(keyPrimitive);
1648
- if (existingIndexed) {
1649
- existingIndexed.value =
1650
- result.value;
1651
- existingIndexed.context = result.context;
1652
- existingIndexed.from = this.node.identity.publicKey;
1653
- existingIndexed.indexed =
1654
- await this.resolveIndexed(result, results.results);
1655
- indexedPlaceholders?.delete(keyPrimitive);
1656
- continue;
1657
- }
1658
- if (visited.has(keyPrimitive)) {
1659
- continue;
1660
- }
1661
- visited.add(keyPrimitive);
1662
- const indexed = await this.resolveIndexed(result, results.results);
1663
- peerBuffer.buffer.push({
1664
- value: result.value,
1665
- context: result.context,
1666
- from: this.node.identity.publicKey,
1667
- indexed,
1668
- });
1932
+ const reqFetch = queryRequestCoerced.fetch ?? 0;
1933
+ const inferredMore = reqFetch > 0 && results.results.length > reqFetch;
1934
+ const effectiveKept = Math.max(Number(results.kept), inferredMore ? 1 : 0);
1935
+ if (effectiveKept > 0) {
1936
+ hasMore = true;
1937
+ }
1938
+ for (const result of results.results) {
1939
+ const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
1940
+ if (result instanceof types.ResultValue) {
1941
+ const existingIndexed = indexedPlaceholders?.get(indexKey);
1942
+ if (existingIndexed) {
1943
+ existingIndexed.value =
1944
+ result.value;
1945
+ existingIndexed.context = result.context;
1946
+ existingIndexed.from = from;
1947
+ existingIndexed.indexed = await this.resolveIndexed(result, results.results);
1948
+ indexedPlaceholders?.delete(indexKey);
1949
+ continue;
1669
1950
  }
1670
- else {
1671
- if (visited.has(keyPrimitive) &&
1672
- !indexedPlaceholders?.has(keyPrimitive)) {
1673
- continue;
1674
- }
1675
- visited.add(keyPrimitive);
1676
- const indexed = coerceWithContext(result.indexed || result.value, result.context);
1677
- const placeholder = {
1678
- value: result.value,
1679
- context: result.context,
1680
- from: this.node.identity.publicKey,
1681
- indexed,
1682
- };
1683
- peerBuffer.buffer.push(placeholder);
1684
- ensureIndexedPlaceholders().set(keyPrimitive, placeholder);
1951
+ if (visited.has(indexKey)) {
1952
+ continue;
1685
1953
  }
1954
+ visited.add(indexKey);
1955
+ buffer.push({
1956
+ value: result.value,
1957
+ context: result.context,
1958
+ from,
1959
+ indexed: await this.resolveIndexed(result, results.results),
1960
+ });
1961
+ }
1962
+ else {
1963
+ if (visited.has(indexKey) &&
1964
+ !indexedPlaceholders?.has(indexKey)) {
1965
+ continue;
1966
+ }
1967
+ visited.add(indexKey);
1968
+ const indexed = coerceWithContext(result.indexed || result.value, result.context);
1969
+ const placeholder = {
1970
+ value: result.value,
1971
+ context: result.context,
1972
+ from,
1973
+ indexed,
1974
+ };
1975
+ buffer.push(placeholder);
1976
+ ensureIndexedPlaceholders().set(indexKey, placeholder);
1686
1977
  }
1687
1978
  }
1688
- })
1689
- .catch((e) => {
1690
- logger.error("Failed to collect sorted results from self. " + e?.message);
1691
- peerBufferMap.delete(peer);
1692
- }));
1693
- }
1694
- else {
1695
- // Fetch remotely
1696
- const idTranslation = this._prefetch?.accumulator.getTranslationMap(queryRequestCoerced);
1697
- let remoteCollectRequest = collectRequest;
1698
- if (idTranslation) {
1699
- remoteCollectRequest = new types.CollectNextRequest({
1700
- id: idTranslation.get(peer) || collectRequest.id,
1701
- amount: collectRequest.amount,
1979
+ peerBufferMap.set(from.hashcode(), {
1980
+ buffer,
1981
+ kept: effectiveKept,
1702
1982
  });
1703
1983
  }
1704
- promises.push(this._query
1705
- .request(remoteCollectRequest, {
1706
- ...options,
1707
- signal: options?.signal
1708
- ? AbortSignal.any([
1709
- options.signal,
1710
- ensureController().signal,
1711
- ])
1712
- : ensureController().signal,
1713
- priority: 1,
1714
- mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
1715
- })
1716
- .then((response) => introduceEntries(queryRequestCoerced, response, this.documentType, this.indexedType, this._sync, options)
1717
- .then(async (responses) => {
1718
- return Promise.all(responses.map(async (response, i) => {
1719
- resultsLeft += Number(response.response.kept);
1720
- const from = responses[i].from;
1721
- if (!from) {
1722
- logger.error("Missing from for sorted query");
1723
- return;
1724
- }
1725
- if (response.response.results.length === 0) {
1984
+ else {
1985
+ throw new Error("Unsupported result type: " + response?.constructor?.name);
1986
+ }
1987
+ },
1988
+ }, fetchOptions?.fetchedFirstForRemote);
1989
+ if (!hasMore) {
1990
+ maybeSetDone();
1991
+ }
1992
+ return !hasMore;
1993
+ };
1994
+ const fetchAtLeast = async (n) => {
1995
+ if (done && first) {
1996
+ return;
1997
+ }
1998
+ if (this.closed) {
1999
+ throw new ClosedError();
2000
+ }
2001
+ await fetchPromise;
2002
+ totalFetchedCounter += n;
2003
+ if (!first) {
2004
+ first = true;
2005
+ fetchPromise = fetchFirst(n);
2006
+ return fetchPromise;
2007
+ }
2008
+ const promises = [];
2009
+ let resultsLeft = 0;
2010
+ for (const [peer, buffer] of peerBufferMap) {
2011
+ if (buffer.buffer.length < n) {
2012
+ const hasExistingRemoteResults = buffer.kept > 0;
2013
+ if (!hasExistingRemoteResults && !keepRemoteAlive) {
2014
+ if (peerBufferMap.get(peer)?.buffer.length === 0) {
2015
+ peerBufferMap.delete(peer); // No more results
2016
+ }
2017
+ continue;
2018
+ }
2019
+ // TODO buffer more than deleted?
2020
+ // TODO batch to multiple 'to's
2021
+ const lacking = n - buffer.buffer.length;
2022
+ const amount = lacking > 0 ? lacking : keepRemoteAlive ? 1 : 0;
2023
+ if (amount <= 0) {
2024
+ continue;
2025
+ }
2026
+ const collectRequest = new types.CollectNextRequest({
2027
+ id: queryRequestCoerced.id,
2028
+ amount,
2029
+ });
2030
+ // Fetch locally?
2031
+ if (peer === this.node.identity.publicKey.hashcode()) {
2032
+ if (!this._resumableIterators.has(queryRequestCoerced.idString)) {
2033
+ continue; // no more results
2034
+ }
2035
+ promises.push(this.processQuery(collectRequest, this.node.identity.publicKey, true)
2036
+ .then(async (results) => {
2037
+ resultsLeft += Number(results.kept);
2038
+ if (results.results.length === 0) {
1726
2039
  if (!keepRemoteAlive &&
1727
2040
  peerBufferMap.get(peer)?.buffer.length === 0) {
1728
2041
  peerBufferMap.delete(peer); // No more results
@@ -1733,799 +2046,843 @@ let DocumentIndex = class DocumentIndex extends Program {
1733
2046
  if (!peerBuffer) {
1734
2047
  return;
1735
2048
  }
1736
- peerBuffer.kept = Number(response.response.kept);
1737
- for (const result of response.response.results) {
1738
- const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
2049
+ peerBuffer.kept = Number(results.kept);
2050
+ for (const result of results.results) {
2051
+ const keyPrimitive = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
1739
2052
  if (result instanceof types.ResultValue) {
1740
- const existingIndexed = indexedPlaceholders?.get(indexKey);
2053
+ const existingIndexed = indexedPlaceholders?.get(keyPrimitive);
1741
2054
  if (existingIndexed) {
1742
2055
  existingIndexed.value =
1743
2056
  result.value;
1744
2057
  existingIndexed.context = result.context;
1745
- existingIndexed.from = from;
2058
+ existingIndexed.from = this.node.identity.publicKey;
1746
2059
  existingIndexed.indexed =
1747
- await this.resolveIndexed(result, response.response
1748
- .results);
1749
- indexedPlaceholders?.delete(indexKey);
2060
+ await this.resolveIndexed(result, results.results);
2061
+ indexedPlaceholders?.delete(keyPrimitive);
1750
2062
  continue;
1751
2063
  }
1752
- if (visited.has(indexKey)) {
2064
+ if (visited.has(keyPrimitive)) {
1753
2065
  continue;
1754
2066
  }
1755
- visited.add(indexKey);
1756
- const indexed = await this.resolveIndexed(result, response.response
1757
- .results);
2067
+ visited.add(keyPrimitive);
2068
+ const indexed = await this.resolveIndexed(result, results.results);
1758
2069
  peerBuffer.buffer.push({
1759
2070
  value: result.value,
1760
2071
  context: result.context,
1761
- from: from,
2072
+ from: this.node.identity.publicKey,
1762
2073
  indexed,
1763
2074
  });
1764
2075
  }
1765
2076
  else {
1766
- if (visited.has(indexKey) &&
1767
- !indexedPlaceholders?.has(indexKey)) {
2077
+ if (visited.has(keyPrimitive) &&
2078
+ !indexedPlaceholders?.has(keyPrimitive)) {
1768
2079
  continue;
1769
2080
  }
1770
- visited.add(indexKey);
1771
- const indexed = coerceWithContext(result.value, result.context);
2081
+ visited.add(keyPrimitive);
2082
+ const indexed = coerceWithContext(result.indexed || result.value, result.context);
1772
2083
  const placeholder = {
1773
2084
  value: result.value,
1774
2085
  context: result.context,
1775
- from: from,
2086
+ from: this.node.identity.publicKey,
1776
2087
  indexed,
1777
2088
  };
1778
2089
  peerBuffer.buffer.push(placeholder);
1779
- ensureIndexedPlaceholders().set(indexKey, placeholder);
2090
+ ensureIndexedPlaceholders().set(keyPrimitive, placeholder);
1780
2091
  }
1781
2092
  }
1782
2093
  }
2094
+ })
2095
+ .catch((e) => {
2096
+ logger.error("Failed to collect sorted results from self. " + e?.message);
2097
+ peerBufferMap.delete(peer);
1783
2098
  }));
1784
- })
1785
- .catch((e) => {
1786
- logger.error("Failed to collect sorted results from: " +
1787
- peer +
1788
- ". " +
1789
- e?.message);
1790
- peerBufferMap.delete(peer);
1791
- })));
1792
- }
1793
- }
1794
- else {
1795
- resultsLeft += peerBufferMap.get(peer)?.kept || 0;
1796
- }
1797
- }
1798
- return (fetchPromise = Promise.all(promises).then(() => {
1799
- return resultsLeft === 0; // 0 results left to fetch and 0 pending results
1800
- }));
1801
- };
1802
- const next = async (n) => {
1803
- if (n < 0) {
1804
- throw new Error("Expecting to fetch a positive amount of element");
1805
- }
1806
- if (n === 0) {
1807
- return [];
1808
- }
1809
- // TODO everything below is not very optimized
1810
- const fetchedAll = await fetchAtLeast(n);
1811
- // get n next top entries, shift and pull more results
1812
- const peerBuffersArr = peerBuffers();
1813
- const results = peerBuffersArr.sort((a, b) => indexerTypes.extractSortCompare(a.indexed, b.indexed, queryRequestCoerced.sort));
1814
- lastValueInOrder = results[0] || lastValueInOrder;
1815
- const pendingMoreResults = n < results.length; // check if there are more results to fetch, before splicing
1816
- const batch = results.splice(0, n);
1817
- const hasMore = !fetchedAll || pendingMoreResults;
1818
- for (const result of batch) {
1819
- const arr = peerBufferMap.get(result.from.hashcode());
1820
- if (!arr) {
1821
- logger.error("Unexpected empty result buffer");
1822
- continue;
1823
- }
1824
- const idx = arr.buffer.findIndex((x) => x.value === result.value);
1825
- if (idx >= 0) {
1826
- arr.buffer.splice(idx, 1);
1827
- const consumedId = indexerTypes.toId(this.indexByResolver(result.indexed)).primitive;
1828
- indexedPlaceholders?.delete(consumedId);
1829
- }
1830
- }
1831
- if (hasMore) {
1832
- unsetDone();
1833
- }
1834
- else {
1835
- maybeSetDone();
1836
- }
1837
- let coercedBatch;
1838
- if (resolve) {
1839
- coercedBatch = (await Promise.all(batch.map(async (x) => {
1840
- const withContext = coerceWithContext(x.value instanceof this.documentType
1841
- ? x.value
1842
- : (await this.resolveDocument({
1843
- head: x.context.head,
1844
- indexed: x.indexed,
1845
- }))?.value, x.context);
1846
- const withIndexed = coerceWithIndexed(withContext, x.indexed);
1847
- return withIndexed;
1848
- }))).filter((x) => !!x);
1849
- }
1850
- else {
1851
- coercedBatch = batch.map((x) => coerceWithContext(coerceWithIndexed(x.value, x.indexed), x.context));
1852
- }
1853
- // no extra queued-first/last in simplified API
1854
- const deduped = dedup(coercedBatch, this.indexByResolver);
1855
- const fallbackReason = hasDeliveredResults ? "next" : "initial";
1856
- await emitOnResults(deduped, fallbackReason);
1857
- return deduped;
1858
- };
1859
- let cleanupAndDone = () => {
1860
- cleanup();
1861
- controller?.abort(new AbortError("Iterator closed"));
1862
- controller = undefined;
1863
- this.prefetch?.accumulator.clear(queryRequestCoerced);
1864
- this.processCloseIteratorRequest(queryRequestCoerced, this.node.identity.publicKey);
1865
- done = true;
1866
- };
1867
- let close = async () => {
1868
- cleanupAndDone();
1869
- // send close to remote
1870
- const closeRequest = new types.CloseIteratorRequest({
1871
- id: queryRequestCoerced.id,
1872
- });
1873
- const promises = [];
1874
- for (const [peer, buffer] of peerBufferMap) {
1875
- if (buffer.kept === 0) {
1876
- peerBufferMap.delete(peer);
1877
- continue;
1878
- }
1879
- if (peer !== this.node.identity.publicKey.hashcode()) {
1880
- // Close remote
1881
- promises.push(this._query.send(closeRequest, {
1882
- ...options,
1883
- mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
1884
- }));
1885
- }
1886
- }
1887
- await Promise.all(promises);
1888
- };
1889
- options?.signal && options.signal.addEventListener("abort", close);
1890
- let doneFn = () => {
1891
- return done;
1892
- };
1893
- let joinListener;
1894
- let fetchedFirstForRemote = undefined;
1895
- let updateDeferred;
1896
- const signalUpdate = (reason) => {
1897
- updateDeferred?.resolve();
1898
- };
1899
- const _waitForUpdate = () => updateDeferred ? updateDeferred.promise : Promise.resolve();
1900
- // ---------------- Live updates wiring (sorted-only with optional filter) ----------------
1901
- function normalizeUpdatesOption(u) {
1902
- const identityFilter = (evt) => evt;
1903
- const buildMergePolicy = (merge, defaultEnabled) => {
1904
- const effective = merge === undefined ? (defaultEnabled ? true : undefined) : merge;
1905
- if (effective === undefined || effective === false) {
1906
- return undefined;
1907
- }
1908
- if (effective === true) {
1909
- return {
1910
- merge: {
1911
- filter: identityFilter,
1912
- },
1913
- };
1914
- }
1915
- return {
1916
- merge: {
1917
- filter: effective.filter ?? identityFilter,
1918
- },
1919
- };
1920
- };
1921
- if (u == null || u === false) {
1922
- return { push: false };
1923
- }
1924
- if (u === true) {
1925
- return {
1926
- mergePolicy: buildMergePolicy(true, true),
1927
- push: false,
1928
- };
1929
- }
1930
- if (typeof u === "string") {
1931
- if (u === "remote") {
1932
- return { push: true };
1933
- }
1934
- if (u === "local") {
1935
- return {
1936
- mergePolicy: buildMergePolicy(true, true),
1937
- push: false,
1938
- };
1939
- }
1940
- if (u === "all") {
1941
- return {
1942
- mergePolicy: buildMergePolicy(true, true),
1943
- push: true,
1944
- };
1945
- }
1946
- }
1947
- if (typeof u === "object") {
1948
- const hasMergeProp = Object.prototype.hasOwnProperty.call(u, "merge");
1949
- const mergeValue = hasMergeProp ? u.merge : undefined;
1950
- return {
1951
- mergePolicy: buildMergePolicy(mergeValue, !hasMergeProp || mergeValue === undefined),
1952
- push: Boolean(u.push),
1953
- callbacks: u,
1954
- };
1955
- }
1956
- return { push: false };
1957
- }
1958
- const updateCallbacks = updateCallbacksRaw;
1959
- let pendingResultsReason;
1960
- let hasDeliveredResults = false;
1961
- const emitOnResults = async (batch, defaultReason) => {
1962
- if (!updateCallbacks?.onResults || batch.length === 0) {
1963
- return;
1964
- }
1965
- let reason;
1966
- if (pendingResultsReason) {
1967
- reason = pendingResultsReason;
1968
- }
1969
- else if (!hasDeliveredResults) {
1970
- reason = "initial";
1971
- }
1972
- else {
1973
- reason = defaultReason;
1974
- }
1975
- pendingResultsReason = undefined;
1976
- hasDeliveredResults = true;
1977
- await updateCallbacks.onResults(batch, { reason });
1978
- };
1979
- // sorted-only mode: no per-queue handling
1980
- // If live updates enabled, ensure deferred exists so awaiting paths can block until changes
1981
- if (hasLiveUpdates && !updateDeferred) {
1982
- updateDeferred = pDefer();
1983
- }
1984
- const keepRemoteAlive = (options?.closePolicy === "manual" || hasLiveUpdates || pushUpdates) &&
1985
- remoteOptions !== false;
1986
- if (queryRequestCoerced instanceof types.IterationRequest) {
1987
- queryRequestCoerced.resolve = resolve;
1988
- queryRequestCoerced.fetch = queryRequestCoerced.fetch ?? 10;
1989
- const replicateFlag = !resolve && replicate ? true : false;
1990
- queryRequestCoerced.replicate = replicateFlag;
1991
- const ttlSource = typeof remoteOptions === "object" &&
1992
- typeof remoteOptions?.wait === "object"
1993
- ? (remoteOptions.wait.timeout ?? DEFAULT_TIMEOUT)
1994
- : DEFAULT_TIMEOUT;
1995
- queryRequestCoerced.keepAliveTtl = keepRemoteAlive
1996
- ? BigInt(ttlSource)
1997
- : undefined;
1998
- queryRequestCoerced.pushUpdates = pushUpdates ? true : undefined;
1999
- queryRequestCoerced.mergeUpdates = mergePolicy?.merge ? true : undefined;
2000
- }
2001
- if (pushUpdates && this.prefetch?.accumulator) {
2002
- const targetPrefetchKey = idAgnosticQueryKey(queryRequestCoerced);
2003
- const mergePrefetchedResults = async (from, results) => {
2004
- const peerHash = from.hashcode();
2005
- const existingBuffer = peerBufferMap.get(peerHash);
2006
- const buffer = existingBuffer?.buffer || [];
2007
- if (results.kept === 0n && results.results.length === 0) {
2008
- peerBufferMap.set(peerHash, {
2009
- buffer,
2010
- kept: Number(results.kept),
2011
- });
2012
- return;
2013
- }
2014
- for (const result of results.results) {
2015
- const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
2016
- if (result instanceof types.ResultValue) {
2017
- const existingIndexed = indexedPlaceholders?.get(indexKey);
2018
- if (existingIndexed) {
2019
- existingIndexed.value =
2020
- result.value;
2021
- existingIndexed.context = result.context;
2022
- existingIndexed.from = from;
2023
- existingIndexed.indexed = await this.resolveIndexed(result, results.results);
2024
- indexedPlaceholders?.delete(indexKey);
2025
- continue;
2026
2099
  }
2027
- if (visited.has(indexKey)) {
2028
- continue;
2100
+ else {
2101
+ // Fetch remotely
2102
+ const idTranslation = this._prefetch?.accumulator.getTranslationMap(queryRequestCoerced);
2103
+ let remoteCollectRequest = collectRequest;
2104
+ if (idTranslation) {
2105
+ remoteCollectRequest = new types.CollectNextRequest({
2106
+ id: idTranslation.get(peer) || collectRequest.id,
2107
+ amount: collectRequest.amount,
2108
+ });
2109
+ }
2110
+ promises.push(this._query
2111
+ .request(remoteCollectRequest, {
2112
+ ...options,
2113
+ signal: options?.signal
2114
+ ? AbortSignal.any([
2115
+ options.signal,
2116
+ ensureController().signal,
2117
+ ])
2118
+ : ensureController().signal,
2119
+ priority: 1,
2120
+ mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
2121
+ })
2122
+ .then((response) => introduceEntries(queryRequestCoerced, response, this.documentType, this.indexedType, this._sync, options)
2123
+ .then(async (responses) => {
2124
+ return Promise.all(responses.map(async (response, i) => {
2125
+ resultsLeft += Number(response.response.kept);
2126
+ const from = responses[i].from;
2127
+ if (!from) {
2128
+ logger.error("Missing from for sorted query");
2129
+ return;
2130
+ }
2131
+ if (response.response.results.length === 0) {
2132
+ if (!keepRemoteAlive &&
2133
+ peerBufferMap.get(peer)?.buffer.length === 0) {
2134
+ peerBufferMap.delete(peer); // No more results
2135
+ }
2136
+ }
2137
+ else {
2138
+ const peerBuffer = peerBufferMap.get(peer);
2139
+ if (!peerBuffer) {
2140
+ return;
2141
+ }
2142
+ peerBuffer.kept = Number(response.response.kept);
2143
+ for (const result of response.response.results) {
2144
+ const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
2145
+ if (result instanceof types.ResultValue) {
2146
+ const existingIndexed = indexedPlaceholders?.get(indexKey);
2147
+ if (existingIndexed) {
2148
+ existingIndexed.value =
2149
+ result.value;
2150
+ existingIndexed.context = result.context;
2151
+ existingIndexed.from = from;
2152
+ existingIndexed.indexed =
2153
+ await this.resolveIndexed(result, response.response
2154
+ .results);
2155
+ indexedPlaceholders?.delete(indexKey);
2156
+ continue;
2157
+ }
2158
+ if (visited.has(indexKey)) {
2159
+ continue;
2160
+ }
2161
+ visited.add(indexKey);
2162
+ const indexed = await this.resolveIndexed(result, response.response
2163
+ .results);
2164
+ peerBuffer.buffer.push({
2165
+ value: result.value,
2166
+ context: result.context,
2167
+ from: from,
2168
+ indexed,
2169
+ });
2170
+ }
2171
+ else {
2172
+ if (visited.has(indexKey) &&
2173
+ !indexedPlaceholders?.has(indexKey)) {
2174
+ continue;
2175
+ }
2176
+ visited.add(indexKey);
2177
+ const indexed = coerceWithContext(result.value, result.context);
2178
+ const placeholder = {
2179
+ value: result.value,
2180
+ context: result.context,
2181
+ from: from,
2182
+ indexed,
2183
+ };
2184
+ peerBuffer.buffer.push(placeholder);
2185
+ ensureIndexedPlaceholders().set(indexKey, placeholder);
2186
+ }
2187
+ }
2188
+ }
2189
+ }));
2190
+ })
2191
+ .catch((e) => {
2192
+ logger.error("Failed to collect sorted results from: " +
2193
+ peer +
2194
+ ". " +
2195
+ e?.message);
2196
+ peerBufferMap.delete(peer);
2197
+ })));
2029
2198
  }
2030
- visited.add(indexKey);
2031
- buffer.push({
2032
- value: result.value,
2033
- context: result.context,
2034
- from,
2035
- indexed: await this.resolveIndexed(result, results.results),
2036
- });
2037
2199
  }
2038
2200
  else {
2039
- if (visited.has(indexKey) && !indexedPlaceholders?.has(indexKey)) {
2040
- continue;
2041
- }
2042
- visited.add(indexKey);
2043
- const indexed = coerceWithContext(result.indexed || result.value, result.context);
2044
- const placeholder = {
2045
- value: result.value,
2046
- context: result.context,
2047
- from,
2048
- indexed,
2049
- };
2050
- buffer.push(placeholder);
2051
- ensureIndexedPlaceholders().set(indexKey, placeholder);
2201
+ resultsLeft += peerBufferMap.get(peer)?.kept || 0;
2052
2202
  }
2053
2203
  }
2054
- peerBufferMap.set(peerHash, {
2055
- buffer,
2056
- kept: Number(results.kept),
2057
- });
2204
+ return (fetchPromise = Promise.all(promises).then(() => {
2205
+ return resultsLeft === 0; // 0 results left to fetch and 0 pending results
2206
+ }));
2058
2207
  };
2059
- const consumePrefetch = async (consumable) => {
2060
- const request = consumable.response?.request;
2061
- if (!request) {
2062
- return;
2208
+ const next = async (n) => {
2209
+ if (n < 0) {
2210
+ throw new Error("Expecting to fetch a positive amount of element");
2063
2211
  }
2064
- if (idAgnosticQueryKey(request) !== targetPrefetchKey) {
2065
- return;
2212
+ if (n === 0) {
2213
+ return [];
2066
2214
  }
2067
- try {
2068
- const prepared = await introduceEntries(queryRequestCoerced, [
2069
- {
2070
- response: consumable.response.results,
2071
- from: consumable.from,
2072
- },
2073
- ], this.documentType, this.indexedType, this._sync, options);
2074
- for (const response of prepared) {
2075
- if (!response.from) {
2076
- continue;
2077
- }
2078
- const payload = response.response;
2079
- if (!(payload instanceof types.Results)) {
2080
- continue;
2081
- }
2082
- await mergePrefetchedResults(response.from, payload);
2215
+ // TODO everything below is not very optimized
2216
+ const fetchedAll = await fetchAtLeast(n);
2217
+ // get n next top entries, shift and pull more results
2218
+ const peerBuffersArr = peerBuffers();
2219
+ const results = peerBuffersArr.sort((a, b) => indexerTypes.extractSortCompare(a.indexed, b.indexed, queryRequestCoerced.sort));
2220
+ lastValueInOrder = results[0] || lastValueInOrder;
2221
+ const pendingMoreResults = n < results.length; // check if there are more results to fetch, before splicing
2222
+ const batch = results.splice(0, n);
2223
+ const hasMore = !fetchedAll || pendingMoreResults;
2224
+ for (const result of batch) {
2225
+ const arr = peerBufferMap.get(result.from.hashcode());
2226
+ if (!arr) {
2227
+ logger.error("Unexpected empty result buffer");
2228
+ continue;
2083
2229
  }
2084
- if (!pendingResultsReason) {
2085
- pendingResultsReason = "change";
2230
+ const idx = arr.buffer.findIndex((x) => x.value === result.value);
2231
+ if (idx >= 0) {
2232
+ arr.buffer.splice(idx, 1);
2233
+ const consumedId = indexerTypes.toId(this.indexByResolver(result.indexed)).primitive;
2234
+ indexedPlaceholders?.delete(consumedId);
2086
2235
  }
2087
- signalUpdate("prefetch-add");
2088
2236
  }
2089
- catch (error) {
2090
- logger.warn("Failed to merge prefetched results", error);
2237
+ if (hasMore) {
2238
+ unsetDone();
2239
+ }
2240
+ else {
2241
+ maybeSetDone();
2242
+ }
2243
+ let coercedBatch;
2244
+ if (resolve) {
2245
+ coercedBatch = (await Promise.all(batch.map(async (x) => {
2246
+ const withContext = coerceWithContext(x.value instanceof this.documentType
2247
+ ? x.value
2248
+ : (await this.resolveDocument({
2249
+ head: x.context.head,
2250
+ indexed: x.indexed,
2251
+ }))?.value, x.context);
2252
+ const withIndexed = coerceWithIndexed(withContext, x.indexed);
2253
+ return withIndexed;
2254
+ }))).filter((x) => !!x);
2255
+ }
2256
+ else {
2257
+ coercedBatch = batch.map((x) => coerceWithContext(coerceWithIndexed(x.value, x.indexed), x.context));
2091
2258
  }
2259
+ // no extra queued-first/last in simplified API
2260
+ const deduped = dedup(coercedBatch, this.indexByResolver);
2261
+ const fallbackReason = hasDeliveredResults ? "manual" : "initial";
2262
+ updateLastDelivered(deduped);
2263
+ await emitOnBatch(deduped, fallbackReason);
2264
+ return deduped;
2092
2265
  };
2093
- const onPrefetchAdd = (evt) => {
2094
- void consumePrefetch(evt.detail.consumable);
2266
+ let cleanupAndDone = () => {
2267
+ cleanup();
2268
+ controller?.abort(new AbortError("Iterator closed"));
2269
+ controller = undefined;
2270
+ this.prefetch?.accumulator.clear(queryRequestCoerced);
2271
+ this.processCloseIteratorRequest(queryRequestCoerced, this.node.identity.publicKey);
2272
+ done = true;
2095
2273
  };
2096
- this.prefetch.accumulator.addEventListener("add", onPrefetchAdd);
2097
- const cleanupDefault = cleanup;
2098
- cleanup = () => {
2099
- this.prefetch?.accumulator.removeEventListener("add", onPrefetchAdd);
2100
- return cleanupDefault();
2274
+ let close = async () => {
2275
+ cleanupAndDone();
2276
+ // send close to remote
2277
+ const closeRequest = new types.CloseIteratorRequest({
2278
+ id: queryRequestCoerced.id,
2279
+ });
2280
+ const promises = [];
2281
+ for (const [peer, buffer] of peerBufferMap) {
2282
+ if (buffer.kept === 0) {
2283
+ peerBufferMap.delete(peer);
2284
+ continue;
2285
+ }
2286
+ if (peer !== this.node.identity.publicKey.hashcode()) {
2287
+ // Close remote
2288
+ promises.push(this._query.send(closeRequest, {
2289
+ ...options,
2290
+ mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
2291
+ }));
2292
+ }
2293
+ }
2294
+ await Promise.all(promises);
2101
2295
  };
2102
- }
2103
- let updatesCleanup;
2104
- if (hasLiveUpdates) {
2105
- const localHash = this.node.identity.publicKey.hashcode();
2106
- if (mergePolicy?.merge) {
2107
- // Ensure local buffer exists for sorted merging
2108
- if (!peerBufferMap.has(localHash)) {
2109
- peerBufferMap.set(localHash, { kept: 0, buffer: [] });
2110
- }
2111
- }
2112
- const queryFiltersForUpdates = indexerTypes.toQuery(queryRequestCoerced.query);
2113
- const hasQueryFiltersForUpdates = queryFiltersForUpdates.length > 0;
2114
- const createUpdateFilterIndex = async () => {
2115
- const index = new HashmapIndex();
2116
- await index.init({
2117
- schema: this.wrappedIndexedType,
2118
- indexBy: this.indexBy,
2119
- nested: this.nestedProperties,
2296
+ options?.signal && options.signal.addEventListener("abort", close);
2297
+ let doneFn = () => {
2298
+ return done;
2299
+ };
2300
+ let joinListener;
2301
+ let fetchedFirstForRemote = undefined;
2302
+ let updateDeferred;
2303
+ const onLateResults = typeof options?.remote === "object" &&
2304
+ typeof options.remote.onLateResults === "function"
2305
+ ? options.remote.onLateResults
2306
+ : undefined;
2307
+ const runNotify = (reason) => {
2308
+ if (!updateCallbacks?.notify) {
2309
+ return;
2310
+ }
2311
+ Promise.resolve(updateCallbacks.notify(reason)).catch((error) => {
2312
+ warn("Update notify callback failed", error);
2120
2313
  });
2121
- return index;
2122
2314
  };
2123
- const toIndexedWithContext = async (value) => {
2124
- const candidate = value;
2125
- if ("__indexed" in candidate && candidate.__indexed) {
2126
- return coerceWithContext(candidate.__indexed, candidate.__context);
2315
+ const signalUpdate = (reason) => {
2316
+ if (reason) {
2317
+ runNotify(reason);
2318
+ }
2319
+ updateDeferred?.resolve();
2320
+ };
2321
+ const _waitForUpdate = () => updateDeferred ? updateDeferred.promise : Promise.resolve();
2322
+ // ---------------- Live updates wiring (sorted-only with optional filter) ----------------
2323
+ const updateCallbacks = updateCallbacksRaw;
2324
+ let pendingBatchReason;
2325
+ let hasDeliveredResults = false;
2326
+ const emitOnBatch = async (batch, defaultReason) => {
2327
+ if (!updateCallbacks?.onBatch || batch.length === 0) {
2328
+ return;
2329
+ }
2330
+ let reason;
2331
+ if (pendingBatchReason) {
2332
+ reason = pendingBatchReason;
2127
2333
  }
2128
- if (value instanceof this.documentType) {
2129
- const transformed = await this.transformer(value, value.__context);
2130
- return coerceWithContext(transformed, value.__context);
2334
+ else if (!hasDeliveredResults) {
2335
+ reason = "initial";
2336
+ }
2337
+ else {
2338
+ reason = defaultReason;
2131
2339
  }
2132
- return value;
2340
+ pendingBatchReason = undefined;
2341
+ hasDeliveredResults = true;
2342
+ await updateCallbacks.onBatch(batch, { reason });
2133
2343
  };
2134
- const onChange = async (evt) => {
2135
- // Optional filter to mutate/suppress change events
2136
- let filtered = evt.detail;
2137
- if (mergePolicy?.merge?.filter) {
2138
- filtered = await mergePolicy.merge?.filter(evt.detail);
2139
- }
2140
- if (filtered) {
2141
- const changeForCallback = {
2142
- added: [],
2143
- removed: [],
2144
- };
2145
- let hasRelevantChange = false;
2146
- // Remove entries that were deleted from all pending structures
2147
- if (filtered.removed?.length) {
2148
- const removedIds = new Set();
2149
- for (const removed of filtered.removed) {
2150
- const id = indexerTypes.toId(this.indexByResolver(removed.__indexed)).primitive;
2151
- removedIds.add(id);
2152
- }
2153
- const matchedRemovedIds = new Set();
2154
- for (const [_peer, entry] of peerBufferMap) {
2155
- if (entry.buffer.length === 0) {
2344
+ // sorted-only mode: no per-queue handling
2345
+ // If live updates enabled, ensure deferred exists so awaiting paths can block until changes
2346
+ if (hasLiveUpdates && !updateDeferred) {
2347
+ updateDeferred = pDefer();
2348
+ }
2349
+ const keepRemoteAlive = (options?.closePolicy === "manual" || hasLiveUpdates || pushUpdates) &&
2350
+ remoteOptions !== false;
2351
+ if (queryRequestCoerced instanceof types.IterationRequest) {
2352
+ queryRequestCoerced.resolve = resolve;
2353
+ queryRequestCoerced.fetch = queryRequestCoerced.fetch ?? 10;
2354
+ const replicateFlag = !resolve && replicate ? true : false;
2355
+ queryRequestCoerced.replicate = replicateFlag;
2356
+ const ttlSource = typeof remoteOptions === "object" &&
2357
+ typeof remoteOptions?.wait === "object" &&
2358
+ remoteOptions.wait.behavior === "block"
2359
+ ? (remoteOptions.wait.timeout ?? DEFAULT_KEEP_REMOTE_ITERATOR_TIMEOUT)
2360
+ : DEFAULT_KEEP_REMOTE_ITERATOR_TIMEOUT;
2361
+ queryRequestCoerced.keepAliveTtl = keepRemoteAlive
2362
+ ? BigInt(ttlSource)
2363
+ : undefined;
2364
+ queryRequestCoerced.pushUpdates = pushUpdates;
2365
+ queryRequestCoerced.mergeUpdates = mergePolicy?.merge ? true : undefined;
2366
+ }
2367
+ if (pushUpdates && this.prefetch?.accumulator) {
2368
+ const currentPrefetchKey = () => idAgnosticQueryKey(queryRequestCoerced);
2369
+ const mergePrefetchedResults = async (from, results) => {
2370
+ const peerHash = from.hashcode();
2371
+ const existingBuffer = peerBufferMap.get(peerHash);
2372
+ const buffer = existingBuffer?.buffer || [];
2373
+ if (results.kept === 0n && results.results.length === 0) {
2374
+ peerBufferMap.set(peerHash, {
2375
+ buffer,
2376
+ kept: Number(results.kept),
2377
+ });
2378
+ return;
2379
+ }
2380
+ for (const result of results.results) {
2381
+ const indexKey = indexerTypes.toId(this.indexByResolver(result.value)).primitive;
2382
+ if (result instanceof types.ResultValue) {
2383
+ const existingIndexed = indexedPlaceholders?.get(indexKey);
2384
+ if (existingIndexed) {
2385
+ existingIndexed.value =
2386
+ result.value;
2387
+ existingIndexed.context = result.context;
2388
+ existingIndexed.from = from;
2389
+ existingIndexed.indexed = await this.resolveIndexed(result, results.results);
2390
+ indexedPlaceholders?.delete(indexKey);
2156
2391
  continue;
2157
2392
  }
2158
- entry.buffer = entry.buffer.filter((x) => {
2159
- const id = indexerTypes.toId(this.indexByResolver(x.indexed)).primitive;
2160
- if (removedIds.has(id)) {
2161
- matchedRemovedIds.add(id);
2162
- indexedPlaceholders?.delete(id);
2163
- return false;
2164
- }
2165
- return true;
2393
+ if (visited.has(indexKey)) {
2394
+ continue;
2395
+ }
2396
+ visited.add(indexKey);
2397
+ const indexed = await this.resolveIndexed(result, results.results);
2398
+ if (isLateResult(indexed)) {
2399
+ onLateResults?.({ amount: 1, peer: from });
2400
+ continue;
2401
+ }
2402
+ buffer.push({
2403
+ value: result.value,
2404
+ context: result.context,
2405
+ from,
2406
+ indexed,
2166
2407
  });
2167
2408
  }
2168
- if (matchedRemovedIds.size > 0) {
2169
- hasRelevantChange = true;
2170
- for (const removed of filtered.removed) {
2171
- const id = indexerTypes.toId(this.indexByResolver(removed.__indexed)).primitive;
2172
- if (matchedRemovedIds.has(id)) {
2173
- changeForCallback.removed.push(removed);
2174
- }
2409
+ else {
2410
+ if (visited.has(indexKey) && !indexedPlaceholders?.has(indexKey)) {
2411
+ continue;
2175
2412
  }
2413
+ visited.add(indexKey);
2414
+ const indexed = coerceWithContext(result.indexed || result.value, result.context);
2415
+ if (isLateResult(indexed)) {
2416
+ onLateResults?.({ amount: 1, peer: from });
2417
+ continue;
2418
+ }
2419
+ const placeholder = {
2420
+ value: result.value,
2421
+ context: result.context,
2422
+ from,
2423
+ indexed,
2424
+ };
2425
+ buffer.push(placeholder);
2426
+ ensureIndexedPlaceholders().set(indexKey, placeholder);
2176
2427
  }
2177
2428
  }
2178
- // Add new entries per strategy (sorted-only)
2179
- if (filtered.added?.length) {
2180
- let buf = peerBufferMap.get(localHash);
2181
- if (!buf) {
2182
- const created = { kept: 0, buffer: [] };
2183
- peerBufferMap.set(localHash, created);
2184
- buf = created;
2429
+ peerBufferMap.set(peerHash, {
2430
+ buffer,
2431
+ // Prefetched batches should not claim remote pending counts;
2432
+ // we'll collect more explicitly if needed.
2433
+ kept: 0,
2434
+ });
2435
+ };
2436
+ const consumePrefetch = async (consumable) => {
2437
+ const request = consumable.response?.request;
2438
+ if (!request) {
2439
+ return;
2440
+ }
2441
+ if (idAgnosticQueryKey(request) !== currentPrefetchKey()) {
2442
+ return;
2443
+ }
2444
+ try {
2445
+ const prepared = await introduceEntries(queryRequestCoerced, [
2446
+ {
2447
+ response: consumable.response.results,
2448
+ from: consumable.from,
2449
+ },
2450
+ ], this.documentType, this.indexedType, this._sync, options);
2451
+ for (const response of prepared) {
2452
+ if (!response.from) {
2453
+ continue;
2454
+ }
2455
+ const payload = response.response;
2456
+ if (!(payload instanceof types.Results)) {
2457
+ continue;
2458
+ }
2459
+ await mergePrefetchedResults(response.from, payload);
2460
+ }
2461
+ if (!pendingBatchReason) {
2462
+ pendingBatchReason = "push";
2185
2463
  }
2186
- const filterIndex = hasQueryFiltersForUpdates
2187
- ? await createUpdateFilterIndex()
2188
- : undefined;
2189
- for (const added of filtered.added) {
2190
- const addedValue = added;
2191
- const indexedCandidate = await toIndexedWithContext(addedValue);
2192
- if (filterIndex) {
2193
- filterIndex.drop();
2194
- filterIndex.put(indexedCandidate);
2195
- const matches = (await filterIndex
2196
- .iterate({
2197
- query: queryFiltersForUpdates,
2198
- sort: queryRequestCoerced.sort,
2199
- }, { reference: true, shape: undefined })
2200
- .next(1)).length > 0;
2201
- if (!matches) {
2464
+ signalUpdate("push");
2465
+ }
2466
+ catch (error) {
2467
+ warn("Failed to merge prefetched results", error);
2468
+ }
2469
+ };
2470
+ const onPrefetchAdd = (evt) => {
2471
+ void consumePrefetch(evt.detail.consumable);
2472
+ };
2473
+ this.prefetch.accumulator.addEventListener("add", onPrefetchAdd);
2474
+ const cleanupDefault = cleanup;
2475
+ cleanup = () => {
2476
+ this.prefetch?.accumulator.removeEventListener("add", onPrefetchAdd);
2477
+ return cleanupDefault();
2478
+ };
2479
+ }
2480
+ let updatesCleanup;
2481
+ if (hasLiveUpdates) {
2482
+ const localHash = this.node.identity.publicKey.hashcode();
2483
+ if (mergePolicy?.merge) {
2484
+ // Ensure local buffer exists for sorted merging
2485
+ if (!peerBufferMap.has(localHash)) {
2486
+ peerBufferMap.set(localHash, { kept: 0, buffer: [] });
2487
+ }
2488
+ }
2489
+ const queryFiltersForUpdates = indexerTypes.toQuery(queryRequestCoerced.query);
2490
+ const hasQueryFiltersForUpdates = queryFiltersForUpdates.length > 0;
2491
+ const createUpdateFilterIndex = async () => {
2492
+ const index = new HashmapIndex();
2493
+ await index.init({
2494
+ schema: this.wrappedIndexedType,
2495
+ indexBy: this.indexBy,
2496
+ nested: this.nestedProperties,
2497
+ });
2498
+ return index;
2499
+ };
2500
+ const toIndexedWithContext = async (value) => {
2501
+ const candidate = value;
2502
+ if ("__indexed" in candidate && candidate.__indexed) {
2503
+ return coerceWithContext(candidate.__indexed, candidate.__context);
2504
+ }
2505
+ if (value instanceof this.documentType) {
2506
+ const transformed = await this.transformer(value, value.__context);
2507
+ return coerceWithContext(transformed, value.__context);
2508
+ }
2509
+ return value;
2510
+ };
2511
+ const onChange = async (evt) => {
2512
+ // Optional filter to mutate/suppress change events
2513
+ indexIteratorLogger.trace("processing live update change event", evt.detail);
2514
+ let filtered = evt.detail;
2515
+ if (mergePolicy?.merge?.filter) {
2516
+ filtered = await mergePolicy.merge?.filter(evt.detail);
2517
+ }
2518
+ if (filtered) {
2519
+ let hasRelevantChange = false;
2520
+ // Remove entries that were deleted from all pending structures
2521
+ if (filtered.removed?.length) {
2522
+ const removedIds = new Set();
2523
+ for (const removed of filtered.removed) {
2524
+ const id = indexerTypes.toId(this.indexByResolver(removed.__indexed)).primitive;
2525
+ removedIds.add(id);
2526
+ }
2527
+ const matchedRemovedIds = new Set();
2528
+ for (const [_peer, entry] of peerBufferMap) {
2529
+ if (entry.buffer.length === 0) {
2202
2530
  continue;
2203
2531
  }
2532
+ entry.buffer = entry.buffer.filter((x) => {
2533
+ const id = indexerTypes.toId(this.indexByResolver(x.indexed)).primitive;
2534
+ if (removedIds.has(id)) {
2535
+ matchedRemovedIds.add(id);
2536
+ indexedPlaceholders?.delete(id);
2537
+ return false;
2538
+ }
2539
+ return true;
2540
+ });
2204
2541
  }
2205
- const id = indexerTypes.toId(this.indexByResolver(indexedCandidate)).primitive;
2206
- const existingIndexed = indexedPlaceholders?.get(id);
2207
- if (existingIndexed) {
2208
- if (resolve) {
2209
- existingIndexed.value = added;
2210
- existingIndexed.context = added.__context;
2211
- existingIndexed.from = this.node.identity.publicKey;
2212
- existingIndexed.indexed = indexedCandidate;
2213
- indexedPlaceholders?.delete(id);
2542
+ if (matchedRemovedIds.size > 0) {
2543
+ hasRelevantChange = true;
2544
+ }
2545
+ }
2546
+ // Add new entries per strategy (sorted-only)
2547
+ if (filtered.added?.length) {
2548
+ let buf = peerBufferMap.get(localHash);
2549
+ if (!buf) {
2550
+ const created = { kept: 0, buffer: [] };
2551
+ peerBufferMap.set(localHash, created);
2552
+ buf = created;
2553
+ }
2554
+ const filterIndex = hasQueryFiltersForUpdates
2555
+ ? await createUpdateFilterIndex()
2556
+ : undefined;
2557
+ for (const added of filtered.added) {
2558
+ const addedValue = added;
2559
+ const indexedCandidate = await toIndexedWithContext(addedValue);
2560
+ if (filterIndex) {
2561
+ filterIndex.drop();
2562
+ filterIndex.put(indexedCandidate);
2563
+ const matches = (await filterIndex
2564
+ .iterate({
2565
+ query: queryFiltersForUpdates,
2566
+ sort: queryRequestCoerced.sort,
2567
+ }, { reference: true, shape: undefined })
2568
+ .next(1)).length > 0;
2569
+ if (!matches) {
2570
+ continue;
2571
+ }
2214
2572
  }
2215
- continue;
2573
+ if (isLateResult(indexedCandidate)) {
2574
+ onLateResults?.({ amount: 1 });
2575
+ continue;
2576
+ }
2577
+ const id = indexerTypes.toId(this.indexByResolver(indexedCandidate)).primitive;
2578
+ const existingIndexed = indexedPlaceholders?.get(id);
2579
+ if (existingIndexed) {
2580
+ if (resolve) {
2581
+ existingIndexed.value = added;
2582
+ existingIndexed.context = added.__context;
2583
+ existingIndexed.from = this.node.identity.publicKey;
2584
+ existingIndexed.indexed = indexedCandidate;
2585
+ indexedPlaceholders?.delete(id);
2586
+ }
2587
+ continue;
2588
+ }
2589
+ if (visited.has(id))
2590
+ continue; // already presented
2591
+ visited.add(id);
2592
+ const valueForBuffer = resolve
2593
+ ? added
2594
+ : indexedCandidate;
2595
+ const placeholder = {
2596
+ value: valueForBuffer,
2597
+ context: added.__context,
2598
+ from: this.node.identity.publicKey,
2599
+ indexed: indexedCandidate,
2600
+ };
2601
+ buf.buffer.push(placeholder);
2602
+ if (!resolve) {
2603
+ ensureIndexedPlaceholders().set(id, placeholder);
2604
+ }
2605
+ hasRelevantChange = true;
2216
2606
  }
2217
- if (visited.has(id))
2218
- continue; // already presented
2219
- visited.add(id);
2220
- const valueForBuffer = resolve
2221
- ? added
2222
- : indexedCandidate;
2223
- const placeholder = {
2224
- value: valueForBuffer,
2225
- context: added.__context,
2226
- from: this.node.identity.publicKey,
2227
- indexed: indexedCandidate,
2228
- };
2229
- buf.buffer.push(placeholder);
2230
- if (!resolve) {
2231
- ensureIndexedPlaceholders().set(id, placeholder);
2607
+ }
2608
+ if (hasRelevantChange) {
2609
+ runNotify("change");
2610
+ if (!pendingBatchReason) {
2611
+ pendingBatchReason = "change";
2232
2612
  }
2233
- hasRelevantChange = true;
2234
- changeForCallback.added.push(added);
2613
+ signalUpdate();
2235
2614
  }
2236
2615
  }
2237
- if (hasRelevantChange) {
2238
- if (!pendingResultsReason) {
2239
- pendingResultsReason = "change";
2616
+ signalUpdate();
2617
+ };
2618
+ this.documentEvents.addEventListener("change", onChange);
2619
+ updatesCleanup = () => {
2620
+ this.documentEvents.removeEventListener("change", onChange);
2621
+ };
2622
+ const cleanupDefaultUpdates = cleanup;
2623
+ cleanup = () => {
2624
+ updatesCleanup?.();
2625
+ return cleanupDefaultUpdates();
2626
+ };
2627
+ }
2628
+ if (typeof options?.remote === "object" && options?.remote.wait) {
2629
+ // was used to account for missed results when a peer joins; omitted in this minimal handler
2630
+ updateDeferred = pDefer();
2631
+ const waitForTime = typeof options.remote.wait === "object" && options.remote.wait.timeout;
2632
+ const prevMaybeSetDone = maybeSetDone;
2633
+ maybeSetDone = () => {
2634
+ prevMaybeSetDone();
2635
+ if (done)
2636
+ signalUpdate(); // break deferred waits
2637
+ };
2638
+ let joinTimeoutId = waitForTime &&
2639
+ setTimeout(() => {
2640
+ signalUpdate();
2641
+ }, waitForTime);
2642
+ ensureController().signal.addEventListener("abort", () => signalUpdate());
2643
+ fetchedFirstForRemote = new Set();
2644
+ joinListener = this.createReplicatorJoinListener({
2645
+ signal: ensureController().signal,
2646
+ eager: options.remote.reach?.eager,
2647
+ onPeer: async (pk) => {
2648
+ if (done)
2649
+ return;
2650
+ const hash = pk.hashcode();
2651
+ await fetchPromise; // ensure fetches in flight are done
2652
+ if (peerBufferMap.has(hash))
2653
+ return;
2654
+ if (fetchedFirstForRemote.has(hash))
2655
+ return;
2656
+ if (totalFetchedCounter > 0) {
2657
+ fetchPromise = fetchFirst(totalFetchedCounter, {
2658
+ from: [hash],
2659
+ fetchedFirstForRemote,
2660
+ });
2661
+ await fetchPromise;
2662
+ if (onLateResults) {
2663
+ const pending = peerBufferMap.get(hash)?.buffer;
2664
+ if (pending && pending.length > 0) {
2665
+ if (lastValueInOrder) {
2666
+ const pendingWithLast = [...pending.flat(), lastValueInOrder];
2667
+ const results = pendingWithLast.sort((a, b) => indexerTypes.extractSortCompare(a.indexed, b.indexed, queryRequestCoerced.sort));
2668
+ const lateResults = results.findIndex((x) => x === lastValueInOrder);
2669
+ if (lateResults > 0) {
2670
+ onLateResults({ amount: lateResults });
2671
+ }
2672
+ }
2673
+ else {
2674
+ onLateResults({ amount: pending.length });
2675
+ }
2676
+ }
2677
+ }
2240
2678
  }
2241
- if (changeForCallback.added.length > 0 ||
2242
- changeForCallback.removed.length > 0) {
2243
- updateCallbacks?.onChange?.(changeForCallback);
2244
- signalUpdate("change");
2679
+ if (!pendingBatchReason) {
2680
+ pendingBatchReason = "join";
2245
2681
  }
2682
+ signalUpdate("join");
2683
+ },
2684
+ });
2685
+ const cleanupDefault = cleanup;
2686
+ cleanup = () => {
2687
+ joinListener && joinListener();
2688
+ joinTimeoutId && clearTimeout(joinTimeoutId);
2689
+ updateDeferred?.resolve();
2690
+ updateDeferred = undefined;
2691
+ return cleanupDefault();
2692
+ };
2693
+ }
2694
+ if (keepRemoteAlive) {
2695
+ const prevMaybeSetDone = maybeSetDone;
2696
+ maybeSetDone = () => {
2697
+ if (drain) {
2698
+ prevMaybeSetDone();
2699
+ }
2700
+ };
2701
+ }
2702
+ const remoteWaitActive = typeof options?.remote === "object" && !!options.remote.wait;
2703
+ const waitForUpdateAndResetDeferred = async () => {
2704
+ if (remoteWaitActive) {
2705
+ // wait until: join fetch adds results, cleanup runs, or the join-wait times out
2706
+ await _waitForUpdate();
2707
+ // re-arm the deferred for the next cycle (only if joining is enabled and we're not done)
2708
+ if (updateDeferred && !doneFn()) {
2709
+ updateDeferred = pDefer();
2246
2710
  }
2247
2711
  }
2248
- signalUpdate();
2249
- };
2250
- this.documentEvents.addEventListener("change", onChange);
2251
- updatesCleanup = () => {
2252
- this.documentEvents.removeEventListener("change", onChange);
2253
- };
2254
- const cleanupDefaultUpdates = cleanup;
2255
- cleanup = () => {
2256
- updatesCleanup?.();
2257
- return cleanupDefaultUpdates();
2258
- };
2259
- }
2260
- if (typeof options?.remote === "object" && options?.remote.wait) {
2261
- // was used to account for missed results when a peer joins; omitted in this minimal handler
2262
- updateDeferred = pDefer();
2263
- // derive optional onMissedResults callback if provided
2264
- let onMissedResults = typeof options?.remote?.wait === "object" &&
2265
- typeof options?.remote.onLateResults === "function"
2266
- ? options.remote.onLateResults
2267
- : undefined;
2268
- const waitForTime = typeof options.remote.wait === "object" && options.remote.wait.timeout;
2269
- const prevMaybeSetDone = maybeSetDone;
2270
- maybeSetDone = () => {
2271
- prevMaybeSetDone();
2272
- if (done)
2273
- signalUpdate(); // break deferred waits
2274
2712
  };
2275
- let joinTimeoutId = waitForTime &&
2276
- setTimeout(() => {
2277
- signalUpdate();
2278
- }, waitForTime);
2279
- ensureController().signal.addEventListener("abort", () => signalUpdate());
2280
- fetchedFirstForRemote = new Set();
2281
- joinListener = this.attachJoinListener({
2282
- signal: ensureController().signal,
2283
- eager: options.remote.reach?.eager,
2284
- onPeer: async (pk) => {
2285
- if (done)
2286
- return;
2287
- const hash = pk.hashcode();
2288
- await fetchPromise; // ensure fetches in flight are done
2289
- if (peerBufferMap.has(hash))
2290
- return;
2291
- if (fetchedFirstForRemote.has(hash))
2292
- return;
2293
- if (totalFetchedCounter > 0) {
2294
- fetchPromise = fetchFirst(totalFetchedCounter, {
2295
- from: [hash],
2296
- fetchedFirstForRemote,
2297
- });
2713
+ return {
2714
+ close,
2715
+ next,
2716
+ done: doneFn,
2717
+ pending: async () => {
2718
+ try {
2298
2719
  await fetchPromise;
2299
- if (onMissedResults) {
2300
- const pending = peerBufferMap.get(hash)?.buffer;
2301
- if (pending && pending.length > 0) {
2302
- if (lastValueInOrder) {
2303
- const pendingWithLast = [...pending.flat(), lastValueInOrder];
2304
- const results = pendingWithLast.sort((a, b) => indexerTypes.extractSortCompare(a.indexed, b.indexed, queryRequestCoerced.sort));
2305
- const lateResults = results.findIndex((x) => x === lastValueInOrder);
2306
- if (lateResults > 0) {
2307
- onMissedResults({ amount: lateResults });
2308
- }
2309
- }
2310
- else {
2311
- onMissedResults({ amount: pending.length });
2312
- }
2313
- }
2720
+ if (!done && keepRemoteAlive) {
2721
+ await fetchAtLeast(1);
2314
2722
  }
2315
2723
  }
2316
- if (!pendingResultsReason) {
2317
- pendingResultsReason = "join";
2724
+ catch (error) {
2725
+ warn("Failed to refresh iterator pending state", error);
2318
2726
  }
2319
- signalUpdate();
2727
+ let pendingCount = 0;
2728
+ for (const buffer of peerBufferMap.values()) {
2729
+ pendingCount += buffer.kept + buffer.buffer.length;
2730
+ }
2731
+ return pendingCount;
2732
+ },
2733
+ all: async () => {
2734
+ drain = true;
2735
+ let result = [];
2736
+ let c = 0;
2737
+ while (doneFn() !== true) {
2738
+ let batch = await next(100);
2739
+ c += batch.length;
2740
+ if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
2741
+ warn("Iterating for more than " +
2742
+ WARNING_WHEN_ITERATING_FOR_MORE_THAN +
2743
+ " results");
2744
+ }
2745
+ if (batch.length > 0) {
2746
+ result.push(...batch);
2747
+ continue;
2748
+ }
2749
+ await waitForUpdateAndResetDeferred();
2750
+ }
2751
+ cleanupAndDone();
2752
+ return result;
2753
+ },
2754
+ first: async () => {
2755
+ if (doneFn()) {
2756
+ return undefined;
2757
+ }
2758
+ let batch = await next(1);
2759
+ cleanupAndDone();
2760
+ return batch[0];
2761
+ },
2762
+ [Symbol.asyncIterator]: async function* () {
2763
+ drain = true;
2764
+ let c = 0;
2765
+ while (doneFn() !== true) {
2766
+ const batch = await next(100);
2767
+ c += batch.length;
2768
+ if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
2769
+ warn("Iterating for more than " +
2770
+ WARNING_WHEN_ITERATING_FOR_MORE_THAN +
2771
+ " results");
2772
+ }
2773
+ for (const entry of batch) {
2774
+ yield entry;
2775
+ }
2776
+ await waitForUpdateAndResetDeferred();
2777
+ }
2778
+ cleanupAndDone();
2320
2779
  },
2321
- });
2322
- const cleanupDefault = cleanup;
2323
- cleanup = () => {
2324
- joinListener && joinListener();
2325
- joinTimeoutId && clearTimeout(joinTimeoutId);
2326
- updateDeferred?.resolve();
2327
- updateDeferred = undefined;
2328
- return cleanupDefault();
2329
2780
  };
2330
2781
  }
2331
- if (keepRemoteAlive) {
2332
- const prevMaybeSetDone = maybeSetDone;
2333
- maybeSetDone = () => {
2334
- if (drain) {
2335
- prevMaybeSetDone();
2782
+ async updateResults(into, change, query, resolve) {
2783
+ let intoIndexable;
2784
+ if (into.length > 0) {
2785
+ if (resolve && into[0] instanceof this.documentType === false) {
2786
+ throw new Error("Expecting 'into' to be of type " + this.documentType.name);
2336
2787
  }
2337
- };
2338
- }
2339
- const remoteWaitActive = typeof options?.remote === "object" && !!options.remote.wait;
2340
- const waitForUpdateAndResetDeferred = async () => {
2341
- if (remoteWaitActive) {
2342
- // wait until: join fetch adds results, cleanup runs, or the join-wait times out
2343
- await _waitForUpdate();
2344
- // re-arm the deferred for the next cycle (only if joining is enabled and we're not done)
2345
- if (updateDeferred && !doneFn()) {
2346
- updateDeferred = pDefer();
2788
+ else if (!resolve && into[0] instanceof this.indexedType === false) {
2789
+ throw new Error("Expecting 'into' to be of type " + this.indexedType.name);
2347
2790
  }
2348
- }
2349
- };
2350
- return {
2351
- close,
2352
- next,
2353
- done: doneFn,
2354
- pending: async () => {
2355
- try {
2356
- await fetchPromise;
2357
- if (!done && keepRemoteAlive) {
2358
- await fetchAtLeast(1);
2359
- }
2791
+ if (resolve) {
2792
+ intoIndexable = await Promise.all(into.map(async (x) => {
2793
+ const transformed = await this.transformer(x, x.__context);
2794
+ return coerceWithContext(transformed, x.__context);
2795
+ }));
2360
2796
  }
2361
- catch (error) {
2362
- logger.warn("Failed to refresh iterator pending state", error);
2363
- }
2364
- let pendingCount = 0;
2365
- for (const buffer of peerBufferMap.values()) {
2366
- pendingCount += buffer.kept + buffer.buffer.length;
2367
- }
2368
- return pendingCount;
2369
- },
2370
- all: async () => {
2371
- drain = true;
2372
- let result = [];
2373
- let c = 0;
2374
- while (doneFn() !== true) {
2375
- let batch = await next(100);
2376
- c += batch.length;
2377
- if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
2378
- logger.warn("Iterating for more than " +
2379
- WARNING_WHEN_ITERATING_FOR_MORE_THAN +
2380
- " results");
2381
- }
2382
- if (batch.length > 0) {
2383
- result.push(...batch);
2384
- continue;
2385
- }
2386
- await waitForUpdateAndResetDeferred();
2797
+ else {
2798
+ intoIndexable = into;
2387
2799
  }
2388
- cleanupAndDone();
2389
- return result;
2390
- },
2391
- first: async () => {
2392
- if (doneFn()) {
2393
- return undefined;
2800
+ }
2801
+ else {
2802
+ intoIndexable = [];
2803
+ }
2804
+ const temporaryIndex = new HashmapIndex();
2805
+ await temporaryIndex.init({
2806
+ schema: this.wrappedIndexedType,
2807
+ indexBy: this.indexBy,
2808
+ nested: this.nestedProperties,
2809
+ });
2810
+ for (const value of intoIndexable) {
2811
+ temporaryIndex.put(value);
2812
+ }
2813
+ let anyChange = false;
2814
+ if (change.added && change.added.length > 0) {
2815
+ for (const added of change.added) {
2816
+ const indexed = added instanceof this.documentType
2817
+ ? coerceWithContext(await this.transformer(added, added.__context), added.__context)
2818
+ : added;
2819
+ temporaryIndex.put(indexed);
2820
+ anyChange = true;
2394
2821
  }
2395
- let batch = await next(1);
2396
- cleanupAndDone();
2397
- return batch[0];
2398
- },
2399
- [Symbol.asyncIterator]: async function* () {
2400
- drain = true;
2401
- let c = 0;
2402
- while (doneFn() !== true) {
2403
- const batch = await next(100);
2404
- c += batch.length;
2405
- if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
2406
- logger.warn("Iterating for more than " +
2407
- WARNING_WHEN_ITERATING_FOR_MORE_THAN +
2408
- " results");
2409
- }
2410
- for (const entry of batch) {
2411
- yield entry;
2822
+ }
2823
+ if (change.removed && change.removed.length > 0) {
2824
+ for (const removed of change.removed) {
2825
+ const indexed = removed instanceof this.documentType
2826
+ ? await this.transformer(removed, removed.__context)
2827
+ : removed;
2828
+ const id = indexerTypes.toId(this.indexByResolver(indexed)).primitive;
2829
+ const deleted = await temporaryIndex.del({
2830
+ query: [indexerTypes.getMatcher(this.indexBy, id)],
2831
+ });
2832
+ if (deleted.length > 0) {
2833
+ anyChange = true;
2412
2834
  }
2413
- await waitForUpdateAndResetDeferred();
2414
2835
  }
2415
- cleanupAndDone();
2416
- },
2417
- };
2418
- }
2419
- async updateResults(into, change, query, resolve) {
2420
- let intoIndexable;
2421
- if (into.length > 0) {
2422
- if (resolve && into[0] instanceof this.documentType === false) {
2423
- throw new Error("Expecting 'into' to be of type " + this.documentType.name);
2424
2836
  }
2425
- else if (!resolve && into[0] instanceof this.indexedType === false) {
2426
- throw new Error("Expecting 'into' to be of type " + this.indexedType.name);
2837
+ if (!anyChange) {
2838
+ return into;
2427
2839
  }
2840
+ let all = await temporaryIndex
2841
+ .iterate({
2842
+ query: indexerTypes.toQuery(query.query),
2843
+ sort: indexerTypes.toSort(query.sort),
2844
+ }, { reference: true, shape: undefined })
2845
+ .all();
2428
2846
  if (resolve) {
2429
- intoIndexable = await Promise.all(into.map(async (x) => {
2430
- const transformed = await this.transformer(x, x.__context);
2431
- return coerceWithContext(transformed, x.__context);
2432
- }));
2433
- }
2434
- else {
2435
- intoIndexable = into;
2436
- }
2437
- }
2438
- else {
2439
- intoIndexable = [];
2440
- }
2441
- const temporaryIndex = new HashmapIndex();
2442
- await temporaryIndex.init({
2443
- schema: this.wrappedIndexedType,
2444
- indexBy: this.indexBy,
2445
- nested: this.nestedProperties,
2446
- });
2447
- for (const value of intoIndexable) {
2448
- temporaryIndex.put(value);
2449
- }
2450
- let anyChange = false;
2451
- if (change.added && change.added.length > 0) {
2452
- for (const added of change.added) {
2453
- const indexed = added instanceof this.documentType
2454
- ? coerceWithContext(await this.transformer(added, added.__context), added.__context)
2455
- : added;
2456
- temporaryIndex.put(indexed);
2457
- anyChange = true;
2847
+ return (await Promise.all(all.map(async ({ id, value }) => {
2848
+ return this.resolveDocument({
2849
+ indexed: value,
2850
+ head: value.__context.head,
2851
+ id: id.primitive,
2852
+ }).then((resolved) => {
2853
+ if (resolved) {
2854
+ return coerceWithContext(resolved.value, value.__context);
2855
+ }
2856
+ return undefined;
2857
+ });
2858
+ }))).filter((x) => !!x);
2458
2859
  }
2860
+ return all.map((x) => x.value);
2459
2861
  }
2460
- if (change.removed && change.removed.length > 0) {
2461
- for (const removed of change.removed) {
2462
- const indexed = removed instanceof this.documentType
2463
- ? await this.transformer(removed, removed.__context)
2464
- : removed;
2465
- const id = indexerTypes.toId(this.indexByResolver(indexed)).primitive;
2466
- const deleted = await temporaryIndex.del({
2467
- query: [indexerTypes.getMatcher(this.indexBy, id)],
2468
- });
2469
- if (deleted.length > 0) {
2470
- anyChange = true;
2862
+ /**
2863
+ * Resolve the primary key for a document or indexed representation using the configured indexBy fields.
2864
+ * Useful when consumers need a stable id without assuming a specific property name exists.
2865
+ */
2866
+ resolveId(value) {
2867
+ let candidate = value;
2868
+ if (candidate && typeof candidate === "object") {
2869
+ if ("__indexed" in candidate && candidate.__indexed) {
2870
+ candidate = candidate.__indexed;
2471
2871
  }
2472
2872
  }
2873
+ const resolved = this.indexByResolver(candidate);
2874
+ return indexerTypes.toId(resolved);
2473
2875
  }
2474
- if (!anyChange) {
2475
- return into;
2476
- }
2477
- let all = await temporaryIndex
2478
- .iterate({
2479
- query: indexerTypes.toQuery(query.query),
2480
- sort: indexerTypes.toSort(query.sort),
2481
- }, { reference: true, shape: undefined })
2482
- .all();
2483
- if (resolve) {
2484
- return (await Promise.all(all.map(async ({ id, value }) => {
2485
- return this.resolveDocument({
2486
- indexed: value,
2487
- head: value.__context.head,
2488
- id: id.primitive,
2489
- }).then((resolved) => {
2490
- if (resolved) {
2491
- return coerceWithContext(resolved.value, value.__context);
2492
- }
2493
- return undefined;
2494
- });
2495
- }))).filter((x) => !!x);
2496
- }
2497
- return all.map((x) => x.value);
2498
- }
2499
- /**
2500
- * Resolve the primary key for a document or indexed representation using the configured indexBy fields.
2501
- * Useful when consumers need a stable id without assuming a specific property name exists.
2502
- */
2503
- resolveId(value) {
2504
- let candidate = value;
2505
- if (candidate && typeof candidate === "object") {
2506
- if ("__indexed" in candidate && candidate.__indexed) {
2507
- candidate = candidate.__indexed;
2876
+ async waitFor(other, options) {
2877
+ const hashes = await super.waitFor(other, options);
2878
+ for (const key of hashes) {
2879
+ await waitFor(async () => (await this._log.replicationIndex.count({ query: { hash: key } })) >
2880
+ 0, options);
2508
2881
  }
2882
+ return hashes;
2509
2883
  }
2510
- const resolved = this.indexByResolver(candidate);
2511
- return indexerTypes.toId(resolved);
2512
- }
2513
- async waitFor(other, options) {
2514
- const hashes = await super.waitFor(other, options);
2515
- for (const key of hashes) {
2516
- await waitFor(async () => (await this._log.replicationIndex.count({ query: { hash: key } })) >
2517
- 0, options);
2518
- }
2519
- return hashes;
2520
- }
2521
- };
2522
- __decorate([
2523
- field({ type: RPC }),
2524
- __metadata("design:type", RPC)
2525
- ], DocumentIndex.prototype, "_query", void 0);
2526
- DocumentIndex = __decorate([
2527
- variant("documents_index"),
2528
- __metadata("design:paramtypes", [Object])
2529
- ], DocumentIndex);
2884
+ };
2885
+ return DocumentIndex = _classThis;
2886
+ })();
2530
2887
  export { DocumentIndex };
2531
2888
  //# sourceMappingURL=search.js.map