@peerbit/document 6.0.7-aa577a5 → 6.0.7-cccc078

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/benchmark/index.js +13 -14
  2. package/dist/benchmark/index.js.map +1 -1
  3. package/dist/benchmark/memory/index.d.ts +2 -0
  4. package/dist/benchmark/memory/index.d.ts.map +1 -0
  5. package/dist/benchmark/memory/index.js +122 -0
  6. package/dist/benchmark/memory/index.js.map +1 -0
  7. package/dist/benchmark/memory/insert.d.ts +2 -0
  8. package/dist/benchmark/memory/insert.d.ts.map +1 -0
  9. package/dist/benchmark/memory/insert.js +133 -0
  10. package/dist/benchmark/memory/insert.js.map +1 -0
  11. package/dist/benchmark/memory/utils.d.ts +13 -0
  12. package/dist/benchmark/memory/utils.d.ts.map +1 -0
  13. package/dist/benchmark/memory/utils.js +2 -0
  14. package/dist/benchmark/memory/utils.js.map +1 -0
  15. package/dist/benchmark/replication.js +27 -29
  16. package/dist/benchmark/replication.js.map +1 -1
  17. package/dist/src/borsh.d.ts +2 -0
  18. package/dist/src/borsh.d.ts.map +1 -0
  19. package/dist/src/borsh.js +16 -0
  20. package/dist/src/borsh.js.map +1 -0
  21. package/dist/src/index.d.ts +0 -1
  22. package/dist/src/index.d.ts.map +1 -1
  23. package/dist/src/index.js +0 -1
  24. package/dist/src/index.js.map +1 -1
  25. package/dist/src/program.d.ts +16 -19
  26. package/dist/src/program.d.ts.map +1 -1
  27. package/dist/src/program.js +57 -68
  28. package/dist/src/program.js.map +1 -1
  29. package/dist/src/search.d.ts +46 -32
  30. package/dist/src/search.d.ts.map +1 -1
  31. package/dist/src/search.js +236 -133
  32. package/dist/src/search.js.map +1 -1
  33. package/package.json +16 -11
  34. package/src/borsh.ts +19 -0
  35. package/src/index.ts +0 -1
  36. package/src/program.ts +118 -118
  37. package/src/search.ts +438 -218
package/src/search.ts CHANGED
@@ -1,22 +1,28 @@
1
1
  import { type AbstractType, field, serialize, variant } from "@dao-xyz/borsh";
2
+ import { Cache } from "@peerbit/cache";
3
+ import {
4
+ type MaybePromise,
5
+ PublicSignKey,
6
+ sha256Base64Sync,
7
+ } from "@peerbit/crypto";
8
+ import * as types from "@peerbit/document-interface";
9
+ import * as indexerTypes from "@peerbit/indexer-interface";
10
+ import { HashmapIndex } from "@peerbit/indexer-simple";
2
11
  import { BORSH_ENCODING, type Encoding, Entry } from "@peerbit/log";
12
+ import { logger as loggerFn } from "@peerbit/logger";
3
13
  import { Program } from "@peerbit/program";
4
- import * as types from "@peerbit/document-interface";
5
14
  import {
15
+ MissingResponsesError,
6
16
  RPC,
17
+ type RPCRequestAllOptions,
7
18
  type RPCResponse,
8
19
  queryAll,
9
- MissingResponsesError,
10
- type RPCRequestAllOptions
11
20
  } from "@peerbit/rpc";
12
- import { logger as loggerFn } from "@peerbit/logger";
13
- import { PublicSignKey, sha256Base64Sync } from "@peerbit/crypto";
14
21
  import { SharedLog } from "@peerbit/shared-log";
15
- import { concat, fromString } from "uint8arrays";
16
22
  import { SilentDelivery } from "@peerbit/stream-interface";
17
23
  import { AbortError } from "@peerbit/time";
18
- import { Cache } from "@peerbit/cache";
19
- import { HashmapIndexEngine } from "@peerbit/document-index-simple";
24
+ import { concat, fromString } from "uint8arrays";
25
+ import { copySerialization } from "./borsh.js";
20
26
  import { MAX_BATCH_SIZE } from "./constants.js";
21
27
 
22
28
  const logger = loggerFn({ module: "document-index" });
@@ -29,7 +35,7 @@ type BufferedResult<T> = {
29
35
  };
30
36
 
31
37
  @variant(0)
32
- export class Operation /* <T> */ { }
38
+ export class Operation /* <T> */ {}
33
39
 
34
40
  export const BORSH_ENCODING_OPERATION = BORSH_ENCODING(Operation);
35
41
 
@@ -44,12 +50,9 @@ export class PutOperation extends Operation /* <T> */ {
44
50
 
45
51
  /* _value?: T; */
46
52
 
47
- constructor(props?: { data: Uint8Array /* value?: T */ }) {
53
+ constructor(props: { data: Uint8Array /* value?: T */ }) {
48
54
  super();
49
- if (props) {
50
- this.data = props.data;
51
- /* this._value = props.value; */
52
- }
55
+ this.data = props.data;
53
56
  }
54
57
  }
55
58
 
@@ -72,10 +75,10 @@ export class PutAllOperation<T> extends Operation<T> {
72
75
  */
73
76
  @variant(2)
74
77
  export class DeleteOperation extends Operation {
75
- @field({ type: types.IdKey })
76
- key: types.IdKey;
78
+ @field({ type: indexerTypes.IdKey })
79
+ key: indexerTypes.IdKey;
77
80
 
78
- constructor(props: { key: types.IdKey }) {
81
+ constructor(props: { key: indexerTypes.IdKey }) {
79
82
  super();
80
83
  this.key = props.key;
81
84
  }
@@ -90,13 +93,11 @@ export type QueryOptions<R> = {
90
93
  remote?: boolean | RemoteQueryOptions<types.AbstractSearchResult<R>>;
91
94
  local?: boolean;
92
95
  };
93
- export type SearchOptions<R> = { size?: number } & QueryOptions<R>;
94
- export type IndexableFields<T> = (
95
- obj: T,
96
- context: types.Context
97
- ) => Record<string, any> | Promise<Record<string, any>>;
96
+ export type SearchOptions<R> = QueryOptions<R>;
97
+
98
+ type Transformer<T, I> = (obj: T, context: types.Context) => MaybePromise<I>;
98
99
 
99
- export type ResultsIterator<T> = {
100
+ type ResultsIterator<T> = {
100
101
  close: () => Promise<void>;
101
102
  next: (number: number) => Promise<T[]>;
102
103
  done: () => boolean;
@@ -105,7 +106,7 @@ export type ResultsIterator<T> = {
105
106
  type QueryDetailedOptions<T> = QueryOptions<T> & {
106
107
  onResponse?: (
107
108
  response: types.AbstractSearchResult<T>,
108
- from: PublicSignKey
109
+ from: PublicSignKey,
109
110
  ) => void | Promise<void>;
110
111
  };
111
112
 
@@ -113,7 +114,7 @@ const introduceEntries = async <T>(
113
114
  responses: RPCResponse<types.AbstractSearchResult<T>>[],
114
115
  type: AbstractType<T>,
115
116
  sync: (result: types.Results<T>) => Promise<void>,
116
- options?: QueryDetailedOptions<T>
117
+ options?: QueryDetailedOptions<T>,
117
118
  ): Promise<RPCResponse<types.Results<T>>[]> => {
118
119
  const results: RPCResponse<types.Results<T>>[] = [];
119
120
  for (const response of responses) {
@@ -140,16 +141,17 @@ const introduceEntries = async <T>(
140
141
 
141
142
  const dedup = <T>(
142
143
  allResult: T[],
143
- dedupBy: (obj: any) => string | Uint8Array | number | bigint
144
+ dedupBy: (obj: any) => string | Uint8Array | number | bigint,
144
145
  ) => {
145
- const unique: Set<types.IdPrimitive> = new Set();
146
+ const unique: Set<indexerTypes.IdPrimitive> = new Set();
146
147
  const dedup: T[] = [];
147
148
  for (const result of allResult) {
148
- const key = types.toIdeable(dedupBy(result));
149
- if (unique.has(key)) {
149
+ const key = indexerTypes.toId(dedupBy(result));
150
+ const primitive = key.primitive;
151
+ if (unique.has(primitive)) {
150
152
  continue;
151
153
  }
152
- unique.add(key);
154
+ unique.add(primitive);
153
155
  dedup.push(result);
154
156
  }
155
157
  return dedup;
@@ -163,44 +165,84 @@ if (!(await this.canRead(message.sender))) {
163
165
  } */
164
166
 
165
167
  export type CanSearch = (
166
- request: types.SearchRequest | types.CollectNextRequest,
167
- from: PublicSignKey
168
+ request: indexerTypes.SearchRequest | indexerTypes.CollectNextRequest,
169
+ from: PublicSignKey,
168
170
  ) => Promise<boolean> | boolean;
169
171
 
170
172
  export type CanRead<T> = (
171
173
  result: T,
172
- from: PublicSignKey
174
+ from: PublicSignKey,
173
175
  ) => Promise<boolean> | boolean;
174
176
 
175
- export type OpenOptions<T> = {
176
- type: AbstractType<T>;
177
+ export type IDocumentWithContext<I> = {
178
+ __context: types.Context;
179
+ } & I;
180
+
181
+ export type TransformerAsConstructor<T, I> = {
182
+ type?: new (arg: T, context: types.Context) => I;
183
+ };
184
+
185
+ export type TransformerAsFunction<T, I> = {
186
+ type: AbstractType<I>;
187
+ transform: (arg: T, context: types.Context) => I | Promise<I>;
188
+ };
189
+ export type TransformOptions<T, I> =
190
+ | TransformerAsConstructor<T, I>
191
+ | TransformerAsFunction<T, I>;
192
+
193
+ const isTransformerWithFunction = <T, I>(
194
+ options: TransformOptions<T, I>,
195
+ ): options is TransformerAsFunction<T, I> => {
196
+ return (options as TransformerAsFunction<T, I>).transform != null;
197
+ };
198
+
199
+ export type OpenOptions<T, I> = {
200
+ documentType: AbstractType<T>;
201
+
177
202
  dbType: AbstractType<types.IDocumentStore<T>>;
178
203
  log: SharedLog<Operation>;
179
204
  canRead?: CanRead<T>;
180
205
  canSearch?: CanSearch;
181
- engine?: types.IndexEngine;
182
206
  sync: (result: types.Results<T>) => Promise<void>;
183
207
  indexBy?: string | string[];
184
- fields: IndexableFields<T>;
208
+ transform?: TransformOptions<T, I>;
185
209
  };
186
210
 
211
+ type IndexableClass<I> = new (
212
+ value: I,
213
+ context: types.Context,
214
+ ) => IDocumentWithContext<I>; /* IDocumentWithContext<T>; */
215
+
187
216
  @variant("documents_index")
188
- export class DocumentIndex<T> extends Program<OpenOptions<T>> {
217
+ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
218
+ OpenOptions<T, I>
219
+ > {
189
220
  @field({ type: RPC })
190
- _query: RPC<types.AbstractSearchRequest, types.AbstractSearchResult<T>>;
221
+ _query: RPC<
222
+ indexerTypes.AbstractSearchRequest,
223
+ types.AbstractSearchResult<T>
224
+ >;
225
+
226
+ // Original document representation
227
+ documentType: AbstractType<T>;
191
228
 
192
- engine: types.IndexEngine;
229
+ // transform options
230
+ transformer: Transformer<T, I>;
193
231
 
194
- type: AbstractType<T>;
232
+ // The indexed document wrapped in a context
233
+ wrappedIndexedType: IndexableClass<I>;
234
+
235
+ // The database type, for recursive indexing
195
236
  dbType: AbstractType<types.IDocumentStore<T>>;
237
+ indexedTypeIsDocumentType: boolean;
196
238
 
197
239
  // Index key
198
- private indexBy: string | string[];
199
- private indexByArr: string[];
240
+ private indexBy: string[];
200
241
  private indexByResolver: (obj: any) => string | Uint8Array;
242
+ index: indexerTypes.Index<IDocumentWithContext<I>>;
201
243
 
202
244
  // Transformation, indexer
203
- fields: IndexableFields<T>;
245
+ /* fields: IndexableFields<T, I>; */
204
246
 
205
247
  private _valueEncoding: Encoding<T>;
206
248
 
@@ -211,8 +253,22 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
211
253
  private _resolverProgramCache?: Map<string | number | bigint, T>;
212
254
  private _resolverCache: Cache<T>;
213
255
  private _isProgramValues: boolean;
256
+
257
+ private _resultQueue: Map<
258
+ string,
259
+ {
260
+ from: PublicSignKey;
261
+ keptInIndex: number;
262
+ timeout: ReturnType<typeof setTimeout>;
263
+ queue: indexerTypes.IndexedResult<IDocumentWithContext<I>>[];
264
+ }
265
+ >;
266
+
214
267
  constructor(properties?: {
215
- query?: RPC<types.AbstractSearchRequest, types.AbstractSearchResult<T>>;
268
+ query?: RPC<
269
+ indexerTypes.AbstractSearchRequest,
270
+ types.AbstractSearchResult<T>
271
+ >;
216
272
  }) {
217
273
  super();
218
274
  this._query = properties?.query || new RPC();
@@ -222,47 +278,84 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
222
278
  return this._valueEncoding;
223
279
  }
224
280
 
225
- async open(properties: OpenOptions<T>) {
281
+ async open(properties: OpenOptions<T, I>) {
226
282
  this._log = properties.log;
227
- this.type = properties.type;
283
+
284
+ this.documentType = properties.documentType;
285
+ this.indexedTypeIsDocumentType =
286
+ !properties.transform?.type ||
287
+ properties.transform?.type === properties.documentType;
288
+
289
+ class IndexedClassWithContex {
290
+ @field({ type: types.Context })
291
+ __context: types.Context;
292
+
293
+ constructor(value: I, context: types.Context) {
294
+ Object.assign(this, value);
295
+ this.__context = context;
296
+ }
297
+ }
298
+
299
+ // copy all prototype values from indexedType to IndexedClassWithContex
300
+ copySerialization(
301
+ (properties.transform?.type || properties.documentType)!,
302
+ IndexedClassWithContex,
303
+ );
304
+
305
+ this.wrappedIndexedType = IndexedClassWithContex as new (
306
+ value: I,
307
+ context: types.Context,
308
+ ) => IDocumentWithContext<I>;
309
+
228
310
  // if this.type is a class that extends Program we want to do special functionality
229
- this._isProgramValues = this.type instanceof Program;
311
+ this._isProgramValues = this.documentType instanceof Program;
230
312
  this.dbType = properties.dbType;
313
+ this._resultQueue = new Map();
231
314
  this._sync = properties.sync;
232
- this.fields = properties.fields;
233
- this.indexBy = properties.indexBy || DEFAULT_INDEX_BY;
234
- this.indexByArr = Array.isArray(this.indexBy)
235
- ? this.indexBy
236
- : [this.indexBy];
237
- this.indexByResolver =
238
- typeof this.indexBy === "string"
239
- ? (obj) => obj[this.indexBy as string]
240
- : (obj: any) => types.extractFieldValue(obj, this.indexBy as string[]);
241
-
242
- this._valueEncoding = BORSH_ENCODING(this.type);
315
+
316
+ const transformOptions = properties.transform;
317
+ this.transformer = transformOptions
318
+ ? isTransformerWithFunction(transformOptions)
319
+ ? (obj, context) => transformOptions.transform(obj, context)
320
+ : transformOptions.type
321
+ ? (obj, context) => new transformOptions.type!(obj, context)
322
+ : (obj) => obj as any as I
323
+ : (obj) => obj as any as I; // TODO types
324
+
325
+ const maybeArr = properties.indexBy || DEFAULT_INDEX_BY;
326
+ this.indexBy = Array.isArray(maybeArr) ? maybeArr : [maybeArr];
327
+ this.indexByResolver = (obj: any) =>
328
+ indexerTypes.extractFieldValue(obj, this.indexBy);
329
+
330
+ this._valueEncoding = BORSH_ENCODING(this.documentType);
243
331
 
244
332
  if (this._isProgramValues) {
245
333
  this._resolverProgramCache = new Map();
246
334
  }
247
- this._resolverCache = new Cache({ max: 1000 }); // TODO choose limit better (adaptive)
248
-
249
- this.engine = properties.engine || new HashmapIndexEngine();
335
+ this._resolverCache = new Cache({ max: 10 }); // TODO choose limit better (adaptive)
336
+
337
+ this.index =
338
+ (await (
339
+ await this.node.indexer.scope(
340
+ sha256Base64Sync(
341
+ concat([this._log.log.id, fromString("/document-index")]),
342
+ ),
343
+ )
344
+ ).init({
345
+ indexBy: this.indexBy,
346
+ schema: this.wrappedIndexedType,
347
+ nested: {
348
+ match: (obj: any): obj is types.IDocumentStore<any> =>
349
+ obj instanceof this.dbType,
350
+ query: async (obj: types.IDocumentStore<any>, query) =>
351
+ obj.index.search(query),
352
+ },
353
+ /* maxBatchSize: MAX_BATCH_SIZE */
354
+ })) || new HashmapIndex<IDocumentWithContext<I>>();
250
355
 
251
- await this.engine.init({
252
- indexBy: this.indexBy,
253
- nested: {
254
- match: (obj: any): obj is types.IDocumentStore<any> =>
255
- obj instanceof this.dbType,
256
- query: async (obj: types.IDocumentStore<any>, query) =>
257
- obj.index.search(query)
258
- },
259
- maxBatchSize: MAX_BATCH_SIZE
260
- });
261
-
262
- await this.engine.start?.();
263
356
  await this._query.open({
264
357
  topic: sha256Base64Sync(
265
- concat([this._log.log.id, fromString("/document")])
358
+ concat([this._log.log.id, fromString("/document")]),
266
359
  ),
267
360
  responseHandler: async (query, ctx) => {
268
361
  if (!ctx.from) {
@@ -272,71 +365,84 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
272
365
 
273
366
  if (
274
367
  properties.canSearch &&
275
- (query instanceof types.SearchRequest ||
276
- query instanceof types.CollectNextRequest) &&
368
+ (query instanceof indexerTypes.SearchRequest ||
369
+ query instanceof indexerTypes.CollectNextRequest) &&
277
370
  !(await properties.canSearch(
278
- query as types.SearchRequest | types.CollectNextRequest,
279
- ctx.from
371
+ query as
372
+ | indexerTypes.SearchRequest
373
+ | indexerTypes.CollectNextRequest,
374
+ ctx.from,
280
375
  ))
281
376
  ) {
282
377
  return new types.NoAccess();
283
378
  }
284
379
 
285
- if (query instanceof types.CloseIteratorRequest) {
380
+ if (query instanceof indexerTypes.CloseIteratorRequest) {
286
381
  this.processCloseIteratorRequest(query, ctx.from);
287
382
  } else {
288
383
  const results = await this.processQuery(
289
384
  query as
290
- | types.SearchRequest
291
- | types.SearchRequest
292
- | types.CollectNextRequest,
385
+ | indexerTypes.SearchRequest
386
+ | indexerTypes.SearchRequest
387
+ | indexerTypes.CollectNextRequest,
293
388
  ctx.from,
389
+ false,
294
390
  {
295
- canRead: properties.canRead
296
- }
391
+ canRead: properties.canRead,
392
+ },
297
393
  );
298
394
 
299
395
  return new types.Results({
300
396
  // Even if results might have length 0, respond, because then we now at least there are no matching results
301
397
  results: results.results,
302
- kept: results.kept
398
+ kept: results.kept,
303
399
  });
304
400
  }
305
401
  },
306
402
  responseType: types.AbstractSearchResult,
307
- queryType: types.AbstractSearchRequest
403
+ queryType: indexerTypes.AbstractSearchRequest,
308
404
  });
309
405
  }
310
406
 
407
+ getPending(cursorId: string): number | undefined {
408
+ const queue = this._resultQueue.get(cursorId);
409
+ if (queue) {
410
+ return queue.queue.length + queue.keptInIndex;
411
+ }
412
+
413
+ return this.index.getPending(cursorId);
414
+ }
415
+
311
416
  async close(from?: Program): Promise<boolean> {
312
417
  const closed = await super.close(from);
313
418
  if (closed) {
314
- await this.engine.stop?.();
419
+ await this.index.stop?.();
315
420
  }
316
421
  return closed;
317
422
  }
318
423
 
319
424
  async drop(from?: Program): Promise<boolean> {
320
- const closed = await super.drop(from);
321
- if (closed) {
322
- await this.engine.stop?.();
425
+ const dropped = await super.drop(from);
426
+ if (dropped) {
427
+ await this.index.drop?.();
428
+ await this.index.stop?.();
323
429
  }
324
- return closed;
430
+ return dropped;
325
431
  }
326
432
 
327
433
  public async get(
328
- key: types.Ideable | types.IdKey,
329
- options?: QueryOptions<T>
434
+ key: indexerTypes.Ideable | indexerTypes.IdKey,
435
+ options?: QueryOptions<T>,
330
436
  ): Promise<T | undefined> {
331
437
  return (
332
438
  await this.getDetailed(
333
- key instanceof types.IdKey ? key : types.toId(key),
334
- options
439
+ key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key),
440
+ options,
335
441
  )
336
442
  )?.[0]?.results[0]?.value;
337
443
  }
338
444
 
339
- public async put(value: T, entry: Entry<Operation>, id: types.IdKey) {
445
+ public async put(value: T, entry: Entry<Operation>, id: indexerTypes.IdKey) {
340
446
  const idString = id.primitive;
341
447
  if (this._isProgramValues) {
342
448
  this._resolverProgramCache!.set(idString, value);
@@ -344,82 +450,94 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
344
450
  this._resolverCache.add(idString, value);
345
451
  }
346
452
 
453
+ const existing = await this.index.get(id);
347
454
  const context = new types.Context({
348
455
  created:
349
- (await this.engine.get(id))?.context.created ||
456
+ existing?.value.__context.created ||
350
457
  entry.meta.clock.timestamp.wallTime,
351
458
  modified: entry.meta.clock.timestamp.wallTime,
352
459
  head: entry.hash,
353
- gid: entry.gid
460
+ gid: entry.gid,
461
+ size: entry.payloadByteLength,
354
462
  });
355
463
 
356
- const valueToIndex = await this.fields(value, context);
357
- this.engine.put({
358
- id,
359
- indexed: valueToIndex,
464
+ const valueToIndex = await this.transformer(value, context);
465
+ const wrappedValueToIndex = new this.wrappedIndexedType(
466
+ valueToIndex as I,
360
467
  context,
361
- size: entry.payload.data.byteLength
362
- /* reference:
363
- valueToIndex === value || value instanceof Program
364
- ? { value }
365
- : undefined */
366
- });
468
+ );
469
+ await this.index.put(wrappedValueToIndex);
367
470
  }
368
471
 
369
- public del(key: types.IdPrimitive) {
370
- const keyObject = types.toId(key);
472
+ public del(key: indexerTypes.IdKey) {
371
473
  if (this._isProgramValues) {
372
- this._resolverProgramCache!.delete(key);
474
+ this._resolverProgramCache!.delete(key.primitive);
373
475
  } else {
374
- this._resolverCache.del(key);
476
+ this._resolverCache.del(key.primitive);
375
477
  }
376
- return this.engine.del(keyObject);
478
+ return this.index.del(
479
+ new indexerTypes.DeleteRequest({
480
+ query: [indexerTypes.getMatcher(this.indexBy, key.key)],
481
+ }),
482
+ );
377
483
  }
378
484
 
379
485
  public async getDetailed(
380
- key: types.IdKey | types.IdPrimitive,
381
- options?: QueryOptions<T>
486
+ key: indexerTypes.IdKey | indexerTypes.IdPrimitive,
487
+ options?: QueryOptions<T>,
382
488
  ): Promise<types.Results<T>[] | undefined> {
383
489
  let results: types.Results<T>[] | undefined;
384
490
  if (key instanceof Uint8Array) {
385
491
  results = await this.queryDetailed(
386
- new types.SearchRequest({
492
+ new indexerTypes.SearchRequest({
387
493
  query: [
388
- new types.ByteMatchQuery({ key: this.indexByArr, value: key })
389
- ]
494
+ new indexerTypes.ByteMatchQuery({ key: this.indexBy, value: key }),
495
+ ],
390
496
  }),
391
- options
497
+ options,
392
498
  );
393
499
  } else {
394
- const indexableKey = types.toIdeable(key);
500
+ const indexableKey = indexerTypes.toIdeable(key);
395
501
 
396
502
  if (
397
503
  typeof indexableKey === "number" ||
398
504
  typeof indexableKey === "bigint"
399
505
  ) {
400
506
  results = await this.queryDetailed(
401
- new types.SearchRequest({
507
+ new indexerTypes.SearchRequest({
402
508
  query: [
403
- new types.IntegerCompare({
404
- key: this.indexByArr,
405
- compare: types.Compare.Equal,
406
- value: indexableKey
407
- })
408
- ]
509
+ new indexerTypes.IntegerCompare({
510
+ key: this.indexBy,
511
+ compare: indexerTypes.Compare.Equal,
512
+ value: indexableKey,
513
+ }),
514
+ ],
409
515
  }),
410
- options
516
+ options,
411
517
  );
412
- } else {
518
+ } else if (typeof indexableKey === "string") {
519
+ results = await this.queryDetailed(
520
+ new indexerTypes.SearchRequest({
521
+ query: [
522
+ new indexerTypes.StringMatch({
523
+ key: this.indexBy,
524
+ value: indexableKey,
525
+ }),
526
+ ],
527
+ }),
528
+ options,
529
+ );
530
+ } else if (indexableKey instanceof Uint8Array) {
413
531
  results = await this.queryDetailed(
414
- new types.SearchRequest({
532
+ new indexerTypes.SearchRequest({
415
533
  query: [
416
- new types.StringMatch({
417
- key: this.indexByArr,
418
- value: indexableKey
419
- })
420
- ]
534
+ new indexerTypes.ByteMatchQuery({
535
+ key: this.indexBy,
536
+ value: indexableKey,
537
+ }),
538
+ ],
421
539
  }),
422
- options
540
+ options,
423
541
  );
424
542
  }
425
543
  }
@@ -428,11 +546,11 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
428
546
  }
429
547
 
430
548
  getSize(): Promise<number> | number {
431
- return this.engine.getSize();
549
+ return this.index.getSize();
432
550
  }
433
551
 
434
552
  private async resolveDocument(
435
- value: types.IndexedResult
553
+ value: indexerTypes.IndexedResult<IDocumentWithContext<I>>,
436
554
  ): Promise<{ value: T } | undefined> {
437
555
  const cached =
438
556
  this._resolverCache.get(value.id.primitive) ||
@@ -441,10 +559,17 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
441
559
  return { value: cached };
442
560
  }
443
561
 
444
- if (value.indexed instanceof this.type) {
445
- return { value: value.indexed as T };
562
+ if (this.indexedTypeIsDocumentType) {
563
+ // cast value to T, i.e. convert the class but keep all properties except the __context
564
+ const obj = Object.assign(
565
+ Object.create(this.documentType.prototype),
566
+ value.value,
567
+ );
568
+ delete obj.__context;
569
+ return { value: obj as T };
446
570
  }
447
- const head = await await this._log.log.get(value.context.head);
571
+
572
+ const head = await this._log.log.get(value.value.__context.head);
448
573
  if (!head) {
449
574
  return undefined; // we could end up here if we recently pruned the document and other peers never persisted the entry
450
575
  // TODO update changes in index before removing entries from log entry storage
@@ -452,36 +577,80 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
452
577
  const payloadValue = await head.getPayloadValue();
453
578
  if (payloadValue instanceof PutOperation) {
454
579
  return {
455
- value: this.valueEncoding.decoder(payloadValue.data)
580
+ value: this.valueEncoding.decoder(payloadValue.data),
456
581
  /* size: payloadValue.data.byteLength */
457
582
  };
458
583
  }
459
584
 
460
585
  throw new Error(
461
586
  "Unexpected value type when getting document: " +
462
- payloadValue?.constructor?.name || typeof payloadValue
587
+ payloadValue?.constructor?.name || typeof payloadValue,
463
588
  );
464
589
  }
465
590
 
466
591
  async processQuery(
467
- query: types.SearchRequest | types.CollectNextRequest,
592
+ query: indexerTypes.SearchRequest | indexerTypes.CollectNextRequest,
468
593
  from: PublicSignKey,
594
+ isLocal: boolean,
469
595
  options?: {
470
596
  canRead?: CanRead<T>;
471
- }
597
+ },
472
598
  ): Promise<types.Results<T>> {
473
599
  // We do special case for querying the id as we can do it faster than iterating
474
600
 
475
- let indexedResult: types.IndexedResults | undefined = undefined;
476
- if (query instanceof types.SearchRequest) {
477
- indexedResult = await this.engine.query(query, from);
478
- } else if (query instanceof types.CollectNextRequest) {
479
- indexedResult = await this.engine.next(query, from);
601
+ let prevQueued = isLocal
602
+ ? undefined
603
+ : this._resultQueue.get(query.idString);
604
+ if (prevQueued && !from.equals(prevQueued.from)) {
605
+ throw new Error("Different from in queued results");
606
+ }
607
+
608
+ let indexedResult:
609
+ | indexerTypes.IndexedResults<IDocumentWithContext<I>>
610
+ | undefined = undefined;
611
+ if (query instanceof indexerTypes.SearchRequest) {
612
+ indexedResult = await this.index.query(query);
613
+ } else if (query instanceof indexerTypes.CollectNextRequest) {
614
+ indexedResult =
615
+ prevQueued?.keptInIndex === 0
616
+ ? { kept: 0, results: [] }
617
+ : await this.index.next(query);
480
618
  } else {
481
619
  throw new Error("Unsupported");
482
620
  }
483
621
  const filteredResults: types.ResultWithSource<T>[] = [];
484
- for (const result of indexedResult.results) {
622
+ let resultSize = 0;
623
+
624
+ let toIterate = prevQueued
625
+ ? [...prevQueued.queue, ...indexedResult.results]
626
+ : indexedResult.results;
627
+
628
+ if (prevQueued) {
629
+ this._resultQueue.delete(query.idString);
630
+ prevQueued = undefined;
631
+ }
632
+
633
+ if (!isLocal) {
634
+ prevQueued = {
635
+ from,
636
+ queue: [],
637
+ timeout: setTimeout(() => {
638
+ this._resultQueue.delete(query.idString);
639
+ }, 6e4),
640
+ keptInIndex: indexedResult.kept,
641
+ };
642
+ this._resultQueue.set(query.idString, prevQueued);
643
+ }
644
+
645
+ for (const result of toIterate) {
646
+ if (!isLocal) {
647
+ resultSize += result.value.__context.size;
648
+ if (resultSize > MAX_BATCH_SIZE) {
649
+ prevQueued!.queue.push(result);
650
+ continue;
651
+ }
652
+ }
653
+
485
654
  const value = await this.resolveDocument(result);
486
655
  if (
487
656
  !value ||
@@ -491,25 +660,48 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
491
660
  }
492
661
  filteredResults.push(
493
662
  new types.ResultWithSource({
494
- context: result.context,
663
+ context: result.value.__context,
495
664
  value: value.value,
496
665
  source: serialize(value.value),
497
- indexed: result.indexed
498
- })
666
+ indexed: result.value,
667
+ }),
499
668
  );
500
669
  }
501
670
  const results: types.Results<T> = new types.Results({
502
671
  results: filteredResults,
503
- kept: BigInt(indexedResult.kept)
672
+ kept: BigInt(indexedResult.kept + (prevQueued?.queue.length || 0)),
504
673
  });
674
+
675
+ if (!isLocal && results.kept === 0n) {
676
+ this.clearResultsQueue(query);
677
+ }
678
+
505
679
  return results;
506
680
  }
507
681
 
682
+ clearResultsQueue(
683
+ query:
684
+ | indexerTypes.SearchRequest
685
+ | indexerTypes.CollectNextRequest
686
+ | indexerTypes.CloseIteratorRequest,
687
+ ) {
688
+ const queue = this._resultQueue.get(query.idString);
689
+ if (queue) {
690
+ clearTimeout(queue.timeout);
691
+ this._resultQueue.delete(query.idString);
692
+ }
693
+ }
508
694
  async processCloseIteratorRequest(
509
- query: types.CloseIteratorRequest,
510
- publicKey: PublicSignKey
695
+ query: indexerTypes.CloseIteratorRequest,
696
+ publicKey: PublicSignKey,
511
697
  ): Promise<void> {
512
- return this.engine.close(query, publicKey);
698
+ const queueData = this._resultQueue.get(query.idString);
699
+ if (queueData && !queueData.from.equals(publicKey)) {
700
+ logger.info("Ignoring close iterator request from different peer");
701
+ return;
702
+ }
703
+ this.clearResultsQueue(query);
704
+ return this.index.close(query);
513
705
  }
514
706
 
515
707
  /**
@@ -519,10 +711,10 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
519
711
  * @returns
520
712
  */
521
713
  public async queryDetailed(
522
- queryRequest: types.SearchRequest,
523
- options?: QueryDetailedOptions<T>
714
+ queryRequest: indexerTypes.SearchRequest,
715
+ options?: QueryDetailedOptions<T>,
524
716
  ): Promise<types.Results<T>[]> {
525
- const local = typeof options?.local == "boolean" ? options?.local : true;
717
+ const local = typeof options?.local === "boolean" ? options?.local : true;
526
718
  let remote: RemoteQueryOptions<types.AbstractSearchResult<T>> | undefined =
527
719
  undefined;
528
720
  if (typeof options?.remote === "boolean") {
@@ -544,7 +736,7 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
544
736
  const promises: Promise<types.Results<T>[] | undefined>[] = [];
545
737
  if (!local && !remote) {
546
738
  throw new Error(
547
- "Expecting either 'options.remote' or 'options.local' to be true"
739
+ "Expecting either 'options.remote' or 'options.local' to be true",
548
740
  );
549
741
  }
550
742
  const allResults: types.Results<T>[] = [];
@@ -552,7 +744,8 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
552
744
  if (local) {
553
745
  const results = await this.processQuery(
554
746
  queryRequest,
555
- this.node.identity.publicKey
747
+ this.node.identity.publicKey,
748
+ true,
556
749
  );
557
750
  if (results.results.length > 0) {
558
751
  options?.onResponse &&
@@ -563,26 +756,27 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
563
756
 
564
757
  if (remote) {
565
758
  const replicatorGroups = await this._log.getReplicatorUnion(
566
- remote.minAge
759
+ remote.minAge,
567
760
  );
761
+
568
762
  if (replicatorGroups) {
569
763
  const groupHashes: string[][] = replicatorGroups.map((x) => [x]);
570
764
  const fn = async () => {
571
765
  const rs: types.Results<T>[] = [];
572
766
  const responseHandler = async (
573
- results: RPCResponse<types.AbstractSearchResult<T>>[]
767
+ results: RPCResponse<types.AbstractSearchResult<T>>[],
574
768
  ) => {
575
769
  for (const r of await introduceEntries(
576
770
  results,
577
- this.type,
771
+ this.documentType,
578
772
  this._sync,
579
- options
773
+ options,
580
774
  )) {
581
775
  rs.push(r.response);
582
776
  }
583
777
  };
584
778
  try {
585
- if (queryRequest instanceof types.CloseIteratorRequest) {
779
+ if (queryRequest instanceof indexerTypes.CloseIteratorRequest) {
586
780
  // don't wait for responses
587
781
  await this._query.request(queryRequest, { mode: remote!.mode });
588
782
  } else {
@@ -591,7 +785,7 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
591
785
  groupHashes,
592
786
  queryRequest,
593
787
  responseHandler,
594
- remote
788
+ remote,
595
789
  );
596
790
  }
597
791
  } catch (error) {
@@ -639,11 +833,11 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
639
833
  * @returns
640
834
  */
641
835
  public async search(
642
- queryRequest: types.SearchRequest,
643
- options?: SearchOptions<T>
836
+ queryRequest: indexerTypes.SearchRequest,
837
+ options?: SearchOptions<T>,
644
838
  ): Promise<T[]> {
645
839
  // Set fetch to search size, or max value (default to max u32 (4294967295))
646
- queryRequest.fetch = options?.size ?? 0xffffffff;
840
+ queryRequest.fetch = queryRequest.fetch ?? 0xffffffff;
647
841
 
648
842
  // So that the iterator is pre-fetching the right amount of entries
649
843
  const iterator = this.iterate(queryRequest, options);
@@ -656,7 +850,7 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
656
850
  ) {
657
851
  // We might need to pull .next multiple time due to data message size limitations
658
852
  for (const result of await iterator.next(
659
- queryRequest.fetch - allResults.length
853
+ queryRequest.fetch - allResults.length,
660
854
  )) {
661
855
  allResults.push(result);
662
856
  }
@@ -675,8 +869,8 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
675
869
  * @returns
676
870
  */
677
871
  public iterate(
678
- queryRequest: types.SearchRequest,
679
- options?: QueryOptions<T>
872
+ queryRequest: indexerTypes.SearchRequest,
873
+ options?: QueryOptions<T>,
680
874
  ): ResultsIterator<T> {
681
875
  let fetchPromise: Promise<any> | undefined = undefined;
682
876
  const peerBufferMap: Map<
@@ -728,9 +922,9 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
728
922
  const buffer: BufferedResult<T>[] = [];
729
923
 
730
924
  for (const result of results.results) {
731
- const indexKey = types.toIdeable(
732
- this.indexByResolver(result.value)
733
- );
925
+ const indexKey = indexerTypes.toId(
926
+ this.indexByResolver(result.value),
927
+ ).primitive;
734
928
  if (visited.has(indexKey)) {
735
929
  continue;
736
930
  }
@@ -738,25 +932,29 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
738
932
  buffer.push({
739
933
  value: result.value,
740
934
  context: result.context,
741
- from: from,
935
+ from,
742
936
  indexed:
743
937
  result.indexed ||
744
- (await this.fields(result.value, result.context))
938
+ (await this.transformer(result.value, result.context)),
745
939
  });
746
940
  }
747
941
 
748
942
  peerBufferMap.set(from.hashcode(), {
749
943
  buffer,
750
- kept: Number(response.kept)
944
+ kept: Number(response.kept),
751
945
  });
752
946
  } else {
753
947
  throw new Error(
754
- "Unsupported result type: " + response?.constructor?.name
948
+ "Unsupported result type: " + response?.constructor?.name,
755
949
  );
756
950
  }
757
- }
951
+ },
758
952
  });
759
953
 
954
+ if (done) {
955
+ this.clearResultsQueue(queryRequest);
956
+ }
957
+
760
958
  return done;
761
959
  };
762
960
 
@@ -787,14 +985,18 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
787
985
 
788
986
  // TODO buffer more than deleted?
789
987
  // TODO batch to multiple 'to's
790
- const collectRequest = new types.CollectNextRequest({
988
+ const collectRequest = new indexerTypes.CollectNextRequest({
791
989
  id: queryRequest.id,
792
- amount: n - buffer.buffer.length
990
+ amount: n - buffer.buffer.length,
793
991
  });
794
992
  // Fetch locally?
795
993
  if (peer === this.node.identity.publicKey.hashcode()) {
796
994
  promises.push(
797
- this.processQuery(collectRequest, this.node.identity.publicKey)
995
+ this.processQuery(
996
+ collectRequest,
997
+ this.node.identity.publicKey,
998
+ true,
999
+ )
798
1000
  .then(async (results) => {
799
1001
  resultsLeft += Number(results.kept);
800
1002
 
@@ -812,13 +1014,15 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
812
1014
  for (const result of results.results) {
813
1015
  if (
814
1016
  visited.has(
815
- types.toIdeable(this.indexByResolver(result.value))
1017
+ indexerTypes.toId(this.indexByResolver(result.value))
1018
+ .primitive,
816
1019
  )
817
1020
  ) {
818
1021
  continue;
819
1022
  }
820
1023
  visited.add(
821
- types.toIdeable(this.indexByResolver(result.value))
1024
+ indexerTypes.toId(this.indexByResolver(result.value))
1025
+ .primitive,
822
1026
  );
823
1027
  peerBuffer.buffer.push({
824
1028
  value: result.value,
@@ -826,17 +1030,20 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
826
1030
  from: this.node.identity.publicKey,
827
1031
  indexed:
828
1032
  result.indexed ||
829
- (await this.fields(result.value, result.context))
1033
+ (await this.transformer(
1034
+ result.value,
1035
+ result.context,
1036
+ )),
830
1037
  });
831
1038
  }
832
1039
  }
833
1040
  })
834
1041
  .catch((e) => {
835
1042
  logger.error(
836
- "Failed to collect sorted results from self. " + e?.message
1043
+ "Failed to collect sorted results from self. " + e?.message,
837
1044
  );
838
1045
  peerBufferMap.delete(peer);
839
- })
1046
+ }),
840
1047
  );
841
1048
  } else {
842
1049
  // Fetch remotely
@@ -846,10 +1053,15 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
846
1053
  ...options,
847
1054
  signal: controller.signal,
848
1055
  priority: 1,
849
- mode: new SilentDelivery({ to: [peer], redundancy: 1 })
1056
+ mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
850
1057
  })
851
1058
  .then((response) =>
852
- introduceEntries(response, this.type, this._sync, options)
1059
+ introduceEntries(
1060
+ response,
1061
+ this.documentType,
1062
+ this._sync,
1063
+ options,
1064
+ )
853
1065
  .then((responses) => {
854
1066
  responses.map((response) => {
855
1067
  resultsLeft += Number(response.response.kept);
@@ -871,23 +1083,26 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
871
1083
  for (const result of response.response.results) {
872
1084
  if (
873
1085
  visited.has(
874
- types.toIdeable(
875
- this.indexByResolver(result.value)
876
- )
1086
+ indexerTypes.toId(
1087
+ this.indexByResolver(result.value),
1088
+ ).primitive,
877
1089
  )
878
1090
  ) {
879
1091
  continue;
880
1092
  }
881
1093
  visited.add(
882
- types.toIdeable(
883
- this.indexByResolver(result.value)
884
- )
1094
+ indexerTypes.toId(
1095
+ this.indexByResolver(result.value),
1096
+ ).primitive,
885
1097
  );
886
1098
  peerBuffer.buffer.push({
887
1099
  value: result.value,
888
1100
  context: result.context,
889
1101
  from: response.from!,
890
- indexed: this.fields(result.value, result.context)
1102
+ indexed: this.transformer(
1103
+ result.value,
1104
+ result.context,
1105
+ ),
891
1106
  });
892
1107
  }
893
1108
  }
@@ -896,13 +1111,13 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
896
1111
  .catch((e) => {
897
1112
  logger.error(
898
1113
  "Failed to collect sorted results from: " +
899
- peer +
900
- ". " +
901
- e?.message
1114
+ peer +
1115
+ ". " +
1116
+ e?.message,
902
1117
  );
903
1118
  peerBufferMap.delete(peer);
904
- })
905
- )
1119
+ }),
1120
+ ),
906
1121
  );
907
1122
  }
908
1123
  } else {
@@ -927,9 +1142,13 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
927
1142
  const fetchedAll = await fetchAtLeast(n);
928
1143
 
929
1144
  // get n next top entries, shift and pull more results
930
- const results = await types.resolvedSort(
931
- peerBuffers(),
932
- queryRequest.sort
1145
+ const peerBuffersArr = peerBuffers();
1146
+ const results = peerBuffersArr.sort((a, b) =>
1147
+ indexerTypes.extractSortCompare(
1148
+ a.indexed,
1149
+ b.indexed,
1150
+ queryRequest.sort,
1151
+ ),
933
1152
  );
934
1153
 
935
1154
  const pendingMoreResults = n < results.length;
@@ -942,24 +1161,25 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
942
1161
  logger.error("Unexpected empty result buffer");
943
1162
  continue;
944
1163
  }
945
- const idx = arr.buffer.findIndex((x) => x.value == result.value);
1164
+ const idx = arr.buffer.findIndex((x) => x.value === result.value);
946
1165
  if (idx >= 0) {
947
1166
  arr.buffer.splice(idx, 1);
948
1167
  }
949
1168
  }
950
1169
 
951
1170
  done = fetchedAll && !pendingMoreResults;
1171
+
952
1172
  return dedup(
953
1173
  batch.map((x) => x.value),
954
- this.indexByResolver
1174
+ this.indexByResolver,
955
1175
  );
956
1176
  };
957
1177
 
958
1178
  const close = async () => {
959
1179
  controller.abort(new AbortError("Iterator closed"));
960
1180
 
961
- const closeRequest = new types.CloseIteratorRequest({
962
- id: queryRequest.id
1181
+ const closeRequest = new indexerTypes.CloseIteratorRequest({
1182
+ id: queryRequest.id,
963
1183
  });
964
1184
  const promises: Promise<any>[] = [];
965
1185
  for (const [peer, buffer] of peerBufferMap) {
@@ -972,16 +1192,16 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
972
1192
  promises.push(
973
1193
  this.processCloseIteratorRequest(
974
1194
  closeRequest,
975
- this.node.identity.publicKey
976
- )
1195
+ this.node.identity.publicKey,
1196
+ ),
977
1197
  );
978
1198
  } else {
979
1199
  // Close remote
980
1200
  promises.push(
981
1201
  this._query.send(closeRequest, {
982
1202
  ...options,
983
- mode: new SilentDelivery({ to: [peer], redundancy: 1 })
984
- })
1203
+ mode: new SilentDelivery({ to: [peer], redundancy: 1 }),
1204
+ }),
985
1205
  );
986
1206
  }
987
1207
  }
@@ -991,7 +1211,7 @@ export class DocumentIndex<T> extends Program<OpenOptions<T>> {
991
1211
  return {
992
1212
  close,
993
1213
  next,
994
- done: () => done
1214
+ done: () => done,
995
1215
  };
996
1216
  }
997
1217
  }