@peerbit/document 6.0.7-efee9d3 → 7.0.0-3a75d6e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/benchmark/index.js +13 -14
  2. package/dist/benchmark/index.js.map +1 -1
  3. package/dist/benchmark/memory/index.d.ts +2 -0
  4. package/dist/benchmark/memory/index.d.ts.map +1 -0
  5. package/dist/benchmark/memory/index.js +122 -0
  6. package/dist/benchmark/memory/index.js.map +1 -0
  7. package/dist/benchmark/memory/insert.d.ts +2 -0
  8. package/dist/benchmark/memory/insert.d.ts.map +1 -0
  9. package/dist/benchmark/memory/insert.js +133 -0
  10. package/dist/benchmark/memory/insert.js.map +1 -0
  11. package/dist/benchmark/memory/utils.d.ts +13 -0
  12. package/dist/benchmark/memory/utils.d.ts.map +1 -0
  13. package/dist/benchmark/memory/utils.js +2 -0
  14. package/dist/benchmark/memory/utils.js.map +1 -0
  15. package/dist/benchmark/replication.js +27 -29
  16. package/dist/benchmark/replication.js.map +1 -1
  17. package/dist/src/borsh.d.ts +2 -0
  18. package/dist/src/borsh.d.ts.map +1 -0
  19. package/dist/src/borsh.js +16 -0
  20. package/dist/src/borsh.js.map +1 -0
  21. package/dist/src/index.d.ts +0 -1
  22. package/dist/src/index.d.ts.map +1 -1
  23. package/dist/src/index.js +0 -1
  24. package/dist/src/index.js.map +1 -1
  25. package/dist/src/program.d.ts +16 -19
  26. package/dist/src/program.d.ts.map +1 -1
  27. package/dist/src/program.js +57 -68
  28. package/dist/src/program.js.map +1 -1
  29. package/dist/src/search.d.ts +46 -32
  30. package/dist/src/search.d.ts.map +1 -1
  31. package/dist/src/search.js +236 -133
  32. package/dist/src/search.js.map +1 -1
  33. package/package.json +16 -11
  34. package/src/borsh.ts +19 -0
  35. package/src/index.ts +0 -1
  36. package/src/program.ts +118 -118
  37. package/src/search.ts +438 -218
package/src/program.ts CHANGED
@@ -3,36 +3,37 @@ import {
3
3
  BorshError,
4
4
  field,
5
5
  serialize,
6
- variant
6
+ variant,
7
7
  } from "@dao-xyz/borsh";
8
- import { type Change, Entry, EntryType, type TrimOptions } from "@peerbit/log";
9
- import { Program, type ProgramEvents } from "@peerbit/program";
8
+ import { CustomEvent } from "@libp2p/interface";
10
9
  import { AccessError, DecryptedThing } from "@peerbit/crypto";
10
+ import * as documentsTypes from "@peerbit/document-interface";
11
+ import * as indexerTypes from "@peerbit/indexer-interface";
12
+ import {
13
+ type Change,
14
+ Entry,
15
+ EntryType,
16
+ type ShallowOrFullEntry,
17
+ type TrimOptions,
18
+ } from "@peerbit/log";
11
19
  import { logger as loggerFn } from "@peerbit/logger";
12
- import { CustomEvent } from "@libp2p/interface";
20
+ import { Program, type ProgramEvents } from "@peerbit/program";
13
21
  import {
14
- type RoleOptions,
15
- Observer,
16
- Replicator,
22
+ type SharedAppendOptions,
17
23
  SharedLog,
18
24
  type SharedLogOptions,
19
- type SharedAppendOptions
20
25
  } from "@peerbit/shared-log";
21
- import * as types from "@peerbit/document-interface";
22
-
23
- export type { RoleOptions }; // For convenience (so that consumers does not have to do the import above from shared-log packages)
24
-
26
+ import { MAX_BATCH_SIZE } from "./constants.js";
25
27
  import {
26
- type IndexableFields,
27
28
  BORSH_ENCODING_OPERATION,
29
+ type CanRead,
30
+ type CanSearch,
28
31
  DeleteOperation,
29
32
  DocumentIndex,
30
33
  Operation,
31
34
  PutOperation,
32
- type CanSearch,
33
- type CanRead
35
+ type TransformOptions,
34
36
  } from "./search.js";
35
- import { MAX_BATCH_SIZE } from "./constants.js";
36
37
 
37
38
  const logger = loggerFn({ module: "document" });
38
39
 
@@ -66,30 +67,29 @@ type CanPerformDelete<T> = {
66
67
 
67
68
  export type CanPerformOperations<T> = CanPerformPut<T> | CanPerformDelete<T>;
68
69
  export type CanPerform<T> = (
69
- properties: CanPerformOperations<T>
70
+ properties: CanPerformOperations<T>,
70
71
  ) => MaybePromise<boolean>;
71
72
 
72
- export type SetupOptions<T> = {
73
+ export type SetupOptions<T, I = T> = {
73
74
  type: AbstractType<T>;
74
75
  canOpen?: (program: T) => MaybePromise<boolean>;
75
76
  canPerform?: CanPerform<T>;
76
- id?: (obj: any) => types.IdPrimitive;
77
+ id?: (obj: any) => indexerTypes.IdPrimitive;
77
78
  index?: {
78
- idProperty?: string | string[];
79
79
  canSearch?: CanSearch;
80
80
  canRead?: CanRead<T>;
81
- fields?: IndexableFields<T>;
82
- };
81
+ idProperty?: string | string[];
82
+ } & TransformOptions<T, I>;
83
83
  log?: {
84
84
  trim?: TrimOptions;
85
85
  };
86
86
  } & SharedLogOptions<Operation>;
87
87
 
88
88
  @variant("documents")
89
- export class Documents<T> extends Program<
90
- SetupOptions<T>,
91
- DocumentEvents<T> & ProgramEvents
92
- > {
89
+ export class Documents<
90
+ T,
91
+ I extends Record<string, any> = T extends Record<string, any> ? T : any,
92
+ > extends Program<SetupOptions<T, I>, DocumentEvents<T> & ProgramEvents> {
93
93
  @field({ type: SharedLog })
94
94
  log: SharedLog<Operation>;
95
95
 
@@ -97,20 +97,20 @@ export class Documents<T> extends Program<
97
97
  immutable: boolean; // "Can I overwrite a document?"
98
98
 
99
99
  @field({ type: DocumentIndex })
100
- private _index: DocumentIndex<T>;
100
+ private _index: DocumentIndex<T, I>;
101
101
 
102
- private _clazz: AbstractType<T>;
102
+ private _clazz!: AbstractType<T>;
103
103
 
104
104
  private _optionCanPerform?: CanPerform<T>;
105
- private _manuallySynced: Set<string>;
106
- private idResolver: (any: any) => types.IdPrimitive;
105
+ private _manuallySynced!: Set<string>;
106
+ private idResolver!: (any: any) => indexerTypes.IdPrimitive;
107
107
 
108
108
  canOpen?: (program: T, entry: Entry<Operation>) => Promise<boolean> | boolean;
109
109
 
110
110
  constructor(properties?: {
111
111
  id?: Uint8Array;
112
112
  immutable?: boolean;
113
- index?: DocumentIndex<T>;
113
+ index?: DocumentIndex<T, I>;
114
114
  }) {
115
115
  super();
116
116
 
@@ -119,11 +119,11 @@ export class Documents<T> extends Program<
119
119
  this._index = properties?.index || new DocumentIndex();
120
120
  }
121
121
 
122
- get index(): DocumentIndex<T> {
122
+ get index(): DocumentIndex<T, I> {
123
123
  return this._index;
124
124
  }
125
125
 
126
- async open(options: SetupOptions<T>) {
126
+ async open(options: SetupOptions<T, I>) {
127
127
  this._clazz = options.type;
128
128
  this.canOpen = options.canOpen;
129
129
 
@@ -131,7 +131,7 @@ export class Documents<T> extends Program<
131
131
  if (Program.isPrototypeOf(this._clazz)) {
132
132
  if (!this.canOpen) {
133
133
  throw new Error(
134
- "Document store needs to be opened with canOpen option when the document type is a Program"
134
+ "Document store needs to be opened with canOpen option when the document type is a Program",
135
135
  );
136
136
  }
137
137
  }
@@ -143,28 +143,19 @@ export class Documents<T> extends Program<
143
143
  options.id ||
144
144
  (typeof idProperty === "string"
145
145
  ? (obj: any) => obj[idProperty as string]
146
- : (obj: any) => types.extractFieldValue(obj, idProperty as string[]));
146
+ : (obj: any) =>
147
+ indexerTypes.extractFieldValue(obj, idProperty as string[]));
147
148
 
148
149
  this.idResolver = idResolver;
149
150
 
150
- let transform: IndexableFields<T>;
151
- if (options.index?.fields) {
152
- if (typeof options.index.fields === "function") {
153
- transform = options.index.fields;
154
- } else {
155
- transform = options.index.fields;
156
- }
157
- } else {
158
- transform = (obj) => obj as Record<string, any>; // TODO check types
159
- }
160
151
  await this._index.open({
161
- type: this._clazz,
162
152
  log: this.log,
163
153
  canRead: options?.index?.canRead,
164
154
  canSearch: options.index?.canSearch,
165
- fields: transform,
155
+ documentType: this._clazz,
156
+ transform: options.index,
166
157
  indexBy: idProperty,
167
- sync: async (result: types.Results<T>) => {
158
+ sync: async (result: documentsTypes.Results<T>) => {
168
159
  // here we arrive for all the results we want to persist.
169
160
  // we we need to do here is
170
161
  // 1. add the entry to a list of entries that we should persist through prunes
@@ -175,7 +166,7 @@ export class Documents<T> extends Program<
175
166
  }
176
167
  return this.log.log.join(heads);
177
168
  },
178
- dbType: this.constructor
169
+ dbType: this.constructor,
179
170
  });
180
171
 
181
172
  await this.log.open({
@@ -184,13 +175,13 @@ export class Documents<T> extends Program<
184
175
  canAppend: this.canAppend.bind(this),
185
176
  onChange: this.handleChanges.bind(this),
186
177
  trim: options?.log?.trim,
187
- role: options?.role,
178
+ replicate: options?.replicate,
188
179
  replicas: options?.replicas,
189
180
  sync: (entry: any) => {
190
181
  // here we arrive when ever a insertion/pruning behaviour processes an entry
191
182
  // returning true means that it should persist
192
183
  return this._manuallySynced.has(entry.gid);
193
- }
184
+ },
194
185
  });
195
186
  }
196
187
 
@@ -201,20 +192,13 @@ export class Documents<T> extends Program<
201
192
  private async _resolveEntry(history: Entry<Operation> | string) {
202
193
  return typeof history === "string"
203
194
  ? (await this.log.log.get(history)) ||
204
- (await Entry.fromMultihash<Operation>(this.log.log.blocks, history))
195
+ (await Entry.fromMultihash<Operation>(this.log.log.blocks, history))
205
196
  : history;
206
197
  }
207
198
 
208
- async updateRole(role: RoleOptions) {
209
- await this.log.updateRole(role);
210
- }
211
- get role(): Replicator | Observer {
212
- return this.log.role;
213
- }
214
-
215
199
  async canAppend(
216
200
  entry: Entry<Operation>,
217
- reference?: { document: T; operation: PutOperation }
201
+ reference?: { document: T; operation: PutOperation },
218
202
  ): Promise<boolean> {
219
203
  const l0 = await this._canAppend(entry as Entry<Operation>, reference);
220
204
  if (!l0) {
@@ -244,16 +228,16 @@ export class Documents<T> extends Program<
244
228
  !(await this._optionCanPerform(
245
229
  operation instanceof PutOperation
246
230
  ? {
247
- type: "put",
248
- value: document!,
249
- operation,
250
- entry: entry as any as Entry<PutOperation>
251
- }
231
+ type: "put",
232
+ value: document!,
233
+ operation,
234
+ entry: entry as any as Entry<PutOperation>,
235
+ }
252
236
  : {
253
- type: "delete",
254
- operation,
255
- entry: entry as any as Entry<DeleteOperation>
256
- }
237
+ type: "delete",
238
+ operation,
239
+ entry: entry as any as Entry<DeleteOperation>,
240
+ },
257
241
  ))
258
242
  ) {
259
243
  return false;
@@ -272,12 +256,12 @@ export class Documents<T> extends Program<
272
256
 
273
257
  async _canAppend(
274
258
  entry: Entry<Operation>,
275
- reference?: { document: T; operation: PutOperation }
259
+ reference?: { document: T; operation: PutOperation },
276
260
  ): Promise<PutOperation | DeleteOperation | false> {
277
261
  const resolve = async (history: Entry<Operation> | string) => {
278
262
  return typeof history === "string"
279
263
  ? this.log.log.get(history) ||
280
- (await Entry.fromMultihash(this.log.log.blocks, history))
264
+ (await Entry.fromMultihash(this.log.log.blocks, history))
281
265
  : history;
282
266
  };
283
267
  const pointsToHistory = async (history: Entry<Operation> | string) => {
@@ -301,7 +285,7 @@ export class Documents<T> extends Program<
301
285
  try {
302
286
  entry.init({
303
287
  encoding: this.log.log.encoding,
304
- keychain: this.node.services.keychain
288
+ keychain: this.node.services.keychain,
305
289
  });
306
290
  const operation =
307
291
  reference?.operation || entry._payload instanceof DecryptedThing
@@ -315,9 +299,10 @@ export class Documents<T> extends Program<
315
299
  this.index.valueEncoding.decoder(putOperation.data);
316
300
  const keyValue = this.idResolver(value);
317
301
 
318
- const key = types.toId(keyValue);
302
+ const key = indexerTypes.toId(keyValue);
319
303
 
320
- const existingDocument = await this.index.engine.get(key);
304
+ const existingDocument = (await this.index.getDetailed(key))?.[0]
305
+ ?.results[0];
321
306
  if (existingDocument && existingDocument.context.head !== entry.hash) {
322
307
  // econd condition can false if we reset the operation log, while not resetting the index. For example when doing .recover
323
308
  if (this.immutable) {
@@ -344,7 +329,9 @@ export class Documents<T> extends Program<
344
329
  if (entry.next.length !== 1) {
345
330
  return false;
346
331
  }
347
- const existingDocument = await this._index.engine.get(operation.key);
332
+ const existingDocument = (
333
+ await this.index.getDetailed(operation.key)
334
+ )?.[0].results[0];
348
335
  if (!existingDocument) {
349
336
  // already deleted
350
337
  return operation; // assume ok
@@ -377,32 +364,33 @@ export class Documents<T> extends Program<
377
364
 
378
365
  public async put(
379
366
  doc: T,
380
- options?: SharedAppendOptions<Operation> & { unique?: boolean }
367
+ options?: SharedAppendOptions<Operation> & { unique?: boolean },
381
368
  ) {
382
369
  const keyValue = this.idResolver(doc);
383
370
 
384
371
  // type check the key
385
- types.checkId(keyValue);
372
+ indexerTypes.checkId(keyValue);
386
373
 
387
374
  const ser = serialize(doc);
388
375
  if (ser.length > MAX_BATCH_SIZE) {
389
376
  throw new Error(
390
- `Document is too large (${ser.length * 1e-6
391
- }) mb). Needs to be less than ${MAX_BATCH_SIZE * 1e-6} mb`
377
+ `Document is too large (${
378
+ ser.length * 1e-6
379
+ }) mb). Needs to be less than ${MAX_BATCH_SIZE * 1e-6} mb`,
392
380
  );
393
381
  }
394
382
 
395
383
  const existingDocument = options?.unique
396
384
  ? undefined
397
385
  : (
398
- await this._index.getDetailed(keyValue, {
399
- local: true,
400
- remote: { sync: true } // only query remote if we know they exist
401
- })
402
- )?.[0]?.results[0];
386
+ await this._index.getDetailed(keyValue, {
387
+ local: true,
388
+ remote: { sync: true }, // only query remote if we know they exist
389
+ })
390
+ )?.[0]?.results[0];
403
391
 
404
392
  const operation = new PutOperation({
405
- data: ser
393
+ data: ser,
406
394
  });
407
395
  const appended = await this.log.append(operation, {
408
396
  ...options,
@@ -410,73 +398,86 @@ export class Documents<T> extends Program<
410
398
  next: existingDocument
411
399
  ? [await this._resolveEntry(existingDocument.context.head)]
412
400
  : [],
413
- ...options?.meta
401
+ ...options?.meta,
414
402
  },
415
403
  canAppend: (entry) => {
416
404
  return this.canAppend(entry, { document: doc, operation });
417
405
  },
418
406
  onChange: (change) => {
419
407
  return this.handleChanges(change, { document: doc, operation });
420
- }
408
+ },
421
409
  });
422
410
 
423
411
  return appended;
424
412
  }
425
413
 
426
- async del(id: types.Ideable, options?: SharedAppendOptions<Operation>) {
427
- const key = types.toId(id);
414
+ async del(
415
+ id: indexerTypes.Ideable,
416
+ options?: SharedAppendOptions<Operation>,
417
+ ) {
418
+ const key = indexerTypes.toId(id);
428
419
  const existing = (
429
420
  await this._index.getDetailed(key, {
430
421
  local: true,
431
- remote: { sync: true }
422
+ remote: { sync: true },
432
423
  })
433
424
  )?.[0]?.results[0];
434
425
 
435
426
  if (!existing) {
436
- throw new Error(`No entry with key '${key}' in the database`);
427
+ throw new Error(`No entry with key '${key.primitive}' in the database`);
437
428
  }
438
429
 
439
430
  return this.log.append(
440
431
  new DeleteOperation({
441
- key
432
+ key,
442
433
  }),
443
434
  {
444
435
  ...options,
445
436
  meta: {
446
437
  next: [await this._resolveEntry(existing.context.head)],
447
438
  type: EntryType.CUT,
448
- ...options?.meta
449
- }
450
- } //
439
+ ...options?.meta,
440
+ },
441
+ }, //
451
442
  );
452
443
  }
453
444
 
454
445
  async handleChanges(
455
446
  change: Change<Operation>,
456
- reference?: { document: T; operation: PutOperation }
447
+ reference?: { document: T; operation: PutOperation },
457
448
  ): Promise<void> {
458
449
  const isAppendOperation =
459
450
  change?.added.length === 1 ? !!change.added[0] : false;
460
451
 
461
- const removed = [...(change.removed || [])];
462
- const removedSet = new Map<string, Entry<Operation>>();
463
- for (const r of removed) {
452
+ const removedSet = new Map<string, ShallowOrFullEntry<Operation>>();
453
+ for (const r of change.removed) {
464
454
  removedSet.set(r.hash, r);
465
455
  }
466
- const sortedEntries = [...change.added, ...(removed || [])]
467
- .sort(this.log.log.sortFn)
468
- .reverse(); // sort so we get newest to oldest
456
+ const sortedEntries = [
457
+ ...change.added,
458
+ ...((await Promise.all(
459
+ change.removed.map((x) =>
460
+ x instanceof Entry ? x : this.log.log.entryIndex.get(x.hash),
461
+ ),
462
+ )) || []),
463
+ ]; // TODO assert sorting
464
+ /*
465
+ const sortedEntries = [...change.added, ...(removed || [])]
466
+ .sort(this.log.log.sortFn)
467
+ .reverse(); // sort so we get newest to oldest */
469
468
 
470
469
  // There might be a case where change.added and change.removed contains the same document id. Usaully because you use the "trim" option
471
470
  // in combination with inserting the same document. To mitigate this, we loop through the changes and modify the behaviour for this
472
471
 
473
472
  let documentsChanged: DocumentsChange<T> = {
474
473
  added: [],
475
- removed: []
474
+ removed: [],
476
475
  };
477
476
 
478
477
  let modified: Set<string | number | bigint> = new Set();
479
478
  for (const item of sortedEntries) {
479
+ if (!item) continue;
480
+
480
481
  try {
481
482
  const payload =
482
483
  item._payload instanceof DecryptedThing
@@ -493,7 +494,7 @@ export class Documents<T> extends Program<
493
494
  // get index key from value
494
495
  const keyObject = this.idResolver(value);
495
496
 
496
- const key = types.toId(keyObject);
497
+ const key = indexerTypes.toId(keyObject);
497
498
 
498
499
  // document is already updated with more recent entry
499
500
  if (modified.has(key.primitive)) {
@@ -505,17 +506,16 @@ export class Documents<T> extends Program<
505
506
  // if replicator, then open
506
507
  if (
507
508
  (await this.canOpen!(value, item)) &&
508
- this.log.role instanceof Replicator &&
509
- (await this.log.replicator(item)) // TODO types, throw runtime error if replicator is not provided
509
+ (await this.log.isReplicator(item)) // TODO types, throw runtime error if replicator is not provided
510
510
  ) {
511
511
  value = (await this.node.open(value, {
512
512
  parent: this as Program<any, any>,
513
- existing: "reuse"
513
+ existing: "reuse",
514
514
  })) as any as T; // TODO types
515
515
  }
516
516
  }
517
517
  documentsChanged.added.push(value);
518
- this._index.put(value, item, key);
518
+ await this._index.put(value, item, key);
519
519
  modified.add(key.primitive);
520
520
  } else if (
521
521
  (payload instanceof DeleteOperation && !removedSet.has(item.hash)) ||
@@ -525,24 +525,24 @@ export class Documents<T> extends Program<
525
525
  this._manuallySynced.delete(item.gid);
526
526
 
527
527
  let value: T;
528
- let key: string | number | bigint;
528
+ let key: indexerTypes.IdKey;
529
529
 
530
530
  if (payload instanceof PutOperation) {
531
531
  value = this.index.valueEncoding.decoder(payload.data);
532
- key = types.toIdeable(this.idResolver(value));
532
+ key = indexerTypes.toId(this.idResolver(value));
533
533
  // document is already updated with more recent entry
534
- if (modified.has(key)) {
534
+ if (modified.has(key.primitive)) {
535
535
  continue;
536
536
  }
537
537
  } else if (payload instanceof DeleteOperation) {
538
- key = payload.key.primitive;
538
+ key = payload.key;
539
539
  // document is already updated with more recent entry
540
- if (modified.has(key)) {
540
+ if (modified.has(key.primitive)) {
541
541
  continue;
542
542
  }
543
543
  const document = await this._index.get(key, {
544
544
  local: true,
545
- remote: false
545
+ remote: false,
546
546
  });
547
547
  if (!document) {
548
548
  continue;
@@ -559,11 +559,11 @@ export class Documents<T> extends Program<
559
559
  }
560
560
 
561
561
  // update index
562
- this._index.del(key);
563
-
564
- modified.add(key);
562
+ await this._index.del(key);
563
+ modified.add(key.primitive);
565
564
  } else {
566
565
  // Unknown operation
566
+ throw new OperationError("Unknown operation");
567
567
  }
568
568
  } catch (error) {
569
569
  if (error instanceof AccessError) {
@@ -574,7 +574,7 @@ export class Documents<T> extends Program<
574
574
  }
575
575
 
576
576
  this.events.dispatchEvent(
577
- new CustomEvent("change", { detail: documentsChanged })
577
+ new CustomEvent("change", { detail: documentsChanged }),
578
578
  );
579
579
  }
580
580
  }