@peerbit/document 8.1.2 → 8.2.0-a4ac71a

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,8 +1,10 @@
1
1
  import { type AbstractType, field, serialize, variant } from "@dao-xyz/borsh";
2
+ import type { PeerId } from "@libp2p/interface";
2
3
  import { Cache } from "@peerbit/cache";
3
4
  import {
4
5
  type MaybePromise,
5
6
  PublicSignKey,
7
+ getPublicKeyFromPeerId,
6
8
  sha256Base64Sync,
7
9
  } from "@peerbit/crypto";
8
10
  import * as types from "@peerbit/document-interface";
@@ -20,7 +22,7 @@ import {
20
22
  } from "@peerbit/rpc";
21
23
  import { type ReplicationDomain, SharedLog } from "@peerbit/shared-log";
22
24
  import { SilentDelivery } from "@peerbit/stream-interface";
23
- import { AbortError } from "@peerbit/time";
25
+ import { AbortError, waitFor } from "@peerbit/time";
24
26
  import { concat, fromString } from "uint8arrays";
25
27
  import { copySerialization } from "./borsh.js";
26
28
  import { MAX_BATCH_SIZE } from "./constants.js";
@@ -30,9 +32,9 @@ import { ResumableIterators } from "./resumable-iterator.js";
30
32
 
31
33
  const logger = loggerFn({ module: "document-index" });
32
34
 
33
- type BufferedResult<T> = {
35
+ type BufferedResult<T, I extends Record<string, any>> = {
34
36
  value: T;
35
- indexed: Record<string, any>;
37
+ indexed: I;
36
38
  context: types.Context;
37
39
  from: PublicSignKey;
38
40
  };
@@ -44,11 +46,16 @@ export type RemoteQueryOptions<R, D> = RPCRequestAllOptions<R> & {
44
46
  domain?: ExtractArgs<D>;
45
47
  eager?: boolean; // whether to query newly joined peers before they have matured
46
48
  };
47
- export type QueryOptions<R, D> = {
48
- remote?: boolean | RemoteQueryOptions<types.AbstractSearchResult<R>, D>;
49
+ export type QueryOptions<R, D, Resolve extends boolean | undefined> = {
50
+ remote?: boolean | RemoteQueryOptions<types.AbstractSearchResult, D>;
49
51
  local?: boolean;
52
+ resolve?: Resolve;
50
53
  };
51
- 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>;
52
59
 
53
60
  type Transformer<T, I> = (obj: T, context: types.Context) => MaybePromise<I>;
54
61
 
@@ -59,37 +66,104 @@ export type ResultsIterator<T> = {
59
66
  all: () => Promise<T[]>;
60
67
  };
61
68
 
62
- type QueryDetailedOptions<T, D> = QueryOptions<T, D> & {
69
+ type QueryDetailedOptions<
70
+ T,
71
+ D,
72
+ Resolve extends boolean | undefined,
73
+ > = QueryOptions<T, D, Resolve> & {
63
74
  onResponse?: (
64
- response: types.AbstractSearchResult<T>,
75
+ response: types.AbstractSearchResult,
65
76
  from: PublicSignKey,
66
77
  ) => void | Promise<void>;
67
78
  };
68
79
 
69
- const introduceEntries = async <T, D>(
70
- queryRequest: types.SearchRequest,
71
- responses: RPCResponse<types.AbstractSearchResult<T>>[],
72
- 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>,
73
143
  sync: (
74
- queryRequest: types.SearchRequest,
75
- result: types.Results<T>,
144
+ request: types.SearchRequest | types.SearchRequestIndexed,
145
+ response: types.Results<any>,
76
146
  ) => Promise<void>,
77
- options?: QueryDetailedOptions<T, D>,
78
- ): Promise<RPCResponse<types.Results<T>>[]> => {
79
- 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>>[] = [];
80
150
  for (const response of responses) {
81
151
  if (!response.from) {
82
152
  logger.error("Missing from for response");
83
153
  }
84
154
 
85
155
  if (response.response instanceof types.Results) {
86
- 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
+ );
87
161
  if (typeof options?.remote !== "boolean" && options?.remote?.replicate) {
88
162
  await sync(queryRequest, response.response);
89
163
  }
90
164
  options?.onResponse &&
91
165
  (await options.onResponse(response.response, response.from!)); // TODO fix types
92
- results.push(response as RPCResponse<types.Results<T>>);
166
+ results.push(response as RPCResponse<types.Results<any>>);
93
167
  } else if (response.response instanceof types.NoAccess) {
94
168
  logger.error("Search resulted in access error");
95
169
  } else {
@@ -134,6 +208,17 @@ export type CanRead<T> = (
134
208
  from: PublicSignKey,
135
209
  ) => Promise<boolean> | boolean;
136
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
+
137
222
  @variant(0)
138
223
  export class IndexableContext implements types.Context {
139
224
  @field({ type: "u64" })
@@ -205,14 +290,19 @@ export type OpenOptions<
205
290
  documentType: AbstractType<T>;
206
291
  dbType: AbstractType<types.IDocumentStore<T>>;
207
292
  log: SharedLog<Operation, D, any>;
208
- canRead?: CanRead<T>;
293
+ canRead?: CanRead<I>;
209
294
  canSearch?: CanSearch;
210
- sync: (
211
- request: types.SearchRequest,
212
- 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
+ >,
213
302
  ) => Promise<void>;
214
303
  indexBy?: string | string[];
215
304
  transform?: TransformOptions<T, I>;
305
+ compatibility: 6 | 7 | 8 | undefined;
216
306
  };
217
307
 
218
308
  type IndexableClass<I> = new (
@@ -227,7 +317,7 @@ export class DocumentIndex<
227
317
  D extends ReplicationDomain<any, Operation, any>,
228
318
  > extends Program<OpenOptions<T, I, D>> {
229
319
  @field({ type: RPC })
230
- _query: RPC<types.AbstractSearchRequest, types.AbstractSearchResult<T>>;
320
+ _query: RPC<types.AbstractSearchRequest, types.AbstractSearchResult>;
231
321
 
232
322
  // Original document representation
233
323
  documentType: AbstractType<T>;
@@ -237,6 +327,7 @@ export class DocumentIndex<
237
327
 
238
328
  // The indexed document wrapped in a context
239
329
  wrappedIndexedType: IndexableClass<I>;
330
+ indexedType: AbstractType<I>;
240
331
 
241
332
  // The database type, for recursive indexing
242
333
  dbType: AbstractType<types.IDocumentStore<T>>;
@@ -248,14 +339,16 @@ export class DocumentIndex<
248
339
  index: indexerTypes.Index<IDocumentWithContext<I>>;
249
340
  private _resumableIterators: ResumableIterators<IDocumentWithContext<I>>;
250
341
 
342
+ compatibility: 6 | 7 | 8 | undefined;
343
+
251
344
  // Transformation, indexer
252
345
  /* fields: IndexableFields<T, I>; */
253
346
 
254
347
  private _valueEncoding: Encoding<T>;
255
348
 
256
- private _sync: (
257
- reques: types.SearchRequest,
258
- 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>,
259
352
  ) => Promise<void>;
260
353
 
261
354
  private _log: SharedLog<Operation, D, any>;
@@ -275,7 +368,7 @@ export class DocumentIndex<
275
368
  >;
276
369
 
277
370
  constructor(properties?: {
278
- query?: RPC<types.AbstractSearchRequest, types.AbstractSearchResult<T>>;
371
+ query?: RPC<types.AbstractSearchRequest, types.AbstractSearchResult>;
279
372
  }) {
280
373
  super();
281
374
  this._query = properties?.query || new RPC();
@@ -293,6 +386,8 @@ export class DocumentIndex<
293
386
  !properties.transform?.type ||
294
387
  properties.transform?.type === properties.documentType;
295
388
 
389
+ this.compatibility = properties.compatibility;
390
+
296
391
  @variant(0)
297
392
  class IndexedClassWithContext {
298
393
  @field({ type: IndexableContext })
@@ -305,10 +400,8 @@ export class DocumentIndex<
305
400
  }
306
401
 
307
402
  // copy all prototype values from indexedType to IndexedClassWithContext
308
- copySerialization(
309
- (properties.transform?.type || properties.documentType)!,
310
- IndexedClassWithContext,
311
- );
403
+ this.indexedType = (properties.transform?.type || properties.documentType)!;
404
+ copySerialization(this.indexedType, IndexedClassWithContext);
312
405
 
313
406
  this.wrappedIndexedType = IndexedClassWithContext as new (
314
407
  value: I,
@@ -319,7 +412,56 @@ export class DocumentIndex<
319
412
  this._isProgramValues = this.documentType instanceof Program;
320
413
  this.dbType = properties.dbType;
321
414
  this._resultQueue = new Map();
322
- 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
+ };
323
465
 
324
466
  const transformOptions = properties.transform;
325
467
  this.transformer = transformOptions
@@ -391,7 +533,7 @@ export class DocumentIndex<
391
533
  const results = await this.processQuery(
392
534
  query as
393
535
  | types.SearchRequest
394
- | types.SearchRequest
536
+ | types.SearchRequestIndexed
395
537
  | types.CollectNextRequest,
396
538
  ctx.from,
397
539
  false,
@@ -440,10 +582,20 @@ export class DocumentIndex<
440
582
  return dropped;
441
583
  }
442
584
 
443
- public async get(
585
+ public async get<Options extends QueryOptions<T, D, true | undefined>>(
444
586
  key: indexerTypes.Ideable | indexerTypes.IdKey,
445
- options?: QueryOptions<T, D>,
446
- ): 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) {
447
599
  return (
448
600
  await this.getDetailed(
449
601
  key instanceof indexerTypes.IdKey ? key : indexerTypes.toId(key),
@@ -497,14 +649,26 @@ export class DocumentIndex<
497
649
  });
498
650
  }
499
651
 
500
- 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
+ >(
501
659
  key: indexerTypes.IdKey | indexerTypes.IdPrimitive,
502
- options?: QueryOptions<T, D>,
503
- ): Promise<types.Results<T>[] | undefined> {
504
- 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;
505
669
  if (key instanceof Uint8Array) {
506
- results = await this.queryDetailed(
507
- new types.SearchRequest({
670
+ results = await this.queryCommence(
671
+ new requestClazz({
508
672
  query: [
509
673
  new indexerTypes.ByteMatchQuery({ key: this.indexBy, value: key }),
510
674
  ],
@@ -518,8 +682,8 @@ export class DocumentIndex<
518
682
  typeof indexableKey === "number" ||
519
683
  typeof indexableKey === "bigint"
520
684
  ) {
521
- results = await this.queryDetailed(
522
- new types.SearchRequest({
685
+ results = await this.queryCommence(
686
+ new requestClazz({
523
687
  query: [
524
688
  new indexerTypes.IntegerCompare({
525
689
  key: this.indexBy,
@@ -531,8 +695,8 @@ export class DocumentIndex<
531
695
  options,
532
696
  );
533
697
  } else if (typeof indexableKey === "string") {
534
- results = await this.queryDetailed(
535
- new types.SearchRequest({
698
+ results = await this.queryCommence(
699
+ new requestClazz({
536
700
  query: [
537
701
  new indexerTypes.StringMatch({
538
702
  key: this.indexBy,
@@ -543,8 +707,8 @@ export class DocumentIndex<
543
707
  options,
544
708
  );
545
709
  } else if (indexableKey instanceof Uint8Array) {
546
- results = await this.queryDetailed(
547
- new types.SearchRequest({
710
+ results = await this.queryCommence(
711
+ new requestClazz({
548
712
  query: [
549
713
  new indexerTypes.ByteMatchQuery({
550
714
  key: this.indexBy,
@@ -557,19 +721,56 @@ export class DocumentIndex<
557
721
  }
558
722
  }
559
723
 
560
- 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>[];
561
757
  }
562
758
 
563
759
  getSize(): Promise<number> | number {
564
760
  return this.index.getSize();
565
761
  }
566
762
 
567
- private async resolveDocument(
568
- value: indexerTypes.IndexedResult<IDocumentWithContext<I>>,
569
- ): 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
+
570
772
  const cached =
571
- this._resolverCache.get(value.id.primitive) ||
572
- this._resolverProgramCache?.get(value.id.primitive);
773
+ this._resolverCache.get(id) || this._resolverProgramCache?.get(id);
573
774
  if (cached != null) {
574
775
  return { value: cached };
575
776
  }
@@ -578,13 +779,13 @@ export class DocumentIndex<
578
779
  // cast value to T, i.e. convert the class but keep all properties except the __context
579
780
  const obj = Object.assign(
580
781
  Object.create(this.documentType.prototype),
581
- value.value,
782
+ value.indexed,
582
783
  );
583
784
  delete obj.__context;
584
785
  return { value: obj as T };
585
786
  }
586
787
 
587
- const head = await this._log.log.get(value.value.__context.head);
788
+ const head = await this._log.log.get(value.head);
588
789
  if (!head) {
589
790
  return undefined; // we could end up here if we recently pruned the document and other peers never persisted the entry
590
791
  // TODO update changes in index before removing entries from log entry storage
@@ -603,14 +804,19 @@ export class DocumentIndex<
603
804
  );
604
805
  }
605
806
 
606
- async processQuery(
607
- query: types.SearchRequest | types.CollectNextRequest,
807
+ async processQuery<
808
+ R extends
809
+ | types.SearchRequest
810
+ | types.SearchRequestIndexed
811
+ | types.CollectNextRequest,
812
+ >(
813
+ query: R,
608
814
  from: PublicSignKey,
609
815
  isLocal: boolean,
610
816
  options?: {
611
- canRead?: CanRead<T>;
817
+ canRead?: CanRead<I>;
612
818
  },
613
- ): Promise<types.Results<T>> {
819
+ ): Promise<types.Results<types.ResultTypeFromRequest<R>>> {
614
820
  // We do special case for querying the id as we can do it faster than iterating
615
821
 
616
822
  let prevQueued = isLocal
@@ -623,9 +829,16 @@ export class DocumentIndex<
623
829
  let indexedResult:
624
830
  | indexerTypes.IndexedResults<IDocumentWithContext<I>>
625
831
  | undefined = undefined;
626
- 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;
627
839
  indexedResult = await this._resumableIterators.iterateAndFetch(query);
628
840
  } else if (query instanceof types.CollectNextRequest) {
841
+ fromQuery = this._resumableIterators.queues.get(query.idString)?.request;
629
842
  indexedResult =
630
843
  prevQueued?.keptInIndex === 0
631
844
  ? []
@@ -633,7 +846,7 @@ export class DocumentIndex<
633
846
  } else {
634
847
  throw new Error("Unsupported");
635
848
  }
636
- const filteredResults: types.ResultWithSource<T>[] = [];
849
+
637
850
  let resultSize = 0;
638
851
 
639
852
  let toIterate = prevQueued
@@ -660,6 +873,8 @@ export class DocumentIndex<
660
873
  this._resultQueue.set(query.idString, prevQueued);
661
874
  }
662
875
 
876
+ const filteredResults: types.Result[] = [];
877
+
663
878
  for (const result of toIterate) {
664
879
  if (!isLocal) {
665
880
  resultSize += result.value.__context.size;
@@ -668,25 +883,55 @@ export class DocumentIndex<
668
883
  continue;
669
884
  }
670
885
  }
886
+ const indexedUnwrapped = Object.assign(
887
+ Object.create(this.indexedType.prototype),
888
+ result.value,
889
+ );
671
890
 
672
- const value = await this.resolveDocument(result);
673
891
  if (
674
- !value ||
675
- (options?.canRead && !(await options.canRead(value.value, from)))
892
+ options?.canRead &&
893
+ !(await options.canRead(indexedUnwrapped, from))
676
894
  ) {
677
895
  continue;
678
896
  }
679
-
680
- filteredResults.push(
681
- new types.ResultWithSource({
682
- context: result.value.__context.toContext(),
683
- value: value.value,
684
- source: serialize(value.value),
897
+ if (fromQuery instanceof types.SearchRequest) {
898
+ const value = await this.resolveDocument({
685
899
  indexed: result.value,
686
- }),
687
- );
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
+ }
688
932
  }
689
- const results: types.Results<T> = new types.Results({
933
+
934
+ const results: types.Results<any> = new types.Results<any>({
690
935
  results: filteredResults,
691
936
  kept: BigInt(kept + (prevQueued?.queue.length || 0)),
692
937
  });
@@ -701,6 +946,7 @@ export class DocumentIndex<
701
946
  private clearResultsQueue(
702
947
  query:
703
948
  | types.SearchRequest
949
+ | types.SearchRequestIndexed
704
950
  | types.CollectNextRequest
705
951
  | types.CloseIteratorRequest,
706
952
  ) {
@@ -738,14 +984,18 @@ export class DocumentIndex<
738
984
  * @param options
739
985
  * @returns
740
986
  */
741
- public async queryDetailed(
742
- queryRequest: types.SearchRequest,
743
- options?: QueryDetailedOptions<T, D>,
744
- ): 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>[]> {
745
996
  const local = typeof options?.local === "boolean" ? options?.local : true;
746
- let remote:
747
- | RemoteQueryOptions<types.AbstractSearchResult<T>, D>
748
- | undefined = undefined;
997
+ let remote: RemoteQueryOptions<types.AbstractSearchResult, D> | undefined =
998
+ undefined;
749
999
  if (typeof options?.remote === "boolean") {
750
1000
  if (options?.remote) {
751
1001
  remote = {};
@@ -767,7 +1017,7 @@ export class DocumentIndex<
767
1017
  "Expecting either 'options.remote' or 'options.local' to be true",
768
1018
  );
769
1019
  }
770
- const allResults: types.Results<T>[] = [];
1020
+ const allResults: types.Results<types.ResultTypeFromRequest<R>>[] = [];
771
1021
 
772
1022
  if (local) {
773
1023
  const results = await this.processQuery(
@@ -782,7 +1032,7 @@ export class DocumentIndex<
782
1032
  }
783
1033
  }
784
1034
 
785
- let resolved: types.Results<T>[] = [];
1035
+ let resolved: types.Results<types.ResultTypeFromRequest<R>>[] = [];
786
1036
  if (remote) {
787
1037
  const replicatorGroups = await this._log.getCover(
788
1038
  remote.domain ?? (undefined as any),
@@ -795,15 +1045,17 @@ export class DocumentIndex<
795
1045
  if (replicatorGroups) {
796
1046
  const groupHashes: string[][] = replicatorGroups.map((x) => [x]);
797
1047
  const responseHandler = async (
798
- results: RPCResponse<types.AbstractSearchResult<T>>[],
1048
+ results: RPCResponse<types.AbstractSearchResult>[],
799
1049
  ) => {
800
- for (const r of await introduceEntries(
1050
+ const resultInitialized = await introduceEntries(
801
1051
  queryRequest,
802
1052
  results,
803
1053
  this.documentType,
1054
+ this.indexedType,
804
1055
  this._sync,
805
1056
  options,
806
- )) {
1057
+ );
1058
+ for (const r of resultInitialized) {
807
1059
  resolved.push(r.response);
808
1060
  }
809
1061
  };
@@ -851,58 +1103,129 @@ export class DocumentIndex<
851
1103
  }
852
1104
  }
853
1105
  }
854
- return allResults;
1106
+ return allResults as any; // TODO types
855
1107
  }
856
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
+
857
1118
  /**
858
1119
  * Query and retrieve results
859
1120
  * @param queryRequest
860
1121
  * @param options
861
1122
  * @returns
862
1123
  */
863
- public async search(
864
- queryRequest: types.SearchRequest,
865
- options?: SearchOptions<T, D>,
866
- ): 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>[]> {
867
1132
  // Set fetch to search size, or max value (default to max u32 (4294967295))
868
- queryRequest.fetch = queryRequest.fetch ?? 0xffffffff;
1133
+ const coercedRequest: types.SearchRequest | types.SearchRequestIndexed =
1134
+ coerceQuery(queryRequest, options);
1135
+ coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
869
1136
 
870
1137
  // So that the iterator is pre-fetching the right amount of entries
871
- const iterator = this.iterate(queryRequest, options);
1138
+ const iterator = this.iterate(coercedRequest, options);
872
1139
 
873
1140
  // So that this call will not do any remote requests
874
- const allResults: T[] = [];
875
- 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
+ ) {
876
1147
  // We might need to pull .next multiple time due to data message size limitations
1148
+
877
1149
  for (const result of await iterator.next(
878
- queryRequest.fetch - allResults.length,
1150
+ coercedRequest.fetch - allResults.length,
879
1151
  )) {
880
- allResults.push(result);
1152
+ allResults.push(result as ValueTypeFromRequest<Resolve, T, I>);
881
1153
  }
882
1154
  }
883
1155
 
884
1156
  await iterator.close();
885
1157
 
886
- //s Deduplicate and return values directly
1158
+ // Deduplicate and return values directly
887
1159
  return dedup(allResults, this.indexByResolver);
888
1160
  }
889
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
+
890
1171
  /**
891
1172
  * Query and retrieve documents in a iterator
892
1173
  * @param queryRequest
893
1174
  * @param options
894
1175
  * @returns
895
1176
  */
896
- public iterate(
897
- queryRequest: types.SearchRequest,
898
- options?: QueryOptions<T, D>,
899
- ): 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
+
900
1220
  let fetchPromise: Promise<any> | undefined = undefined;
901
1221
  const peerBufferMap: Map<
902
1222
  string,
903
1223
  {
904
1224
  kept: number;
905
- buffer: BufferedResult<T>[];
1225
+ buffer: BufferedResult<
1226
+ types.ResultValue<T> | types.ResultIndexedValue<I>,
1227
+ I
1228
+ >[];
906
1229
  }
907
1230
  > = new Map();
908
1231
  const visited = new Set<string | number | bigint>();
@@ -914,8 +1237,8 @@ export class DocumentIndex<
914
1237
  const controller = new AbortController();
915
1238
 
916
1239
  const peerBuffers = (): {
917
- indexed: Record<string, any>;
918
- value: T;
1240
+ indexed: I;
1241
+ value: types.ResultValue<T> | types.ResultIndexedValue<I>;
919
1242
  from: PublicSignKey;
920
1243
  context: types.Context;
921
1244
  }[] => {
@@ -924,8 +1247,8 @@ export class DocumentIndex<
924
1247
 
925
1248
  const fetchFirst = async (n: number): Promise<boolean> => {
926
1249
  done = true; // Assume we are donne
927
- queryRequest.fetch = n;
928
- await this.queryDetailed(queryRequest, {
1250
+ queryRequestCoerced.fetch = n;
1251
+ await this.queryCommence(queryRequestCoerced, {
929
1252
  ...options,
930
1253
  onResponse: async (response, from) => {
931
1254
  if (!from) {
@@ -936,7 +1259,9 @@ export class DocumentIndex<
936
1259
  logger.error("Dont have access");
937
1260
  return;
938
1261
  } else if (response instanceof types.Results) {
939
- const results = response as types.Results<T>;
1262
+ const results = response as types.Results<
1263
+ types.ResultTypeFromRequest<R>
1264
+ >;
940
1265
  if (results.kept === 0n && results.results.length === 0) {
941
1266
  return;
942
1267
  }
@@ -944,24 +1269,42 @@ export class DocumentIndex<
944
1269
  if (results.kept > 0n) {
945
1270
  done = false; // we have more to do later!
946
1271
  }
947
- const buffer: BufferedResult<T>[] = [];
1272
+ const buffer: BufferedResult<types.ResultTypeFromRequest<R>, I>[] =
1273
+ [];
948
1274
 
949
1275
  for (const result of results.results) {
950
- const indexKey = indexerTypes.toId(
951
- this.indexByResolver(result.value),
952
- ).primitive;
953
- if (visited.has(indexKey)) {
954
- 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
+ });
955
1307
  }
956
- visited.add(indexKey);
957
- buffer.push({
958
- value: result.value,
959
- context: result.context,
960
- from,
961
- indexed:
962
- result.indexed ||
963
- (await this.transformer(result.value, result.context)),
964
- });
965
1308
  }
966
1309
 
967
1310
  peerBufferMap.set(from.hashcode(), {
@@ -977,7 +1320,7 @@ export class DocumentIndex<
977
1320
  });
978
1321
 
979
1322
  if (done) {
980
- this.clearResultsQueue(queryRequest);
1323
+ this.clearResultsQueue(queryRequestCoerced);
981
1324
  }
982
1325
 
983
1326
  return done;
@@ -1015,7 +1358,7 @@ export class DocumentIndex<
1015
1358
  // TODO buffer more than deleted?
1016
1359
  // TODO batch to multiple 'to's
1017
1360
  const collectRequest = new types.CollectNextRequest({
1018
- id: queryRequest.id,
1361
+ id: queryRequestCoerced.id,
1019
1362
  amount: n - buffer.buffer.length,
1020
1363
  });
1021
1364
  // Fetch locally?
@@ -1086,57 +1429,54 @@ export class DocumentIndex<
1086
1429
  })
1087
1430
  .then((response) =>
1088
1431
  introduceEntries(
1089
- queryRequest,
1432
+ queryRequestCoerced,
1090
1433
  response,
1091
1434
  this.documentType,
1435
+ this.indexedType,
1092
1436
  this._sync,
1093
1437
  options,
1094
1438
  )
1095
- .then((responses) => {
1096
- responses.map((response) => {
1097
- resultsLeft += Number(response.response.kept);
1098
- if (!response.from) {
1099
- logger.error("Missing from for sorted query");
1100
- return;
1101
- }
1102
-
1103
- if (response.response.results.length === 0) {
1104
- if (peerBufferMap.get(peer)?.buffer.length === 0) {
1105
- peerBufferMap.delete(peer); // No more results
1106
- }
1107
- } else {
1108
- const peerBuffer = peerBufferMap.get(peer);
1109
- 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");
1110
1446
  return;
1111
1447
  }
1112
- peerBuffer.kept = Number(response.response.kept);
1113
- for (const result of response.response.results) {
1114
- if (
1115
- visited.has(
1116
- indexerTypes.toId(
1117
- this.indexByResolver(result.value),
1118
- ).primitive,
1119
- )
1120
- ) {
1121
- 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;
1122
1457
  }
1123
- visited.add(
1124
- indexerTypes.toId(
1458
+ peerBuffer.kept = Number(response.response.kept);
1459
+ for (const result of response.response.results) {
1460
+ const idPrimitive = indexerTypes.toId(
1125
1461
  this.indexByResolver(result.value),
1126
- ).primitive,
1127
- );
1128
- peerBuffer.buffer.push({
1129
- value: result.value,
1130
- context: result.context,
1131
- from: response.from!,
1132
- indexed: this.transformer(
1133
- result.value,
1134
- result.context,
1135
- ),
1136
- });
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: await this.transformer(
1472
+ result.value,
1473
+ result.context,
1474
+ ),
1475
+ });
1476
+ }
1137
1477
  }
1138
- }
1139
- });
1478
+ }),
1479
+ );
1140
1480
  })
1141
1481
  .catch((e) => {
1142
1482
  logger.error(
@@ -1177,7 +1517,7 @@ export class DocumentIndex<
1177
1517
  indexerTypes.extractSortCompare(
1178
1518
  a.indexed,
1179
1519
  b.indexed,
1180
- queryRequest.sort,
1520
+ queryRequestCoerced.sort,
1181
1521
  ),
1182
1522
  );
1183
1523
 
@@ -1198,18 +1538,38 @@ export class DocumentIndex<
1198
1538
  }
1199
1539
 
1200
1540
  done = fetchedAll && !pendingMoreResults;
1541
+ let coercedBatch: ValueTypeFromRequest<Resolve, T, I>[];
1542
+ if (resolve) {
1543
+ coercedBatch = (
1544
+ await Promise.all(
1545
+ batch.map(async (x) =>
1546
+ x.value instanceof this.documentType
1547
+ ? x.value
1548
+ : (
1549
+ await this.resolveDocument({
1550
+ head: x.context.head,
1551
+ indexed: x.indexed,
1552
+ })
1553
+ )?.value,
1554
+ ),
1555
+ )
1556
+ ).filter((x) => !!x) as ValueTypeFromRequest<Resolve, T, I>[];
1557
+ } else {
1558
+ coercedBatch = batch.map((x) => x.value) as ValueTypeFromRequest<
1559
+ Resolve,
1560
+ T,
1561
+ I
1562
+ >[];
1563
+ }
1201
1564
 
1202
- return dedup(
1203
- batch.map((x) => x.value),
1204
- this.indexByResolver,
1205
- );
1565
+ return dedup(coercedBatch, this.indexByResolver);
1206
1566
  };
1207
1567
 
1208
1568
  const close = async () => {
1209
1569
  controller.abort(new AbortError("Iterator closed"));
1210
1570
 
1211
1571
  const closeRequest = new types.CloseIteratorRequest({
1212
- id: queryRequest.id,
1572
+ id: queryRequestCoerced.id,
1213
1573
  });
1214
1574
  const promises: Promise<any>[] = [];
1215
1575
  for (const [peer, buffer] of peerBufferMap) {
@@ -1243,7 +1603,7 @@ export class DocumentIndex<
1243
1603
  next,
1244
1604
  done: doneFn,
1245
1605
  all: async () => {
1246
- let result: T[] = [];
1606
+ let result: ValueTypeFromRequest<Resolve, T, I>[] = [];
1247
1607
  while (doneFn() !== true) {
1248
1608
  let batch = await next(100);
1249
1609
  result.push(...batch);
@@ -1252,4 +1612,34 @@ export class DocumentIndex<
1252
1612
  },
1253
1613
  };
1254
1614
  }
1615
+
1616
+ public async waitFor(
1617
+ other:
1618
+ | PublicSignKey
1619
+ | PeerId
1620
+ | string
1621
+ | (PublicSignKey | string | PeerId)[],
1622
+ options?: { signal?: AbortSignal; timeout?: number },
1623
+ ): Promise<void> {
1624
+ await super.waitFor(other, options);
1625
+ const ids = Array.isArray(other) ? other : [other];
1626
+ const expectedHashes = new Set(
1627
+ ids.map((x) =>
1628
+ typeof x === "string"
1629
+ ? x
1630
+ : x instanceof PublicSignKey
1631
+ ? x.hashcode()
1632
+ : getPublicKeyFromPeerId(x).hashcode(),
1633
+ ),
1634
+ );
1635
+
1636
+ for (const key of expectedHashes) {
1637
+ await waitFor(
1638
+ async () =>
1639
+ (await this._log.replicationIndex.count({ query: { hash: key } })) >
1640
+ 0,
1641
+ options,
1642
+ );
1643
+ }
1644
+ }
1255
1645
  }