@peerbit/document 8.2.0 → 9.0.0-63e24d5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/search.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { type AbstractType, field, serialize, variant } from "@dao-xyz/borsh";
2
- import { BlockResponse } from "@peerbit/blocks";
2
+ import type { PeerId } from "@libp2p/interface";
3
3
  import { Cache } from "@peerbit/cache";
4
4
  import {
5
5
  type MaybePromise,
6
6
  PublicSignKey,
7
+ getPublicKeyFromPeerId,
7
8
  sha256Base64Sync,
8
9
  } from "@peerbit/crypto";
9
10
  import * as types from "@peerbit/document-interface";
@@ -19,13 +20,9 @@ import {
19
20
  type RPCResponse,
20
21
  queryAll,
21
22
  } from "@peerbit/rpc";
22
- import {
23
- BlocksMessage,
24
- type ReplicationDomain,
25
- SharedLog,
26
- } from "@peerbit/shared-log";
23
+ import { type ReplicationDomain, SharedLog } from "@peerbit/shared-log";
27
24
  import { SilentDelivery } from "@peerbit/stream-interface";
28
- import { AbortError } from "@peerbit/time";
25
+ import { AbortError, waitFor } from "@peerbit/time";
29
26
  import { concat, fromString } from "uint8arrays";
30
27
  import { copySerialization } from "./borsh.js";
31
28
  import { MAX_BATCH_SIZE } from "./constants.js";
@@ -35,9 +32,9 @@ import { ResumableIterators } from "./resumable-iterator.js";
35
32
 
36
33
  const logger = loggerFn({ module: "document-index" });
37
34
 
38
- type BufferedResult<T> = {
35
+ type BufferedResult<T, I extends Record<string, any>> = {
39
36
  value: T;
40
- indexed: Record<string, any>;
37
+ indexed: I;
41
38
  context: types.Context;
42
39
  from: PublicSignKey;
43
40
  };
@@ -49,11 +46,16 @@ export type RemoteQueryOptions<R, D> = RPCRequestAllOptions<R> & {
49
46
  domain?: ExtractArgs<D>;
50
47
  eager?: boolean; // whether to query newly joined peers before they have matured
51
48
  };
52
- export type QueryOptions<R, D> = {
53
- remote?: boolean | RemoteQueryOptions<types.AbstractSearchResult<R>, D>;
49
+ export type QueryOptions<R, D, Resolve extends boolean | undefined> = {
50
+ remote?: boolean | RemoteQueryOptions<types.AbstractSearchResult, D>;
54
51
  local?: boolean;
52
+ resolve?: Resolve;
55
53
  };
56
- export type SearchOptions<R, D> = QueryOptions<R, D>;
54
+ export type SearchOptions<
55
+ R,
56
+ D,
57
+ Resolve extends boolean | undefined,
58
+ > = QueryOptions<R, D, Resolve>;
57
59
 
58
60
  type Transformer<T, I> = (obj: T, context: types.Context) => MaybePromise<I>;
59
61
 
@@ -64,37 +66,104 @@ export type ResultsIterator<T> = {
64
66
  all: () => Promise<T[]>;
65
67
  };
66
68
 
67
- type QueryDetailedOptions<T, D> = QueryOptions<T, D> & {
69
+ type QueryDetailedOptions<
70
+ T,
71
+ D,
72
+ Resolve extends boolean | undefined,
73
+ > = QueryOptions<T, D, Resolve> & {
68
74
  onResponse?: (
69
- response: types.AbstractSearchResult<T>,
75
+ response: types.AbstractSearchResult,
70
76
  from: PublicSignKey,
71
77
  ) => void | Promise<void>;
72
78
  };
73
79
 
74
- const introduceEntries = async <T, D>(
75
- queryRequest: types.SearchRequest,
76
- responses: RPCResponse<types.AbstractSearchResult<T>>[],
77
- type: AbstractType<T>,
80
+ type QueryLike /* <Resolve extends boolean | undefined> */ = {
81
+ query?: indexerTypes.Query[] | indexerTypes.QueryLike;
82
+ sort?: indexerTypes.Sort[] | indexerTypes.Sort | indexerTypes.SortLike;
83
+ /* resolve?: Resolve; */
84
+ };
85
+
86
+ /* type ExtractResolve<R> =
87
+ R extends QueryLike<infer X>
88
+ ? X extends boolean // if X is a boolean (true or false)
89
+ ? X
90
+ : true // else default to true
91
+ : true; // if R isn't QueryLike at all, default to true */
92
+
93
+ type ExtractResolveFromOptions<O> =
94
+ O extends QueryOptions<any, any, infer X>
95
+ ? X extends boolean // if X is a boolean (true or false)
96
+ ? X
97
+ : true // else default to true
98
+ : true; // if R isn't QueryLike at all, default to true
99
+
100
+ const coerceQuery = <Resolve extends boolean | undefined>(
101
+ query: types.SearchRequest | types.SearchRequestIndexed | QueryLike,
102
+ options?: QueryOptions<any, any, Resolve>,
103
+ ) => {
104
+ let replicate =
105
+ typeof options?.remote !== "boolean" ? options?.remote?.replicate : false;
106
+
107
+ if (
108
+ query instanceof types.SearchRequestIndexed &&
109
+ query.replicate === false &&
110
+ replicate
111
+ ) {
112
+ query.replicate = true;
113
+ return query;
114
+ }
115
+ if (query instanceof types.SearchRequest) {
116
+ return query;
117
+ }
118
+
119
+ const queryObject = query as QueryLike;
120
+
121
+ return options?.resolve || options?.resolve == null
122
+ ? new types.SearchRequest({
123
+ query: indexerTypes.toQuery(queryObject.query),
124
+ sort: indexerTypes.toSort(query.sort),
125
+ })
126
+ : new types.SearchRequestIndexed({
127
+ query: indexerTypes.toQuery(queryObject.query),
128
+ sort: indexerTypes.toSort(query.sort),
129
+ replicate,
130
+ });
131
+ };
132
+
133
+ const introduceEntries = async <
134
+ T,
135
+ I,
136
+ D,
137
+ R extends types.SearchRequest | types.SearchRequestIndexed,
138
+ >(
139
+ queryRequest: R,
140
+ responses: RPCResponse<types.AbstractSearchResult>[],
141
+ documentType: AbstractType<T>,
142
+ indexedType: AbstractType<I>,
78
143
  sync: (
79
- queryRequest: types.SearchRequest,
80
- result: types.Results<T>,
144
+ request: types.SearchRequest | types.SearchRequestIndexed,
145
+ response: types.Results<any>,
81
146
  ) => Promise<void>,
82
- options?: QueryDetailedOptions<T, D>,
83
- ): Promise<RPCResponse<types.Results<T>>[]> => {
84
- const results: RPCResponse<types.Results<T>>[] = [];
147
+ options?: QueryDetailedOptions<T, D, any>,
148
+ ): Promise<RPCResponse<types.Results<types.ResultTypeFromRequest<R>>>[]> => {
149
+ const results: RPCResponse<types.Results<any>>[] = [];
85
150
  for (const response of responses) {
86
151
  if (!response.from) {
87
152
  logger.error("Missing from for response");
88
153
  }
89
154
 
90
155
  if (response.response instanceof types.Results) {
91
- response.response.results.forEach((r) => r.init(type));
156
+ response.response.results.forEach((r) =>
157
+ r instanceof types.ResultValue
158
+ ? r.init(documentType)
159
+ : r.init(indexedType),
160
+ );
92
161
  if (typeof options?.remote !== "boolean" && options?.remote?.replicate) {
93
162
  await sync(queryRequest, response.response);
94
163
  }
95
164
  options?.onResponse &&
96
165
  (await options.onResponse(response.response, response.from!)); // TODO fix types
97
- results.push(response as RPCResponse<types.Results<T>>);
166
+ results.push(response as RPCResponse<types.Results<any>>);
98
167
  } else if (response.response instanceof types.NoAccess) {
99
168
  logger.error("Search resulted in access error");
100
169
  } else {
@@ -139,6 +208,17 @@ export type CanRead<T> = (
139
208
  from: PublicSignKey,
140
209
  ) => Promise<boolean> | boolean;
141
210
 
211
+ export type CanReadIndexed<I> = (
212
+ result: I,
213
+ from: PublicSignKey,
214
+ ) => Promise<boolean> | boolean;
215
+
216
+ type ValueTypeFromRequest<
217
+ Resolve extends boolean | undefined,
218
+ T,
219
+ I,
220
+ > = Resolve extends false ? I : T;
221
+
142
222
  @variant(0)
143
223
  export class IndexableContext implements types.Context {
144
224
  @field({ type: "u64" })
@@ -210,15 +290,19 @@ export type OpenOptions<
210
290
  documentType: AbstractType<T>;
211
291
  dbType: AbstractType<types.IDocumentStore<T>>;
212
292
  log: SharedLog<Operation, D, any>;
213
- canRead?: CanRead<T>;
293
+ canRead?: CanRead<I>;
214
294
  canSearch?: CanSearch;
215
- sync: (
216
- request: types.SearchRequest,
217
- result: types.Results<T>,
295
+ replicate: (
296
+ request: types.SearchRequest | types.SearchRequestIndexed,
297
+ results: types.Results<
298
+ types.ResultTypeFromRequest<
299
+ types.SearchRequest | types.SearchRequestIndexed
300
+ >
301
+ >,
218
302
  ) => Promise<void>;
219
303
  indexBy?: string | string[];
220
304
  transform?: TransformOptions<T, I>;
221
- emitBlocksEagerly?: boolean;
305
+ compatibility: 6 | 7 | 8 | undefined;
222
306
  };
223
307
 
224
308
  type IndexableClass<I> = new (
@@ -233,7 +317,7 @@ export class DocumentIndex<
233
317
  D extends ReplicationDomain<any, Operation, any>,
234
318
  > extends Program<OpenOptions<T, I, D>> {
235
319
  @field({ type: RPC })
236
- _query: RPC<types.AbstractSearchRequest, types.AbstractSearchResult<T>>;
320
+ _query: RPC<types.AbstractSearchRequest, types.AbstractSearchResult>;
237
321
 
238
322
  // Original document representation
239
323
  documentType: AbstractType<T>;
@@ -243,6 +327,7 @@ export class DocumentIndex<
243
327
 
244
328
  // The indexed document wrapped in a context
245
329
  wrappedIndexedType: IndexableClass<I>;
330
+ indexedType: AbstractType<I>;
246
331
 
247
332
  // The database type, for recursive indexing
248
333
  dbType: AbstractType<types.IDocumentStore<T>>;
@@ -254,16 +339,16 @@ export class DocumentIndex<
254
339
  index: indexerTypes.Index<IDocumentWithContext<I>>;
255
340
  private _resumableIterators: ResumableIterators<IDocumentWithContext<I>>;
256
341
 
257
- emitBlocksEagerly: boolean;
342
+ compatibility: 6 | 7 | 8 | undefined;
258
343
 
259
344
  // Transformation, indexer
260
345
  /* fields: IndexableFields<T, I>; */
261
346
 
262
347
  private _valueEncoding: Encoding<T>;
263
348
 
264
- private _sync: (
265
- reques: types.SearchRequest,
266
- result: types.Results<T>,
349
+ private _sync: <V extends types.ResultValue<T> | types.ResultIndexedValue<I>>(
350
+ request: types.SearchRequest | types.SearchRequestIndexed,
351
+ results: types.Results<V>,
267
352
  ) => Promise<void>;
268
353
 
269
354
  private _log: SharedLog<Operation, D, any>;
@@ -283,7 +368,7 @@ export class DocumentIndex<
283
368
  >;
284
369
 
285
370
  constructor(properties?: {
286
- query?: RPC<types.AbstractSearchRequest, types.AbstractSearchResult<T>>;
371
+ query?: RPC<types.AbstractSearchRequest, types.AbstractSearchResult>;
287
372
  }) {
288
373
  super();
289
374
  this._query = properties?.query || new RPC();
@@ -301,7 +386,7 @@ export class DocumentIndex<
301
386
  !properties.transform?.type ||
302
387
  properties.transform?.type === properties.documentType;
303
388
 
304
- this.emitBlocksEagerly = properties.emitBlocksEagerly || false;
389
+ this.compatibility = properties.compatibility;
305
390
 
306
391
  @variant(0)
307
392
  class IndexedClassWithContext {
@@ -315,10 +400,8 @@ export class DocumentIndex<
315
400
  }
316
401
 
317
402
  // copy all prototype values from indexedType to IndexedClassWithContext
318
- copySerialization(
319
- (properties.transform?.type || properties.documentType)!,
320
- IndexedClassWithContext,
321
- );
403
+ this.indexedType = (properties.transform?.type || properties.documentType)!;
404
+ copySerialization(this.indexedType, IndexedClassWithContext);
322
405
 
323
406
  this.wrappedIndexedType = IndexedClassWithContext as new (
324
407
  value: I,
@@ -329,7 +412,56 @@ export class DocumentIndex<
329
412
  this._isProgramValues = this.documentType instanceof Program;
330
413
  this.dbType = properties.dbType;
331
414
  this._resultQueue = new Map();
332
- this._sync = properties.sync;
415
+ this._sync = async (request, results) => {
416
+ /*
417
+ let allPromises: Promise<void> | undefined = undefined
418
+ if (waitForValue) {
419
+ let promises: Map<string, DeferredPromise<T>> = new Map();
420
+
421
+ for (const result of results) {
422
+ for (let i = 0; i < result.results.length; i++) {
423
+ let promise = defer<T>();
424
+ let r = result.results[i];
425
+ promises.set(r.context.head, promise);
426
+ const head = result.results[0].context.head;
427
+ let listeners = this.hashToValueListener.get(head);
428
+ if (!listeners) {
429
+ listeners = [];
430
+ this.hashToValueListener.set(head, listeners);
431
+ }
432
+ listeners.push(async (value) => {
433
+ promise.resolve(value);
434
+ result.results[i] = new types.ResultValue<T>({
435
+ context: r.context,
436
+ value,
437
+ source: serialize(value),
438
+ indexed: r.indexed,
439
+ }) as any;
440
+ });
441
+ promise.promise.finally(() => {
442
+ this.hashToValueListener.delete(head);
443
+ });
444
+ }
445
+ }
446
+
447
+ let timeout = setTimeout(() => {
448
+ for (const promise of promises!) {
449
+ promise[1].reject("Timed out resolving search result from value");
450
+ }
451
+ }, 1e4);
452
+
453
+ allPromises = Promise.all([...promises.values()].map((x) => x.promise)).then(
454
+ () => {
455
+ clearTimeout(timeout);
456
+ },
457
+ );
458
+ } */
459
+
460
+ await properties.replicate(request, results);
461
+ /* if (allPromises) {
462
+ await allPromises;
463
+ } */
464
+ };
333
465
 
334
466
  const transformOptions = properties.transform;
335
467
  this.transformer = transformOptions
@@ -401,7 +533,7 @@ export class DocumentIndex<
401
533
  const results = await this.processQuery(
402
534
  query as
403
535
  | types.SearchRequest
404
- | types.SearchRequest
536
+ | types.SearchRequestIndexed
405
537
  | types.CollectNextRequest,
406
538
  ctx.from,
407
539
  false,
@@ -410,29 +542,6 @@ export class DocumentIndex<
410
542
  },
411
543
  );
412
544
 
413
- if (this.emitBlocksEagerly) {
414
- await Promise.all(
415
- results.results.map(async (x) => {
416
- const hash = x.context.head;
417
- const block = await this._log.log.blocks.get(hash);
418
- if (!block) {
419
- return;
420
- }
421
-
422
- // todo dont do bytes -> entry -> bytes, but send the block directly
423
- return this._log.rpc.send(
424
- new BlocksMessage(new BlockResponse(hash, block)),
425
- {
426
- mode: new SilentDelivery({
427
- to: [ctx.from!],
428
- redundancy: 1,
429
- }),
430
- },
431
- );
432
- }),
433
- );
434
- }
435
-
436
545
  return new types.Results({
437
546
  // Even if results might have length 0, respond, because then we now at least there are no matching results
438
547
  results: results.results,
@@ -473,10 +582,20 @@ export class DocumentIndex<
473
582
  return dropped;
474
583
  }
475
584
 
476
- public async get(
585
+ public async get<Options extends QueryOptions<T, D, true | undefined>>(
477
586
  key: indexerTypes.Ideable | indexerTypes.IdKey,
478
- options?: QueryOptions<T, D>,
479
- ): Promise<T | undefined> {
587
+ options?: Options,
588
+ ): Promise<T>;
589
+
590
+ public async get<Options extends QueryOptions<T, D, false>>(
591
+ key: indexerTypes.Ideable | indexerTypes.IdKey,
592
+ options?: Options,
593
+ ): Promise<I>;
594
+
595
+ public async get<
596
+ Options extends QueryOptions<T, D, Resolve>,
597
+ Resolve extends boolean | undefined = ExtractResolveFromOptions<Options>,
598
+ >(key: indexerTypes.Ideable | indexerTypes.IdKey, options?: Options) {
480
599
  return (
481
600
  await this.getDetailed(
482
601
  key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key),
@@ -530,14 +649,26 @@ export class DocumentIndex<
530
649
  });
531
650
  }
532
651
 
533
- public async getDetailed(
652
+ public async getDetailed<
653
+ Options extends QueryOptions<T, D, Resolve>,
654
+ Resolve extends boolean | undefined = ExtractResolveFromOptions<Options>,
655
+ RT extends types.Result = Resolve extends true
656
+ ? types.ResultValue<T>
657
+ : types.ResultIndexedValue<I>,
658
+ >(
534
659
  key: indexerTypes.IdKey | indexerTypes.IdPrimitive,
535
- options?: QueryOptions<T, D>,
536
- ): Promise<types.Results<T>[] | undefined> {
537
- let results: types.Results<T>[] | undefined;
660
+ options?: QueryOptions<T, D, Resolve>,
661
+ ): Promise<types.Results<RT>[] | undefined> {
662
+ let results:
663
+ | types.Results<types.ResultValue<T> | types.ResultIndexedValue<I>>[]
664
+ | undefined;
665
+ const resolve = options?.resolve || options?.resolve == null;
666
+ let requestClazz = resolve
667
+ ? types.SearchRequest
668
+ : types.SearchRequestIndexed;
538
669
  if (key instanceof Uint8Array) {
539
- results = await this.queryDetailed(
540
- new types.SearchRequest({
670
+ results = await this.queryCommence(
671
+ new requestClazz({
541
672
  query: [
542
673
  new indexerTypes.ByteMatchQuery({ key: this.indexBy, value: key }),
543
674
  ],
@@ -551,8 +682,8 @@ export class DocumentIndex<
551
682
  typeof indexableKey === "number" ||
552
683
  typeof indexableKey === "bigint"
553
684
  ) {
554
- results = await this.queryDetailed(
555
- new types.SearchRequest({
685
+ results = await this.queryCommence(
686
+ new requestClazz({
556
687
  query: [
557
688
  new indexerTypes.IntegerCompare({
558
689
  key: this.indexBy,
@@ -564,8 +695,8 @@ export class DocumentIndex<
564
695
  options,
565
696
  );
566
697
  } else if (typeof indexableKey === "string") {
567
- results = await this.queryDetailed(
568
- new types.SearchRequest({
698
+ results = await this.queryCommence(
699
+ new requestClazz({
569
700
  query: [
570
701
  new indexerTypes.StringMatch({
571
702
  key: this.indexBy,
@@ -576,8 +707,8 @@ export class DocumentIndex<
576
707
  options,
577
708
  );
578
709
  } else if (indexableKey instanceof Uint8Array) {
579
- results = await this.queryDetailed(
580
- new types.SearchRequest({
710
+ results = await this.queryCommence(
711
+ new requestClazz({
581
712
  query: [
582
713
  new indexerTypes.ByteMatchQuery({
583
714
  key: this.indexBy,
@@ -590,19 +721,56 @@ export class DocumentIndex<
590
721
  }
591
722
  }
592
723
 
593
- return results;
724
+ if (
725
+ resolve &&
726
+ requestClazz === types.SearchRequestIndexed &&
727
+ !this.indexedTypeIsDocumentType &&
728
+ results
729
+ ) {
730
+ for (const set of results) {
731
+ let coercedResult: types.ResultValue<T>[] = [];
732
+
733
+ for (const value of set.results) {
734
+ const resolved =
735
+ value instanceof types.ResultIndexedValue
736
+ ? (
737
+ await this.resolveDocument({
738
+ indexed: value.value,
739
+ head: value.context.head,
740
+ })
741
+ )?.value
742
+ : value.value;
743
+ if (resolved) {
744
+ coercedResult.push(
745
+ new types.ResultValue({
746
+ context: value.context,
747
+ value: resolved,
748
+ }),
749
+ );
750
+ }
751
+ }
752
+ set.results = coercedResult;
753
+ }
754
+ }
755
+
756
+ return results as any as types.Results<RT>[];
594
757
  }
595
758
 
596
759
  getSize(): Promise<number> | number {
597
760
  return this.index.getSize();
598
761
  }
599
762
 
600
- private async resolveDocument(
601
- value: indexerTypes.IndexedResult<IDocumentWithContext<I>>,
602
- ): Promise<{ value: T } | undefined> {
763
+ private async resolveDocument(value: {
764
+ id?: indexerTypes.IdPrimitive;
765
+ indexed: I;
766
+ head: string;
767
+ }): Promise<{ value: T } | undefined> {
768
+ const id =
769
+ value.id ??
770
+ indexerTypes.toId(this.indexByResolver(value.indexed)).primitive;
771
+
603
772
  const cached =
604
- this._resolverCache.get(value.id.primitive) ||
605
- this._resolverProgramCache?.get(value.id.primitive);
773
+ this._resolverCache.get(id) || this._resolverProgramCache?.get(id);
606
774
  if (cached != null) {
607
775
  return { value: cached };
608
776
  }
@@ -611,13 +779,13 @@ export class DocumentIndex<
611
779
  // cast value to T, i.e. convert the class but keep all properties except the __context
612
780
  const obj = Object.assign(
613
781
  Object.create(this.documentType.prototype),
614
- value.value,
782
+ value.indexed,
615
783
  );
616
784
  delete obj.__context;
617
785
  return { value: obj as T };
618
786
  }
619
787
 
620
- const head = await this._log.log.get(value.value.__context.head);
788
+ const head = await this._log.log.get(value.head);
621
789
  if (!head) {
622
790
  return undefined; // we could end up here if we recently pruned the document and other peers never persisted the entry
623
791
  // TODO update changes in index before removing entries from log entry storage
@@ -636,14 +804,19 @@ export class DocumentIndex<
636
804
  );
637
805
  }
638
806
 
639
- async processQuery(
640
- query: types.SearchRequest | types.CollectNextRequest,
807
+ async processQuery<
808
+ R extends
809
+ | types.SearchRequest
810
+ | types.SearchRequestIndexed
811
+ | types.CollectNextRequest,
812
+ >(
813
+ query: R,
641
814
  from: PublicSignKey,
642
815
  isLocal: boolean,
643
816
  options?: {
644
- canRead?: CanRead<T>;
817
+ canRead?: CanRead<I>;
645
818
  },
646
- ): Promise<types.Results<T>> {
819
+ ): Promise<types.Results<types.ResultTypeFromRequest<R>>> {
647
820
  // We do special case for querying the id as we can do it faster than iterating
648
821
 
649
822
  let prevQueued = isLocal
@@ -656,9 +829,16 @@ export class DocumentIndex<
656
829
  let indexedResult:
657
830
  | indexerTypes.IndexedResults<IDocumentWithContext<I>>
658
831
  | undefined = undefined;
659
- if (query instanceof types.SearchRequest) {
832
+
833
+ let fromQuery: types.SearchRequest | types.SearchRequestIndexed | undefined;
834
+ if (
835
+ query instanceof types.SearchRequest ||
836
+ query instanceof types.SearchRequestIndexed
837
+ ) {
838
+ fromQuery = query;
660
839
  indexedResult = await this._resumableIterators.iterateAndFetch(query);
661
840
  } else if (query instanceof types.CollectNextRequest) {
841
+ fromQuery = this._resumableIterators.queues.get(query.idString)?.request;
662
842
  indexedResult =
663
843
  prevQueued?.keptInIndex === 0
664
844
  ? []
@@ -666,7 +846,7 @@ export class DocumentIndex<
666
846
  } else {
667
847
  throw new Error("Unsupported");
668
848
  }
669
- const filteredResults: types.ResultWithSource<T>[] = [];
849
+
670
850
  let resultSize = 0;
671
851
 
672
852
  let toIterate = prevQueued
@@ -693,6 +873,8 @@ export class DocumentIndex<
693
873
  this._resultQueue.set(query.idString, prevQueued);
694
874
  }
695
875
 
876
+ const filteredResults: types.Result[] = [];
877
+
696
878
  for (const result of toIterate) {
697
879
  if (!isLocal) {
698
880
  resultSize += result.value.__context.size;
@@ -701,25 +883,55 @@ export class DocumentIndex<
701
883
  continue;
702
884
  }
703
885
  }
886
+ const indexedUnwrapped = Object.assign(
887
+ Object.create(this.indexedType.prototype),
888
+ result.value,
889
+ );
704
890
 
705
- const value = await this.resolveDocument(result);
706
891
  if (
707
- !value ||
708
- (options?.canRead && !(await options.canRead(value.value, from)))
892
+ options?.canRead &&
893
+ !(await options.canRead(indexedUnwrapped, from))
709
894
  ) {
710
895
  continue;
711
896
  }
712
-
713
- filteredResults.push(
714
- new types.ResultWithSource({
715
- context: result.value.__context.toContext(),
716
- value: value.value,
717
- source: serialize(value.value),
897
+ if (fromQuery instanceof types.SearchRequest) {
898
+ const value = await this.resolveDocument({
718
899
  indexed: result.value,
719
- }),
720
- );
900
+ head: result.value.__context.head,
901
+ });
902
+
903
+ if (!value) {
904
+ continue;
905
+ }
906
+
907
+ filteredResults.push(
908
+ new types.ResultValue({
909
+ context: result.value.__context.toContext(),
910
+ value: value.value,
911
+ source: serialize(value.value),
912
+ indexed: indexedUnwrapped,
913
+ }),
914
+ );
915
+ } else if (fromQuery instanceof types.SearchRequestIndexed) {
916
+ const context = result.value.__context.toContext();
917
+ const head = await this._log.log.get(context.head);
918
+ // assume remote peer will start to replicate (TODO is this ideal?)
919
+ if (fromQuery.replicate) {
920
+ this._log.addPeersToGidPeerHistory(context.gid, [from.hashcode()]);
921
+ }
922
+
923
+ filteredResults.push(
924
+ new types.ResultIndexedValue({
925
+ context,
926
+ source: serialize(indexedUnwrapped),
927
+ indexed: indexedUnwrapped,
928
+ entries: head ? [head] : [],
929
+ }),
930
+ );
931
+ }
721
932
  }
722
- const results: types.Results<T> = new types.Results({
933
+
934
+ const results: types.Results<any> = new types.Results<any>({
723
935
  results: filteredResults,
724
936
  kept: BigInt(kept + (prevQueued?.queue.length || 0)),
725
937
  });
@@ -734,6 +946,7 @@ export class DocumentIndex<
734
946
  private clearResultsQueue(
735
947
  query:
736
948
  | types.SearchRequest
949
+ | types.SearchRequestIndexed
737
950
  | types.CollectNextRequest
738
951
  | types.CloseIteratorRequest,
739
952
  ) {
@@ -771,14 +984,18 @@ export class DocumentIndex<
771
984
  * @param options
772
985
  * @returns
773
986
  */
774
- public async queryDetailed(
775
- queryRequest: types.SearchRequest,
776
- options?: QueryDetailedOptions<T, D>,
777
- ): Promise<types.Results<T>[]> {
987
+ private async queryCommence<
988
+ R extends types.SearchRequest | types.SearchRequestIndexed,
989
+ RT extends types.Result = R extends types.SearchRequest
990
+ ? types.ResultValue<T>
991
+ : types.ResultIndexedValue<I>,
992
+ >(
993
+ queryRequest: R,
994
+ options?: QueryDetailedOptions<T, D, boolean | undefined>,
995
+ ): Promise<types.Results<RT>[]> {
778
996
  const local = typeof options?.local === "boolean" ? options?.local : true;
779
- let remote:
780
- | RemoteQueryOptions<types.AbstractSearchResult<T>, D>
781
- | undefined = undefined;
997
+ let remote: RemoteQueryOptions<types.AbstractSearchResult, D> | undefined =
998
+ undefined;
782
999
  if (typeof options?.remote === "boolean") {
783
1000
  if (options?.remote) {
784
1001
  remote = {};
@@ -800,7 +1017,7 @@ export class DocumentIndex<
800
1017
  "Expecting either 'options.remote' or 'options.local' to be true",
801
1018
  );
802
1019
  }
803
- const allResults: types.Results<T>[] = [];
1020
+ const allResults: types.Results<types.ResultTypeFromRequest<R>>[] = [];
804
1021
 
805
1022
  if (local) {
806
1023
  const results = await this.processQuery(
@@ -815,7 +1032,7 @@ export class DocumentIndex<
815
1032
  }
816
1033
  }
817
1034
 
818
- let resolved: types.Results<T>[] = [];
1035
+ let resolved: types.Results<types.ResultTypeFromRequest<R>>[] = [];
819
1036
  if (remote) {
820
1037
  const replicatorGroups = await this._log.getCover(
821
1038
  remote.domain ?? (undefined as any),
@@ -828,15 +1045,17 @@ export class DocumentIndex<
828
1045
  if (replicatorGroups) {
829
1046
  const groupHashes: string[][] = replicatorGroups.map((x) => [x]);
830
1047
  const responseHandler = async (
831
- results: RPCResponse<types.AbstractSearchResult<T>>[],
1048
+ results: RPCResponse<types.AbstractSearchResult>[],
832
1049
  ) => {
833
- for (const r of await introduceEntries(
1050
+ const resultInitialized = await introduceEntries(
834
1051
  queryRequest,
835
1052
  results,
836
1053
  this.documentType,
1054
+ this.indexedType,
837
1055
  this._sync,
838
1056
  options,
839
- )) {
1057
+ );
1058
+ for (const r of resultInitialized) {
840
1059
  resolved.push(r.response);
841
1060
  }
842
1061
  };
@@ -884,58 +1103,129 @@ export class DocumentIndex<
884
1103
  }
885
1104
  }
886
1105
  }
887
- return allResults;
1106
+ return allResults as any; // TODO types
888
1107
  }
889
1108
 
1109
+ public search(
1110
+ queryRequest: QueryLike,
1111
+ options?: SearchOptions<T, D, true>,
1112
+ ): Promise<ValueTypeFromRequest<true, T, I>[]>;
1113
+ public search(
1114
+ queryRequest: QueryLike,
1115
+ options?: SearchOptions<T, D, false>,
1116
+ ): Promise<ValueTypeFromRequest<false, T, I>[]>;
1117
+
890
1118
  /**
891
1119
  * Query and retrieve results
892
1120
  * @param queryRequest
893
1121
  * @param options
894
1122
  * @returns
895
1123
  */
896
- public async search(
897
- queryRequest: types.SearchRequest,
898
- options?: SearchOptions<T, D>,
899
- ): Promise<T[]> {
1124
+ public async search<
1125
+ R extends types.SearchRequest | types.SearchRequestIndexed | QueryLike,
1126
+ O extends SearchOptions<T, D, Resolve>,
1127
+ Resolve extends boolean | undefined = ExtractResolveFromOptions<O>,
1128
+ >(
1129
+ queryRequest: R,
1130
+ options?: SearchOptions<T, D, Resolve>,
1131
+ ): Promise<ValueTypeFromRequest<Resolve, T, I>[]> {
900
1132
  // Set fetch to search size, or max value (default to max u32 (4294967295))
901
- queryRequest.fetch = queryRequest.fetch ?? 0xffffffff;
1133
+ const coercedRequest: types.SearchRequest | types.SearchRequestIndexed =
1134
+ coerceQuery(queryRequest, options);
1135
+ coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
902
1136
 
903
1137
  // So that the iterator is pre-fetching the right amount of entries
904
- const iterator = this.iterate(queryRequest, options);
1138
+ const iterator = this.iterate(coercedRequest, options);
905
1139
 
906
1140
  // So that this call will not do any remote requests
907
- const allResults: T[] = [];
908
- while (iterator.done() !== true && queryRequest.fetch > allResults.length) {
1141
+ const allResults: ValueTypeFromRequest<Resolve, T, I>[] = [];
1142
+
1143
+ while (
1144
+ iterator.done() !== true &&
1145
+ coercedRequest.fetch > allResults.length
1146
+ ) {
909
1147
  // We might need to pull .next multiple time due to data message size limitations
1148
+
910
1149
  for (const result of await iterator.next(
911
- queryRequest.fetch - allResults.length,
1150
+ coercedRequest.fetch - allResults.length,
912
1151
  )) {
913
- allResults.push(result);
1152
+ allResults.push(result as ValueTypeFromRequest<Resolve, T, I>);
914
1153
  }
915
1154
  }
916
1155
 
917
1156
  await iterator.close();
918
1157
 
919
- //s Deduplicate and return values directly
1158
+ // Deduplicate and return values directly
920
1159
  return dedup(allResults, this.indexByResolver);
921
1160
  }
922
1161
 
1162
+ public iterate(
1163
+ query: QueryLike,
1164
+ options?: QueryOptions<T, D, undefined>,
1165
+ ): ResultsIterator<ValueTypeFromRequest<true, T, I>>;
1166
+ public iterate<Resolve extends boolean>(
1167
+ query: QueryLike,
1168
+ options?: QueryOptions<T, D, Resolve>,
1169
+ ): ResultsIterator<ValueTypeFromRequest<Resolve, T, I>>;
1170
+
923
1171
  /**
924
1172
  * Query and retrieve documents in a iterator
925
1173
  * @param queryRequest
926
1174
  * @param options
927
1175
  * @returns
928
1176
  */
929
- public iterate(
930
- queryRequest: types.SearchRequest,
931
- options?: QueryOptions<T, D>,
932
- ): ResultsIterator<T> {
1177
+ public iterate<
1178
+ R extends types.SearchRequest | types.SearchRequestIndexed | QueryLike,
1179
+ O extends SearchOptions<T, D, Resolve>,
1180
+ Resolve extends boolean | undefined = ExtractResolveFromOptions<O>,
1181
+ >(
1182
+ queryRequest: R,
1183
+ options?: QueryOptions<T, D, Resolve>,
1184
+ ): ResultsIterator<ValueTypeFromRequest<Resolve, T, I>> {
1185
+ let queryRequestCoerced: types.SearchRequest | types.SearchRequestIndexed =
1186
+ coerceQuery(queryRequest, options);
1187
+
1188
+ let resolve = false;
1189
+ if (
1190
+ options?.remote &&
1191
+ typeof options.remote !== "boolean" &&
1192
+ options.remote.replicate &&
1193
+ options?.resolve !== false
1194
+ ) {
1195
+ if (
1196
+ (queryRequest instanceof types.SearchRequestIndexed === false &&
1197
+ this.compatibility == null) ||
1198
+ (this.compatibility != null && this.compatibility > 8)
1199
+ ) {
1200
+ queryRequestCoerced = new types.SearchRequestIndexed({
1201
+ query: queryRequestCoerced.query,
1202
+ fetch: queryRequestCoerced.fetch,
1203
+ sort: queryRequestCoerced.sort,
1204
+ });
1205
+ resolve = true;
1206
+ }
1207
+ }
1208
+
1209
+ let replicate =
1210
+ options?.remote &&
1211
+ typeof options.remote !== "boolean" &&
1212
+ options.remote.replicate;
1213
+ if (
1214
+ replicate &&
1215
+ queryRequestCoerced instanceof types.SearchRequestIndexed
1216
+ ) {
1217
+ queryRequestCoerced.replicate = true;
1218
+ }
1219
+
933
1220
  let fetchPromise: Promise<any> | undefined = undefined;
934
1221
  const peerBufferMap: Map<
935
1222
  string,
936
1223
  {
937
1224
  kept: number;
938
- buffer: BufferedResult<T>[];
1225
+ buffer: BufferedResult<
1226
+ types.ResultValue<T> | types.ResultIndexedValue<I>,
1227
+ I
1228
+ >[];
939
1229
  }
940
1230
  > = new Map();
941
1231
  const visited = new Set<string | number | bigint>();
@@ -947,8 +1237,8 @@ export class DocumentIndex<
947
1237
  const controller = new AbortController();
948
1238
 
949
1239
  const peerBuffers = (): {
950
- indexed: Record<string, any>;
951
- value: T;
1240
+ indexed: I;
1241
+ value: types.ResultValue<T> | types.ResultIndexedValue<I>;
952
1242
  from: PublicSignKey;
953
1243
  context: types.Context;
954
1244
  }[] => {
@@ -957,8 +1247,8 @@ export class DocumentIndex<
957
1247
 
958
1248
  const fetchFirst = async (n: number): Promise<boolean> => {
959
1249
  done = true; // Assume we are donne
960
- queryRequest.fetch = n;
961
- await this.queryDetailed(queryRequest, {
1250
+ queryRequestCoerced.fetch = n;
1251
+ await this.queryCommence(queryRequestCoerced, {
962
1252
  ...options,
963
1253
  onResponse: async (response, from) => {
964
1254
  if (!from) {
@@ -969,7 +1259,9 @@ export class DocumentIndex<
969
1259
  logger.error("Dont have access");
970
1260
  return;
971
1261
  } else if (response instanceof types.Results) {
972
- const results = response as types.Results<T>;
1262
+ const results = response as types.Results<
1263
+ types.ResultTypeFromRequest<R>
1264
+ >;
973
1265
  if (results.kept === 0n && results.results.length === 0) {
974
1266
  return;
975
1267
  }
@@ -977,24 +1269,42 @@ export class DocumentIndex<
977
1269
  if (results.kept > 0n) {
978
1270
  done = false; // we have more to do later!
979
1271
  }
980
- const buffer: BufferedResult<T>[] = [];
1272
+ const buffer: BufferedResult<types.ResultTypeFromRequest<R>, I>[] =
1273
+ [];
981
1274
 
982
1275
  for (const result of results.results) {
983
- const indexKey = indexerTypes.toId(
984
- this.indexByResolver(result.value),
985
- ).primitive;
986
- if (visited.has(indexKey)) {
987
- continue;
1276
+ if (result instanceof types.ResultValue) {
1277
+ const indexKey = indexerTypes.toId(
1278
+ this.indexByResolver(result.value),
1279
+ ).primitive;
1280
+ if (visited.has(indexKey)) {
1281
+ continue;
1282
+ }
1283
+ visited.add(indexKey);
1284
+ buffer.push({
1285
+ value: result.value,
1286
+ context: result.context,
1287
+ from,
1288
+ indexed:
1289
+ (result.indexed as I) ||
1290
+ (await this.transformer(result.value, result.context)),
1291
+ });
1292
+ } else {
1293
+ const indexKey = indexerTypes.toId(
1294
+ this.indexByResolver(result.value),
1295
+ ).primitive;
1296
+
1297
+ if (visited.has(indexKey)) {
1298
+ continue;
1299
+ }
1300
+ visited.add(indexKey);
1301
+ buffer.push({
1302
+ value: result.value,
1303
+ context: result.context,
1304
+ from,
1305
+ indexed: result.indexed || result.value,
1306
+ });
988
1307
  }
989
- visited.add(indexKey);
990
- buffer.push({
991
- value: result.value,
992
- context: result.context,
993
- from,
994
- indexed:
995
- result.indexed ||
996
- (await this.transformer(result.value, result.context)),
997
- });
998
1308
  }
999
1309
 
1000
1310
  peerBufferMap.set(from.hashcode(), {
@@ -1010,7 +1320,7 @@ export class DocumentIndex<
1010
1320
  });
1011
1321
 
1012
1322
  if (done) {
1013
- this.clearResultsQueue(queryRequest);
1323
+ this.clearResultsQueue(queryRequestCoerced);
1014
1324
  }
1015
1325
 
1016
1326
  return done;
@@ -1048,7 +1358,7 @@ export class DocumentIndex<
1048
1358
  // TODO buffer more than deleted?
1049
1359
  // TODO batch to multiple 'to's
1050
1360
  const collectRequest = new types.CollectNextRequest({
1051
- id: queryRequest.id,
1361
+ id: queryRequestCoerced.id,
1052
1362
  amount: n - buffer.buffer.length,
1053
1363
  });
1054
1364
  // Fetch locally?
@@ -1119,57 +1429,57 @@ export class DocumentIndex<
1119
1429
  })
1120
1430
  .then((response) =>
1121
1431
  introduceEntries(
1122
- queryRequest,
1432
+ queryRequestCoerced,
1123
1433
  response,
1124
1434
  this.documentType,
1435
+ this.indexedType,
1125
1436
  this._sync,
1126
1437
  options,
1127
1438
  )
1128
- .then((responses) => {
1129
- responses.map((response) => {
1130
- resultsLeft += Number(response.response.kept);
1131
- if (!response.from) {
1132
- logger.error("Missing from for sorted query");
1133
- return;
1134
- }
1135
-
1136
- if (response.response.results.length === 0) {
1137
- if (peerBufferMap.get(peer)?.buffer.length === 0) {
1138
- peerBufferMap.delete(peer); // No more results
1139
- }
1140
- } else {
1141
- const peerBuffer = peerBufferMap.get(peer);
1142
- if (!peerBuffer) {
1439
+ .then(async (responses) => {
1440
+ return Promise.all(
1441
+ responses.map(async (response, i) => {
1442
+ resultsLeft += Number(response.response.kept);
1443
+ const from = responses[i].from;
1444
+ if (!from) {
1445
+ logger.error("Missing from for sorted query");
1143
1446
  return;
1144
1447
  }
1145
- peerBuffer.kept = Number(response.response.kept);
1146
- for (const result of response.response.results) {
1147
- if (
1148
- visited.has(
1149
- indexerTypes.toId(
1150
- this.indexByResolver(result.value),
1151
- ).primitive,
1152
- )
1153
- ) {
1154
- continue;
1448
+
1449
+ if (response.response.results.length === 0) {
1450
+ if (peerBufferMap.get(peer)?.buffer.length === 0) {
1451
+ peerBufferMap.delete(peer); // No more results
1452
+ }
1453
+ } else {
1454
+ const peerBuffer = peerBufferMap.get(peer);
1455
+ if (!peerBuffer) {
1456
+ return;
1155
1457
  }
1156
- visited.add(
1157
- indexerTypes.toId(
1458
+ peerBuffer.kept = Number(response.response.kept);
1459
+ for (const result of response.response.results) {
1460
+ const idPrimitive = indexerTypes.toId(
1158
1461
  this.indexByResolver(result.value),
1159
- ).primitive,
1160
- );
1161
- peerBuffer.buffer.push({
1162
- value: result.value,
1163
- context: result.context,
1164
- from: response.from!,
1165
- indexed: this.transformer(
1166
- result.value,
1167
- result.context,
1168
- ),
1169
- });
1462
+ ).primitive;
1463
+ if (visited.has(idPrimitive)) {
1464
+ continue;
1465
+ }
1466
+ visited.add(idPrimitive);
1467
+ peerBuffer.buffer.push({
1468
+ value: result.value,
1469
+ context: result.context,
1470
+ from: from!,
1471
+ indexed:
1472
+ result instanceof types.ResultIndexedValue
1473
+ ? result.value
1474
+ : await this.transformer(
1475
+ result.value,
1476
+ result.context,
1477
+ ),
1478
+ });
1479
+ }
1170
1480
  }
1171
- }
1172
- });
1481
+ }),
1482
+ );
1173
1483
  })
1174
1484
  .catch((e) => {
1175
1485
  logger.error(
@@ -1210,7 +1520,7 @@ export class DocumentIndex<
1210
1520
  indexerTypes.extractSortCompare(
1211
1521
  a.indexed,
1212
1522
  b.indexed,
1213
- queryRequest.sort,
1523
+ queryRequestCoerced.sort,
1214
1524
  ),
1215
1525
  );
1216
1526
 
@@ -1231,18 +1541,38 @@ export class DocumentIndex<
1231
1541
  }
1232
1542
 
1233
1543
  done = fetchedAll && !pendingMoreResults;
1544
+ let coercedBatch: ValueTypeFromRequest<Resolve, T, I>[];
1545
+ if (resolve) {
1546
+ coercedBatch = (
1547
+ await Promise.all(
1548
+ batch.map(async (x) =>
1549
+ x.value instanceof this.documentType
1550
+ ? x.value
1551
+ : (
1552
+ await this.resolveDocument({
1553
+ head: x.context.head,
1554
+ indexed: x.indexed,
1555
+ })
1556
+ )?.value,
1557
+ ),
1558
+ )
1559
+ ).filter((x) => !!x) as ValueTypeFromRequest<Resolve, T, I>[];
1560
+ } else {
1561
+ coercedBatch = batch.map((x) => x.value) as ValueTypeFromRequest<
1562
+ Resolve,
1563
+ T,
1564
+ I
1565
+ >[];
1566
+ }
1234
1567
 
1235
- return dedup(
1236
- batch.map((x) => x.value),
1237
- this.indexByResolver,
1238
- );
1568
+ return dedup(coercedBatch, this.indexByResolver);
1239
1569
  };
1240
1570
 
1241
1571
  const close = async () => {
1242
1572
  controller.abort(new AbortError("Iterator closed"));
1243
1573
 
1244
1574
  const closeRequest = new types.CloseIteratorRequest({
1245
- id: queryRequest.id,
1575
+ id: queryRequestCoerced.id,
1246
1576
  });
1247
1577
  const promises: Promise<any>[] = [];
1248
1578
  for (const [peer, buffer] of peerBufferMap) {
@@ -1276,7 +1606,7 @@ export class DocumentIndex<
1276
1606
  next,
1277
1607
  done: doneFn,
1278
1608
  all: async () => {
1279
- let result: T[] = [];
1609
+ let result: ValueTypeFromRequest<Resolve, T, I>[] = [];
1280
1610
  while (doneFn() !== true) {
1281
1611
  let batch = await next(100);
1282
1612
  result.push(...batch);
@@ -1285,4 +1615,34 @@ export class DocumentIndex<
1285
1615
  },
1286
1616
  };
1287
1617
  }
1618
+
1619
+ public async waitFor(
1620
+ other:
1621
+ | PublicSignKey
1622
+ | PeerId
1623
+ | string
1624
+ | (PublicSignKey | string | PeerId)[],
1625
+ options?: { signal?: AbortSignal; timeout?: number },
1626
+ ): Promise<void> {
1627
+ await super.waitFor(other, options);
1628
+ const ids = Array.isArray(other) ? other : [other];
1629
+ const expectedHashes = new Set(
1630
+ ids.map((x) =>
1631
+ typeof x === "string"
1632
+ ? x
1633
+ : x instanceof PublicSignKey
1634
+ ? x.hashcode()
1635
+ : getPublicKeyFromPeerId(x).hashcode(),
1636
+ ),
1637
+ );
1638
+
1639
+ for (const key of expectedHashes) {
1640
+ await waitFor(
1641
+ async () =>
1642
+ (await this._log.replicationIndex.count({ query: { hash: key } })) >
1643
+ 0,
1644
+ options,
1645
+ );
1646
+ }
1647
+ }
1288
1648
  }