@peerbit/document 10.0.4 → 10.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmark/index.js +114 -59
- package/dist/benchmark/index.js.map +1 -1
- package/dist/benchmark/iterate-replicate-2.js +117 -63
- package/dist/benchmark/iterate-replicate-2.js.map +1 -1
- package/dist/benchmark/iterate-replicate.js +106 -56
- package/dist/benchmark/iterate-replicate.js.map +1 -1
- package/dist/benchmark/memory/child.js +114 -59
- package/dist/benchmark/memory/child.js.map +1 -1
- package/dist/benchmark/replication.js +106 -52
- package/dist/benchmark/replication.js.map +1 -1
- package/dist/src/domain.d.ts.map +1 -1
- package/dist/src/domain.js +1 -3
- package/dist/src/domain.js.map +1 -1
- package/dist/src/events.d.ts +1 -1
- package/dist/src/events.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/most-common-query-predictor.d.ts +3 -3
- package/dist/src/most-common-query-predictor.d.ts.map +1 -1
- package/dist/src/most-common-query-predictor.js.map +1 -1
- package/dist/src/operation.js +175 -81
- package/dist/src/operation.js.map +1 -1
- package/dist/src/prefetch.d.ts +2 -2
- package/dist/src/prefetch.d.ts.map +1 -1
- package/dist/src/prefetch.js.map +1 -1
- package/dist/src/program.d.ts +2 -2
- package/dist/src/program.d.ts.map +1 -1
- package/dist/src/program.js +550 -508
- package/dist/src/program.js.map +1 -1
- package/dist/src/resumable-iterator.d.ts.map +1 -1
- package/dist/src/resumable-iterator.js +44 -0
- package/dist/src/resumable-iterator.js.map +1 -1
- package/dist/src/search.d.ts +14 -10
- package/dist/src/search.d.ts.map +1 -1
- package/dist/src/search.js +2477 -2120
- package/dist/src/search.js.map +1 -1
- package/package.json +21 -19
- package/src/domain.ts +1 -3
- package/src/events.ts +1 -1
- package/src/index.ts +1 -0
- package/src/most-common-query-predictor.ts +19 -5
- package/src/prefetch.ts +12 -3
- package/src/program.ts +7 -5
- package/src/resumable-iterator.ts +44 -0
- package/src/search.ts +564 -196
package/dist/src/search.js
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
8
|
-
|
|
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
|
-
|
|
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 =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
329
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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(
|
|
347
|
-
mode: new SilentDelivery({
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
392
|
-
|
|
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
|
-
|
|
404
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
:
|
|
410
|
-
|
|
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 (
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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:
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
return
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
if (
|
|
545
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
results
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
|
|
751
|
-
|
|
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
|
-
|
|
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
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
-
|
|
985
|
-
|
|
986
|
-
|
|
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
|
-
|
|
994
|
-
|
|
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
|
-
|
|
1236
|
+
timers.delete(idString);
|
|
1001
1237
|
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
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
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
|
|
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
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
-
|
|
1031
|
-
|
|
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
|
-
|
|
1346
|
+
cleanup();
|
|
1035
1347
|
}
|
|
1036
|
-
};
|
|
1037
|
-
if (signal) {
|
|
1038
|
-
signal.addEventListener("abort", onAbort);
|
|
1039
1348
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
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
|
-
|
|
1046
|
-
|
|
1376
|
+
finally {
|
|
1377
|
+
active.delete(hash);
|
|
1047
1378
|
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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 =
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
.
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
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
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
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
|
-
|
|
1425
|
-
|
|
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
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
if (setDoneIfTimeout) {
|
|
1434
|
-
cleanup();
|
|
1435
|
-
done = true;
|
|
1630
|
+
if (effective === true) {
|
|
1631
|
+
return {
|
|
1632
|
+
merge: {
|
|
1633
|
+
filter: identityFilter,
|
|
1634
|
+
},
|
|
1635
|
+
};
|
|
1436
1636
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1637
|
+
return {
|
|
1638
|
+
merge: {
|
|
1639
|
+
filter: effective.filter ?? identityFilter,
|
|
1640
|
+
},
|
|
1641
|
+
};
|
|
1441
1642
|
};
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
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
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
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
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1663
|
+
if (u === "all") {
|
|
1664
|
+
self.ensurePrefetchAccumulator();
|
|
1665
|
+
return {
|
|
1666
|
+
mergePolicy: buildMergePolicy(true, true),
|
|
1667
|
+
push: types.PushUpdatesMode.STREAM,
|
|
1668
|
+
};
|
|
1511
1669
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
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
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
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
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
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
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1708
|
+
}
|
|
1709
|
+
else if (remoteOptions === undefined || remoteOptions === true) {
|
|
1710
|
+
remoteOptions = { replicate: true };
|
|
1711
|
+
}
|
|
1585
1712
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
let
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
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
|
-
|
|
1614
|
-
|
|
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
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
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
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
peerBufferMap.
|
|
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
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
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
|
-
|
|
1671
|
-
|
|
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
|
-
|
|
1690
|
-
|
|
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
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
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(
|
|
1737
|
-
for (const result of
|
|
1738
|
-
const
|
|
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(
|
|
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 =
|
|
2058
|
+
existingIndexed.from = this.node.identity.publicKey;
|
|
1746
2059
|
existingIndexed.indexed =
|
|
1747
|
-
await this.resolveIndexed(result,
|
|
1748
|
-
|
|
1749
|
-
indexedPlaceholders?.delete(indexKey);
|
|
2060
|
+
await this.resolveIndexed(result, results.results);
|
|
2061
|
+
indexedPlaceholders?.delete(keyPrimitive);
|
|
1750
2062
|
continue;
|
|
1751
2063
|
}
|
|
1752
|
-
if (visited.has(
|
|
2064
|
+
if (visited.has(keyPrimitive)) {
|
|
1753
2065
|
continue;
|
|
1754
2066
|
}
|
|
1755
|
-
visited.add(
|
|
1756
|
-
const indexed = await this.resolveIndexed(result,
|
|
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:
|
|
2072
|
+
from: this.node.identity.publicKey,
|
|
1762
2073
|
indexed,
|
|
1763
2074
|
});
|
|
1764
2075
|
}
|
|
1765
2076
|
else {
|
|
1766
|
-
if (visited.has(
|
|
1767
|
-
!indexedPlaceholders?.has(
|
|
2077
|
+
if (visited.has(keyPrimitive) &&
|
|
2078
|
+
!indexedPlaceholders?.has(keyPrimitive)) {
|
|
1768
2079
|
continue;
|
|
1769
2080
|
}
|
|
1770
|
-
visited.add(
|
|
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:
|
|
2086
|
+
from: this.node.identity.publicKey,
|
|
1776
2087
|
indexed,
|
|
1777
2088
|
};
|
|
1778
2089
|
peerBuffer.buffer.push(placeholder);
|
|
1779
|
-
ensureIndexedPlaceholders().set(
|
|
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
|
-
|
|
2028
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
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
|
|
2060
|
-
|
|
2061
|
-
|
|
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 (
|
|
2065
|
-
return;
|
|
2212
|
+
if (n === 0) {
|
|
2213
|
+
return [];
|
|
2066
2214
|
}
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
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
|
-
|
|
2085
|
-
|
|
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
|
-
|
|
2090
|
-
|
|
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
|
-
|
|
2094
|
-
|
|
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
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
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
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
const
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
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
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
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 (
|
|
2129
|
-
|
|
2130
|
-
|
|
2334
|
+
else if (!hasDeliveredResults) {
|
|
2335
|
+
reason = "initial";
|
|
2336
|
+
}
|
|
2337
|
+
else {
|
|
2338
|
+
reason = defaultReason;
|
|
2131
2339
|
}
|
|
2132
|
-
|
|
2340
|
+
pendingBatchReason = undefined;
|
|
2341
|
+
hasDeliveredResults = true;
|
|
2342
|
+
await updateCallbacks.onBatch(batch, { reason });
|
|
2133
2343
|
};
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
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
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
}
|
|
2165
|
-
|
|
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
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
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
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
if
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
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
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
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
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
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
|
-
|
|
2234
|
-
changeForCallback.added.push(added);
|
|
2613
|
+
signalUpdate();
|
|
2235
2614
|
}
|
|
2236
2615
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
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 (
|
|
2242
|
-
|
|
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
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
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 (
|
|
2300
|
-
|
|
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
|
-
|
|
2317
|
-
|
|
2724
|
+
catch (error) {
|
|
2725
|
+
warn("Failed to refresh iterator pending state", error);
|
|
2318
2726
|
}
|
|
2319
|
-
|
|
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
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
if (
|
|
2335
|
-
|
|
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
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
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
|
-
|
|
2362
|
-
|
|
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
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
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
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
if (
|
|
2406
|
-
|
|
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
|
-
|
|
2426
|
-
|
|
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
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
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
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
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
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
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
|
-
|
|
2511
|
-
|
|
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
|