@peerbit/document 7.0.14 → 7.1.0

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,17 @@ 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>;
144
44
  };
145
- export type QueryOptions<R> = {
146
- remote?: boolean | RemoteQueryOptions<types.AbstractSearchResult<R>>;
45
+ export type QueryOptions<R, D> = {
46
+ remote?: boolean | RemoteQueryOptions<types.AbstractSearchResult<R>, D>;
147
47
  local?: boolean;
148
48
  };
149
- export type SearchOptions<R> = QueryOptions<R>;
49
+ export type SearchOptions<R, D> = QueryOptions<R, D>;
150
50
 
151
51
  type Transformer<T, I> = (obj: T, context: types.Context) => MaybePromise<I>;
152
52
 
@@ -156,18 +56,18 @@ export type ResultsIterator<T> = {
156
56
  done: () => boolean;
157
57
  };
158
58
 
159
- type QueryDetailedOptions<T> = QueryOptions<T> & {
59
+ type QueryDetailedOptions<T, D> = QueryOptions<T, D> & {
160
60
  onResponse?: (
161
61
  response: types.AbstractSearchResult<T>,
162
62
  from: PublicSignKey,
163
63
  ) => void | Promise<void>;
164
64
  };
165
65
 
166
- const introduceEntries = async <T>(
66
+ const introduceEntries = async <T, D>(
167
67
  responses: RPCResponse<types.AbstractSearchResult<T>>[],
168
68
  type: AbstractType<T>,
169
69
  sync: (result: types.Results<T>) => Promise<void>,
170
- options?: QueryDetailedOptions<T>,
70
+ options?: QueryDetailedOptions<T, D>,
171
71
  ): Promise<RPCResponse<types.Results<T>>[]> => {
172
72
  const results: RPCResponse<types.Results<T>>[] = [];
173
73
  for (const response of responses) {
@@ -177,7 +77,7 @@ const introduceEntries = async <T>(
177
77
 
178
78
  if (response.response instanceof types.Results) {
179
79
  response.response.results.forEach((r) => r.init(type));
180
- if (typeof options?.remote !== "boolean" && options?.remote?.sync) {
80
+ if (typeof options?.remote !== "boolean" && options?.remote?.replicate) {
181
81
  await sync(response.response);
182
82
  }
183
83
  options?.onResponse &&
@@ -290,11 +190,10 @@ const isTransformerWithFunction = <T, I>(
290
190
  return (options as TransformerAsFunction<T, I>).transform != null;
291
191
  };
292
192
 
293
- export type OpenOptions<T, I> = {
193
+ export type OpenOptions<T, I, D extends ReplicationDomain<any, Operation>> = {
294
194
  documentType: AbstractType<T>;
295
-
296
195
  dbType: AbstractType<types.IDocumentStore<T>>;
297
- log: SharedLog<Operation>;
196
+ log: SharedLog<Operation, D>;
298
197
  canRead?: CanRead<T>;
299
198
  canSearch?: CanSearch;
300
199
  sync: (result: types.Results<T>) => Promise<void>;
@@ -308,9 +207,11 @@ type IndexableClass<I> = new (
308
207
  ) => IDocumentWithContext<I>;
309
208
 
310
209
  @variant("documents_index")
311
- export class DocumentIndex<T, I extends Record<string, any>> extends Program<
312
- OpenOptions<T, I>
313
- > {
210
+ export class DocumentIndex<
211
+ T,
212
+ I extends Record<string, any>,
213
+ D extends ReplicationDomain<any, Operation>,
214
+ > extends Program<OpenOptions<T, I, D>> {
314
215
  @field({ type: RPC })
315
216
  _query: RPC<
316
217
  indexerTypes.AbstractSearchRequest,
@@ -342,7 +243,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
342
243
 
343
244
  private _sync: (result: types.Results<T>) => Promise<void>;
344
245
 
345
- private _log: SharedLog<Operation>;
246
+ private _log: SharedLog<Operation, D>;
346
247
 
347
248
  private _resolverProgramCache?: Map<string | number | bigint, T>;
348
249
  private _resolverCache: Cache<T>;
@@ -372,7 +273,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
372
273
  return this._valueEncoding;
373
274
  }
374
275
 
375
- async open(properties: OpenOptions<T, I>) {
276
+ async open(properties: OpenOptions<T, I, D>) {
376
277
  this._log = properties.log;
377
278
 
378
279
  this.documentType = properties.documentType;
@@ -527,7 +428,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
527
428
 
528
429
  public async get(
529
430
  key: indexerTypes.Ideable | indexerTypes.IdKey,
530
- options?: QueryOptions<T>,
431
+ options?: QueryOptions<T, D>,
531
432
  ): Promise<T | undefined> {
532
433
  return (
533
434
  await this.getDetailed(
@@ -579,7 +480,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
579
480
 
580
481
  public async getDetailed(
581
482
  key: indexerTypes.IdKey | indexerTypes.IdPrimitive,
582
- options?: QueryOptions<T>,
483
+ options?: QueryOptions<T, D>,
583
484
  ): Promise<types.Results<T>[] | undefined> {
584
485
  let results: types.Results<T>[] | undefined;
585
486
  if (key instanceof Uint8Array) {
@@ -808,11 +709,12 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
808
709
  */
809
710
  public async queryDetailed(
810
711
  queryRequest: indexerTypes.SearchRequest,
811
- options?: QueryDetailedOptions<T>,
712
+ options?: QueryDetailedOptions<T, D>,
812
713
  ): Promise<types.Results<T>[]> {
813
714
  const local = typeof options?.local === "boolean" ? options?.local : true;
814
- let remote: RemoteQueryOptions<types.AbstractSearchResult<T>> | undefined =
815
- undefined;
715
+ let remote:
716
+ | RemoteQueryOptions<types.AbstractSearchResult<T>, D>
717
+ | undefined = undefined;
816
718
  if (typeof options?.remote === "boolean") {
817
719
  if (options?.remote) {
818
720
  remote = {};
@@ -829,7 +731,6 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
829
731
  remote.priority = 1;
830
732
  }
831
733
 
832
- const promises: Promise<types.Results<T>[] | undefined>[] = [];
833
734
  if (!local && !remote) {
834
735
  throw new Error(
835
736
  "Expecting either 'options.remote' or 'options.local' to be true",
@@ -850,53 +751,50 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
850
751
  }
851
752
  }
852
753
 
754
+ let resolved: types.Results<T>[] = [];
853
755
  if (remote) {
854
- const replicatorGroups = await this._log.getReplicatorUnion(
756
+ const replicatorGroups = await this._log.getCover(
757
+ remote.domain ?? (undefined as any),
855
758
  remote.minAge,
856
759
  );
857
760
 
858
761
  if (replicatorGroups) {
859
762
  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 {
763
+ const responseHandler = async (
764
+ results: RPCResponse<types.AbstractSearchResult<T>>[],
765
+ ) => {
766
+ for (const r of await introduceEntries(
767
+ results,
768
+ this.documentType,
769
+ this._sync,
770
+ options,
771
+ )) {
772
+ resolved.push(r.response);
773
+ }
774
+ };
775
+ try {
776
+ if (queryRequest instanceof indexerTypes.CloseIteratorRequest) {
777
+ // don't wait for responses
778
+ await this._query.request(queryRequest, { mode: remote!.mode });
779
+ } else {
780
+ await queryAll(
781
+ this._query,
782
+ groupHashes,
783
+ queryRequest,
784
+ responseHandler,
785
+ remote,
786
+ );
787
+ }
788
+ } catch (error) {
789
+ if (error instanceof MissingResponsesError) {
790
+ logger.warn("Did not reciveve responses from all shard");
791
+ if (remote?.throwOnMissing) {
894
792
  throw error;
895
793
  }
794
+ } else {
795
+ throw error;
896
796
  }
897
- return rs;
898
- };
899
- promises.push(fn());
797
+ }
900
798
  } else {
901
799
  // TODO send without direction out to the world? or just assume we can insert?
902
800
  /* promises.push(
@@ -909,7 +807,6 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
909
807
  ); */
910
808
  }
911
809
  }
912
- const resolved = await Promise.all(promises);
913
810
  for (const r of resolved) {
914
811
  if (r) {
915
812
  if (r instanceof Array) {
@@ -930,7 +827,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
930
827
  */
931
828
  public async search(
932
829
  queryRequest: indexerTypes.SearchRequest,
933
- options?: SearchOptions<T>,
830
+ options?: SearchOptions<T, D>,
934
831
  ): Promise<T[]> {
935
832
  // Set fetch to search size, or max value (default to max u32 (4294967295))
936
833
  queryRequest.fetch = queryRequest.fetch ?? 0xffffffff;
@@ -966,7 +863,7 @@ export class DocumentIndex<T, I extends Record<string, any>> extends Program<
966
863
  */
967
864
  public iterate(
968
865
  queryRequest: indexerTypes.SearchRequest,
969
- options?: QueryOptions<T>,
866
+ options?: QueryOptions<T, D>,
970
867
  ): ResultsIterator<T> {
971
868
  let fetchPromise: Promise<any> | undefined = undefined;
972
869
  const peerBufferMap: Map<