@peerbit/document 7.0.14 → 7.1.0-57b8640

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/program.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  import { logger as loggerFn } from "@peerbit/logger";
20
20
  import { Program, type ProgramEvents } from "@peerbit/program";
21
21
  import {
22
+ type ReplicationDomain,
22
23
  type SharedAppendOptions,
23
24
  SharedLog,
24
25
  type SharedLogOptions,
@@ -26,17 +27,19 @@ import {
26
27
  import { MAX_BATCH_SIZE } from "./constants.js";
27
28
  import {
28
29
  BORSH_ENCODING_OPERATION,
29
- type CanRead,
30
- type CanSearch,
31
30
  DeleteOperation,
32
- DocumentIndex,
33
- Operation,
31
+ type Operation,
34
32
  PutOperation,
35
33
  PutWithKeyOperation,
36
- type TransformOptions,
37
34
  coerceDeleteOperation,
38
35
  isDeleteOperation,
39
36
  isPutOperation,
37
+ } from "./operation.js";
38
+ import {
39
+ type CanRead,
40
+ type CanSearch,
41
+ DocumentIndex,
42
+ type TransformOptions,
40
43
  } from "./search.js";
41
44
 
42
45
  const logger = loggerFn({ module: "document" });
@@ -74,7 +77,11 @@ export type CanPerform<T> = (
74
77
  properties: CanPerformOperations<T>,
75
78
  ) => MaybePromise<boolean>;
76
79
 
77
- export type SetupOptions<T, I = T> = {
80
+ export type SetupOptions<
81
+ T,
82
+ I = T,
83
+ D extends ReplicationDomain<any, Operation> = any,
84
+ > = {
78
85
  type: AbstractType<T>;
79
86
  canOpen?: (program: T) => MaybePromise<boolean>;
80
87
  canPerform?: CanPerform<T>;
@@ -88,26 +95,29 @@ export type SetupOptions<T, I = T> = {
88
95
  trim?: TrimOptions;
89
96
  };
90
97
  compatibility?: 6;
91
- } & Exclude<SharedLogOptions<Operation>, "compatibility">;
98
+ } & Exclude<SharedLogOptions<Operation, D>, "compatibility">;
99
+
100
+ export type ExtractArgs<T> =
101
+ T extends ReplicationDomain<infer Args, any> ? Args : never;
92
102
 
93
103
  @variant("documents")
94
104
  export class Documents<
95
105
  T,
96
106
  I extends Record<string, any> = T extends Record<string, any> ? T : any,
97
- > extends Program<SetupOptions<T, I>, DocumentEvents<T> & ProgramEvents> {
107
+ D extends ReplicationDomain<any, Operation> = any,
108
+ > extends Program<SetupOptions<T, I, D>, DocumentEvents<T> & ProgramEvents> {
98
109
  @field({ type: SharedLog })
99
- log: SharedLog<Operation>;
110
+ log: SharedLog<Operation, D>;
100
111
 
101
112
  @field({ type: "bool" })
102
113
  immutable: boolean; // "Can I overwrite a document?"
103
114
 
104
115
  @field({ type: DocumentIndex })
105
- private _index: DocumentIndex<T, I>;
116
+ private _index: DocumentIndex<T, I, D>;
106
117
 
107
118
  private _clazz!: AbstractType<T>;
108
119
 
109
120
  private _optionCanPerform?: CanPerform<T>;
110
- private _manuallySynced!: Set<string>;
111
121
  private idResolver!: (any: any) => indexerTypes.IdPrimitive;
112
122
 
113
123
  canOpen?: (program: T, entry: Entry<Operation>) => Promise<boolean> | boolean;
@@ -117,7 +127,7 @@ export class Documents<
117
127
  constructor(properties?: {
118
128
  id?: Uint8Array;
119
129
  immutable?: boolean;
120
- index?: DocumentIndex<T, I>;
130
+ index?: DocumentIndex<T, I, ExtractArgs<D>>;
121
131
  }) {
122
132
  super();
123
133
 
@@ -126,11 +136,11 @@ export class Documents<
126
136
  this._index = properties?.index || new DocumentIndex();
127
137
  }
128
138
 
129
- get index(): DocumentIndex<T, I> {
139
+ get index(): DocumentIndex<T, I, D> {
130
140
  return this._index;
131
141
  }
132
142
 
133
- async open(options: SetupOptions<T, I>) {
143
+ async open(options: SetupOptions<T, I, D>) {
134
144
  this._clazz = options.type;
135
145
  this.canOpen = options.canOpen;
136
146
 
@@ -144,7 +154,6 @@ export class Documents<
144
154
  }
145
155
 
146
156
  this._optionCanPerform = options.canPerform;
147
- this._manuallySynced = new Set();
148
157
  const idProperty = options.index?.idProperty || "id";
149
158
  const idResolver =
150
159
  options.id ||
@@ -167,12 +176,10 @@ export class Documents<
167
176
  // here we arrive for all the results we want to persist.
168
177
  // we we need to do here is
169
178
  // 1. add the entry to a list of entries that we should persist through prunes
170
- let heads: string[] = [];
171
- for (const entry of result.results) {
172
- this._manuallySynced.add(entry.context.gid);
173
- heads.push(entry.context.head);
174
- }
175
- return this.log.log.join(heads);
179
+ await this.log.join(
180
+ result.results.map((x) => x.context.head),
181
+ { replicate: true },
182
+ );
176
183
  },
177
184
  dbType: this.constructor,
178
185
  });
@@ -185,11 +192,7 @@ export class Documents<
185
192
  trim: options?.log?.trim,
186
193
  replicate: options?.replicate,
187
194
  replicas: options?.replicas,
188
- sync: (entry: any) => {
189
- // here we arrive when ever a insertion/pruning behaviour processes an entry
190
- // returning true means that it should persist
191
- return this._manuallySynced.has(entry.gid);
192
- },
195
+ domain: options?.domain,
193
196
 
194
197
  // document v6 and below need log compatibility of v8 or below
195
198
  compatibility:
@@ -403,7 +406,10 @@ export class Documents<
403
406
 
404
407
  public async put(
405
408
  doc: T,
406
- options?: SharedAppendOptions<Operation> & { unique?: boolean },
409
+ options?: SharedAppendOptions<Operation> & {
410
+ unique?: boolean;
411
+ replicate?: boolean;
412
+ },
407
413
  ) {
408
414
  const keyValue = this.idResolver(doc);
409
415
 
@@ -424,7 +430,7 @@ export class Documents<
424
430
  : (
425
431
  await this._index.getDetailed(keyValue, {
426
432
  local: true,
427
- remote: { sync: true }, // only query remote if we know they exist
433
+ remote: { replicate: options?.replicate }, // only query remote if we know they exist
428
434
  })
429
435
  )?.[0]?.results[0];
430
436
 
@@ -458,6 +464,7 @@ export class Documents<
458
464
  onChange: (change) => {
459
465
  return this.handleChanges(change, { document: doc, operation });
460
466
  },
467
+ replicate: options?.replicate,
461
468
  });
462
469
 
463
470
  return appended;
@@ -471,7 +478,7 @@ export class Documents<
471
478
  const existing = (
472
479
  await this._index.getDetailed(key, {
473
480
  local: true,
474
- remote: { sync: true },
481
+ remote: { replicate: options?.replicate },
475
482
  })
476
483
  )?.[0]?.results[0];
477
484
 
@@ -479,6 +486,7 @@ export class Documents<
479
486
  throw new Error(`No entry with key '${key.primitive}' in the database`);
480
487
  }
481
488
 
489
+ const entry = await this._resolveEntry(existing.context.head);
482
490
  return this.log.append(
483
491
  new DeleteOperation({
484
492
  key,
@@ -486,7 +494,7 @@ export class Documents<
486
494
  {
487
495
  ...options,
488
496
  meta: {
489
- next: [await this._resolveEntry(existing.context.head)],
497
+ next: [entry],
490
498
  type: EntryType.CUT,
491
499
  ...options?.meta,
492
500
  },
@@ -507,7 +515,7 @@ export class Documents<
507
515
  }
508
516
 
509
517
  const sortedEntries = [
510
- ...change.added,
518
+ ...change.added.map((x) => x.entry),
511
519
  ...((await Promise.all(
512
520
  change.removed.map((x) =>
513
521
  x instanceof Entry ? x : this.log.log.entryIndex.get(x.hash),
@@ -545,7 +553,6 @@ export class Documents<
545
553
 
546
554
  // get index key from value
547
555
  const keyObject = this.idResolver(value);
548
-
549
556
  const key = indexerTypes.toId(keyObject);
550
557
 
551
558
  // document is already updated with more recent entry
@@ -574,8 +581,6 @@ export class Documents<
574
581
  isPutOperation(payload) ||
575
582
  removedSet.has(item.hash)
576
583
  ) {
577
- this._manuallySynced.delete(item.meta.gid);
578
-
579
584
  let value: T;
580
585
  let key: indexerTypes.IdKey;
581
586
 
package/src/search.ts CHANGED
@@ -18,12 +18,14 @@ import {
18
18
  type RPCResponse,
19
19
  queryAll,
20
20
  } from "@peerbit/rpc";
21
- import { SharedLog } from "@peerbit/shared-log";
21
+ import { type ReplicationDomain, SharedLog } from "@peerbit/shared-log";
22
22
  import { SilentDelivery } from "@peerbit/stream-interface";
23
23
  import { AbortError } from "@peerbit/time";
24
24
  import { concat, fromString } from "uint8arrays";
25
25
  import { copySerialization } from "./borsh.js";
26
26
  import { MAX_BATCH_SIZE } from "./constants.js";
27
+ import { type Operation, isPutOperation } from "./operation.js";
28
+ import type { ExtractArgs } from "./program.js";
27
29
 
28
30
  const logger = loggerFn({ module: "document-index" });
29
31
 
@@ -34,119 +36,18 @@ type BufferedResult<T> = {
34
36
  from: PublicSignKey;
35
37
  };
36
38
 
37
- @variant(0)
38
- export class Operation /* <T> */ {}
39
-
40
- export const BORSH_ENCODING_OPERATION = BORSH_ENCODING(Operation);
41
-
42
- // @deprecated
43
- @variant(0)
44
- export class PutWithKeyOperation extends Operation {
45
- @field({ type: "string" })
46
- key: string;
47
-
48
- @field({ type: Uint8Array })
49
- data: Uint8Array;
50
-
51
- constructor(props: { key: string; data: Uint8Array }) {
52
- super();
53
- this.key = props.key;
54
- this.data = props.data;
55
- }
56
- }
57
-
58
- // @deprecated
59
- /* @variant(1)
60
- export class PutAllOperation<T> extends Operation<T> {
61
- @field({ type: vec(PutOperation) })
62
- docs: PutOperation<T>[];
63
-
64
- constructor(props?: { docs: PutOperation<T>[] }) {
65
- super();
66
- if (props) {
67
- this.docs = props.docs;
68
- }
69
- }
70
- }
71
- */
72
-
73
- // @deprecated
74
- @variant(2)
75
- export class DeleteByStringKeyOperation extends Operation {
76
- @field({ type: "string" })
77
- key: string;
78
-
79
- constructor(props: { key: string }) {
80
- super();
81
- this.key = props.key;
82
- }
83
-
84
- toDeleteOperation(): DeleteOperation {
85
- return new DeleteOperation({ key: indexerTypes.toId(this.key) });
86
- }
87
- }
88
-
89
- export const coerceDeleteOperation = (
90
- operation: DeleteOperation | DeleteByStringKeyOperation,
91
- ): DeleteOperation => {
92
- return operation instanceof DeleteByStringKeyOperation
93
- ? operation.toDeleteOperation()
94
- : operation;
95
- };
96
-
97
- @variant(3)
98
- export class PutOperation extends Operation {
99
- @field({ type: Uint8Array })
100
- data: Uint8Array;
101
-
102
- constructor(props: { data: Uint8Array }) {
103
- super();
104
- this.data = props.data;
105
- }
106
- }
107
-
108
- export const isPutOperation = (
109
- operation: Operation,
110
- ): operation is PutOperation | PutWithKeyOperation => {
111
- return (
112
- operation instanceof PutOperation ||
113
- operation instanceof PutWithKeyOperation
114
- );
115
- };
116
-
117
- /**
118
- * Delete a document at a key
119
- */
120
- @variant(4)
121
- export class DeleteOperation extends Operation {
122
- @field({ type: indexerTypes.IdKey })
123
- key: indexerTypes.IdKey;
124
-
125
- constructor(props: { key: indexerTypes.IdKey }) {
126
- super();
127
- this.key = props.key;
128
- }
129
- }
130
-
131
- export const isDeleteOperation = (
132
- operation: Operation,
133
- ): operation is DeleteOperation | DeleteByStringKeyOperation => {
134
- return (
135
- operation instanceof DeleteOperation ||
136
- operation instanceof DeleteByStringKeyOperation
137
- );
138
- };
139
-
140
- export type RemoteQueryOptions<R> = RPCRequestAllOptions<R> & {
141
- sync?: boolean;
39
+ export type RemoteQueryOptions<R, D> = RPCRequestAllOptions<R> & {
40
+ replicate?: boolean;
142
41
  minAge?: number;
143
42
  throwOnMissing?: boolean;
43
+ domain?: ExtractArgs<D>;
44
+ eager?: boolean; // whether to query newly joined peers before they have matured
144
45
  };
145
- export type QueryOptions<R> = {
146
- remote?: boolean | RemoteQueryOptions<types.AbstractSearchResult<R>>;
46
+ export type QueryOptions<R, D> = {
47
+ remote?: boolean | RemoteQueryOptions<types.AbstractSearchResult<R>, D>;
147
48
  local?: boolean;
148
49
  };
149
- export type SearchOptions<R> = QueryOptions<R>;
50
+ export type SearchOptions<R, D> = QueryOptions<R, D>;
150
51
 
151
52
  type Transformer<T, I> = (obj: T, context: types.Context) => MaybePromise<I>;
152
53
 
@@ -156,18 +57,18 @@ export type ResultsIterator<T> = {
156
57
  done: () => boolean;
157
58
  };
158
59
 
159
- type QueryDetailedOptions<T> = QueryOptions<T> & {
60
+ type QueryDetailedOptions<T, D> = QueryOptions<T, D> & {
160
61
  onResponse?: (
161
62
  response: types.AbstractSearchResult<T>,
162
63
  from: PublicSignKey,
163
64
  ) => void | Promise<void>;
164
65
  };
165
66
 
166
- const introduceEntries = async <T>(
67
+ const introduceEntries = async <T, D>(
167
68
  responses: RPCResponse<types.AbstractSearchResult<T>>[],
168
69
  type: AbstractType<T>,
169
70
  sync: (result: types.Results<T>) => Promise<void>,
170
- options?: QueryDetailedOptions<T>,
71
+ options?: QueryDetailedOptions<T, D>,
171
72
  ): Promise<RPCResponse<types.Results<T>>[]> => {
172
73
  const results: RPCResponse<types.Results<T>>[] = [];
173
74
  for (const response of responses) {
@@ -177,7 +78,7 @@ const introduceEntries = async <T>(
177
78
 
178
79
  if (response.response instanceof types.Results) {
179
80
  response.response.results.forEach((r) => r.init(type));
180
- if (typeof options?.remote !== "boolean" && options?.remote?.sync) {
81
+ if (typeof options?.remote !== "boolean" && options?.remote?.replicate) {
181
82
  await sync(response.response);
182
83
  }
183
84
  options?.onResponse &&
@@ -290,11 +191,10 @@ const isTransformerWithFunction = <T, I>(
290
191
  return (options as TransformerAsFunction<T, I>).transform != null;
291
192
  };
292
193
 
293
- export type OpenOptions<T, I> = {
194
+ export type OpenOptions<T, I, D extends ReplicationDomain<any, Operation>> = {
294
195
  documentType: AbstractType<T>;
295
-
296
196
  dbType: AbstractType<types.IDocumentStore<T>>;
297
- log: SharedLog<Operation>;
197
+ log: SharedLog<Operation, D>;
298
198
  canRead?: CanRead<T>;
299
199
  canSearch?: CanSearch;
300
200
  sync: (result: types.Results<T>) => Promise<void>;
@@ -308,9 +208,11 @@ type IndexableClass<I> = new (
308
208
  ) => IDocumentWithContext<I>;
309
209
 
310
210
  @variant("documents_index")
311
- export class DocumentIndex<T, I extends Record<string, any>> extends Program<
312
- OpenOptions<T, I>
313
- > {
211
+ export class DocumentIndex<
212
+ T,
213
+ I extends Record<string, any>,
214
+ D extends ReplicationDomain<any, Operation>,
215
+ > extends Program<OpenOptions<T, I, D>> {
314
216
  @field({ type: RPC })
315
217
  _query: RPC<
316
218
  indexerTypes.AbstractSearchRequest,
@@ -342,7 +244,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
342
244
 
343
245
  private _sync: (result: types.Results<T>) => Promise<void>;
344
246
 
345
- private _log: SharedLog<Operation>;
247
+ private _log: SharedLog<Operation, D>;
346
248
 
347
249
  private _resolverProgramCache?: Map<string | number | bigint, T>;
348
250
  private _resolverCache: Cache<T>;
@@ -372,7 +274,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
372
274
  return this._valueEncoding;
373
275
  }
374
276
 
375
- async open(properties: OpenOptions<T, I>) {
277
+ async open(properties: OpenOptions<T, I, D>) {
376
278
  this._log = properties.log;
377
279
 
378
280
  this.documentType = properties.documentType;
@@ -527,7 +429,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
527
429
 
528
430
  public async get(
529
431
  key: indexerTypes.Ideable | indexerTypes.IdKey,
530
- options?: QueryOptions<T>,
432
+ options?: QueryOptions<T, D>,
531
433
  ): Promise<T | undefined> {
532
434
  return (
533
435
  await this.getDetailed(
@@ -579,7 +481,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
579
481
 
580
482
  public async getDetailed(
581
483
  key: indexerTypes.IdKey | indexerTypes.IdPrimitive,
582
- options?: QueryOptions<T>,
484
+ options?: QueryOptions<T, D>,
583
485
  ): Promise<types.Results<T>[] | undefined> {
584
486
  let results: types.Results<T>[] | undefined;
585
487
  if (key instanceof Uint8Array) {
@@ -808,11 +710,12 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
808
710
  */
809
711
  public async queryDetailed(
810
712
  queryRequest: indexerTypes.SearchRequest,
811
- options?: QueryDetailedOptions<T>,
713
+ options?: QueryDetailedOptions<T, D>,
812
714
  ): Promise<types.Results<T>[]> {
813
715
  const local = typeof options?.local === "boolean" ? options?.local : true;
814
- let remote: RemoteQueryOptions<types.AbstractSearchResult<T>> | undefined =
815
- undefined;
716
+ let remote:
717
+ | RemoteQueryOptions<types.AbstractSearchResult<T>, D>
718
+ | undefined = undefined;
816
719
  if (typeof options?.remote === "boolean") {
817
720
  if (options?.remote) {
818
721
  remote = {};
@@ -829,7 +732,6 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
829
732
  remote.priority = 1;
830
733
  }
831
734
 
832
- const promises: Promise<types.Results<T>[] | undefined>[] = [];
833
735
  if (!local && !remote) {
834
736
  throw new Error(
835
737
  "Expecting either 'options.remote' or 'options.local' to be true",
@@ -850,53 +752,53 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
850
752
  }
851
753
  }
852
754
 
755
+ let resolved: types.Results<T>[] = [];
853
756
  if (remote) {
854
- const replicatorGroups = await this._log.getReplicatorUnion(
855
- remote.minAge,
757
+ const replicatorGroups = await this._log.getCover(
758
+ remote.domain ?? (undefined as any),
759
+ {
760
+ roleAge: remote.minAge,
761
+ eager: remote.eager,
762
+ },
856
763
  );
857
764
 
858
765
  if (replicatorGroups) {
859
766
  const groupHashes: string[][] = replicatorGroups.map((x) => [x]);
860
- const fn = async () => {
861
- const rs: types.Results<T>[] = [];
862
- const responseHandler = async (
863
- results: RPCResponse<types.AbstractSearchResult<T>>[],
864
- ) => {
865
- for (const r of await introduceEntries(
866
- results,
867
- this.documentType,
868
- this._sync,
869
- options,
870
- )) {
871
- rs.push(r.response);
872
- }
873
- };
874
- try {
875
- if (queryRequest instanceof indexerTypes.CloseIteratorRequest) {
876
- // don't wait for responses
877
- await this._query.request(queryRequest, { mode: remote!.mode });
878
- } else {
879
- await queryAll(
880
- this._query,
881
- groupHashes,
882
- queryRequest,
883
- responseHandler,
884
- remote,
885
- );
886
- }
887
- } catch (error) {
888
- if (error instanceof MissingResponsesError) {
889
- logger.warn("Did not reciveve responses from all shard");
890
- if (remote?.throwOnMissing) {
891
- throw error;
892
- }
893
- } else {
767
+ const responseHandler = async (
768
+ results: RPCResponse<types.AbstractSearchResult<T>>[],
769
+ ) => {
770
+ for (const r of await introduceEntries(
771
+ results,
772
+ this.documentType,
773
+ this._sync,
774
+ options,
775
+ )) {
776
+ resolved.push(r.response);
777
+ }
778
+ };
779
+ try {
780
+ if (queryRequest instanceof indexerTypes.CloseIteratorRequest) {
781
+ // don't wait for responses
782
+ await this._query.request(queryRequest, { mode: remote!.mode });
783
+ } else {
784
+ await queryAll(
785
+ this._query,
786
+ groupHashes,
787
+ queryRequest,
788
+ responseHandler,
789
+ remote,
790
+ );
791
+ }
792
+ } catch (error) {
793
+ if (error instanceof MissingResponsesError) {
794
+ logger.warn("Did not reciveve responses from all shard");
795
+ if (remote?.throwOnMissing) {
894
796
  throw error;
895
797
  }
798
+ } else {
799
+ throw error;
896
800
  }
897
- return rs;
898
- };
899
- promises.push(fn());
801
+ }
900
802
  } else {
901
803
  // TODO send without direction out to the world? or just assume we can insert?
902
804
  /* promises.push(
@@ -909,7 +811,6 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
909
811
  ); */
910
812
  }
911
813
  }
912
- const resolved = await Promise.all(promises);
913
814
  for (const r of resolved) {
914
815
  if (r) {
915
816
  if (r instanceof Array) {
@@ -930,7 +831,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
930
831
  */
931
832
  public async search(
932
833
  queryRequest: indexerTypes.SearchRequest,
933
- options?: SearchOptions<T>,
834
+ options?: SearchOptions<T, D>,
934
835
  ): Promise<T[]> {
935
836
  // Set fetch to search size, or max value (default to max u32 (4294967295))
936
837
  queryRequest.fetch = queryRequest.fetch ?? 0xffffffff;
@@ -966,7 +867,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
966
867
  */
967
868
  public iterate(
968
869
  queryRequest: indexerTypes.SearchRequest,
969
- options?: QueryOptions<T>,
870
+ options?: QueryOptions<T, D>,
970
871
  ): ResultsIterator<T> {
971
872
  let fetchPromise: Promise<any> | undefined = undefined;
972
873
  const peerBufferMap: Map<