@peerbit/document 7.0.13 → 7.0.14-ccaf4f4

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/package.json CHANGED
@@ -1,76 +1,76 @@
1
1
  {
2
- "name": "@peerbit/document",
3
- "version": "7.0.13",
4
- "description": "Document store implementation",
5
- "type": "module",
6
- "sideEffects": false,
7
- "types": "./dist/src/index.d.ts",
8
- "typesVersions": {
9
- "*": {
10
- "*": [
11
- "*",
12
- "dist/*",
13
- "dist/src/*",
14
- "dist/src/*/index"
15
- ],
16
- "src/*": [
17
- "*",
18
- "dist/*",
19
- "dist/src/*",
20
- "dist/src/*/index"
21
- ]
22
- }
23
- },
24
- "files": [
25
- "src",
26
- "dist",
27
- "!dist/e2e",
28
- "!dist/test",
29
- "!**/*.tsbuildinfo"
30
- ],
31
- "exports": {
32
- ".": {
33
- "types": "./dist/src/index.d.ts",
34
- "import": "./dist/src/index.js"
35
- }
36
- },
37
- "eslintConfig": {
38
- "extends": "peerbit",
39
- "parserOptions": {
40
- "project": true,
41
- "sourceType": "module"
42
- },
43
- "ignorePatterns": [
44
- "!.aegir.js",
45
- "test/ts-use",
46
- "*.d.ts"
47
- ]
48
- },
49
- "publishConfig": {
50
- "access": "public"
51
- },
52
- "scripts": {
53
- "clean": "aegir clean",
54
- "build": "aegir build --no-bundle",
55
- "test": "aegir test --target node",
56
- "lint": "aegir lint"
57
- },
58
- "author": "dao.xyz",
59
- "license": "MIT",
60
- "dependencies": {
61
- "@dao-xyz/borsh": "^5.2.3",
62
- "@peerbit/program": "5.0.1",
63
- "@peerbit/rpc": "5.0.7",
64
- "@peerbit/shared-log": "9.0.9",
65
- "@peerbit/indexer-interface": "^1.0.2",
66
- "@peerbit/indexer-simple": "^1.0.3",
67
- "@peerbit/indexer-sqlite3": "^1.0.5",
68
- "@peerbit/document-interface": "^1.0.3"
69
- },
70
- "devDependencies": {
71
- "@peerbit/test-utils": "2.0.41",
72
- "@peerbit/time": "2.0.7",
73
- "@types/pidusage": "^2.0.5",
74
- "pidusage": "^3.0.2"
75
- }
2
+ "name": "@peerbit/document",
3
+ "version": "7.0.14-ccaf4f4",
4
+ "description": "Document store implementation",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "types": "./dist/src/index.d.ts",
8
+ "typesVersions": {
9
+ "*": {
10
+ "*": [
11
+ "*",
12
+ "dist/*",
13
+ "dist/src/*",
14
+ "dist/src/*/index"
15
+ ],
16
+ "src/*": [
17
+ "*",
18
+ "dist/*",
19
+ "dist/src/*",
20
+ "dist/src/*/index"
21
+ ]
22
+ }
23
+ },
24
+ "files": [
25
+ "src",
26
+ "dist",
27
+ "!dist/e2e",
28
+ "!dist/test",
29
+ "!**/*.tsbuildinfo"
30
+ ],
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/src/index.d.ts",
34
+ "import": "./dist/src/index.js"
35
+ }
36
+ },
37
+ "eslintConfig": {
38
+ "extends": "peerbit",
39
+ "parserOptions": {
40
+ "project": true,
41
+ "sourceType": "module"
42
+ },
43
+ "ignorePatterns": [
44
+ "!.aegir.js",
45
+ "test/ts-use",
46
+ "*.d.ts"
47
+ ]
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "scripts": {
53
+ "clean": "aegir clean",
54
+ "build": "aegir build --no-bundle",
55
+ "test": "aegir test --target node",
56
+ "lint": "aegir lint"
57
+ },
58
+ "author": "dao.xyz",
59
+ "license": "MIT",
60
+ "dependencies": {
61
+ "@dao-xyz/borsh": "^5.2.3",
62
+ "@peerbit/program": "5.0.1-ccaf4f4",
63
+ "@peerbit/rpc": "5.0.7-ccaf4f4",
64
+ "@peerbit/shared-log": "9.0.10-ccaf4f4",
65
+ "@peerbit/indexer-interface": "1.0.2-ccaf4f4",
66
+ "@peerbit/indexer-simple": "1.0.3-ccaf4f4",
67
+ "@peerbit/indexer-sqlite3": "1.0.5-ccaf4f4",
68
+ "@peerbit/document-interface": "1.0.3-ccaf4f4"
69
+ },
70
+ "devDependencies": {
71
+ "@peerbit/test-utils": "2.0.41-ccaf4f4",
72
+ "@peerbit/time": "2.0.7-ccaf4f4",
73
+ "@types/pidusage": "^2.0.5",
74
+ "pidusage": "^3.0.2"
75
+ }
76
76
  }
package/src/index.ts CHANGED
@@ -2,14 +2,10 @@ export * from "@peerbit/indexer-interface";
2
2
  export * from "./program.js";
3
3
  export type {
4
4
  CanRead,
5
- BORSH_ENCODING_OPERATION,
6
5
  CanSearch,
7
- DeleteOperation,
8
6
  DocumentIndex,
9
7
  IDocumentWithContext,
10
8
  OpenOptions,
11
- Operation,
12
- PutOperation,
13
9
  QueryOptions,
14
10
  RemoteQueryOptions,
15
11
  ResultsIterator,
@@ -18,5 +14,6 @@ export type {
18
14
  TransformerAsConstructor,
19
15
  TransformerAsFunction,
20
16
  } from "./search.js";
17
+ export * from "./operation.js";
21
18
  export { MAX_BATCH_SIZE as MAX_DOCUMENT_SIZE } from "./constants.js";
22
19
  export { ClosedError } from "@peerbit/program";
@@ -0,0 +1,106 @@
1
+ import { field, variant } from "@dao-xyz/borsh";
2
+ import * as indexerTypes from "@peerbit/indexer-interface";
3
+ import { BORSH_ENCODING } from "@peerbit/log";
4
+
5
+ @variant(0)
6
+ export class Operation /* <T> */ {}
7
+
8
+ export const BORSH_ENCODING_OPERATION = BORSH_ENCODING(Operation);
9
+
10
+ // @deprecated
11
+ @variant(0)
12
+ export class PutWithKeyOperation extends Operation {
13
+ @field({ type: "string" })
14
+ key: string;
15
+
16
+ @field({ type: Uint8Array })
17
+ data: Uint8Array;
18
+
19
+ constructor(props: { key: string; data: Uint8Array }) {
20
+ super();
21
+ this.key = props.key;
22
+ this.data = props.data;
23
+ }
24
+ }
25
+
26
+ // @deprecated
27
+ /* @variant(1)
28
+ export class PutAllOperation<T> extends Operation<T> {
29
+ @field({ type: vec(PutOperation) })
30
+ docs: PutOperation<T>[];
31
+
32
+ constructor(props?: { docs: PutOperation<T>[] }) {
33
+ super();
34
+ if (props) {
35
+ this.docs = props.docs;
36
+ }
37
+ }
38
+ }
39
+ */
40
+
41
+ // @deprecated
42
+ @variant(2)
43
+ export class DeleteByStringKeyOperation extends Operation {
44
+ @field({ type: "string" })
45
+ key: string;
46
+
47
+ constructor(props: { key: string }) {
48
+ super();
49
+ this.key = props.key;
50
+ }
51
+
52
+ toDeleteOperation(): DeleteOperation {
53
+ return new DeleteOperation({ key: indexerTypes.toId(this.key) });
54
+ }
55
+ }
56
+
57
+ export const coerceDeleteOperation = (
58
+ operation: DeleteOperation | DeleteByStringKeyOperation,
59
+ ): DeleteOperation => {
60
+ return operation instanceof DeleteByStringKeyOperation
61
+ ? operation.toDeleteOperation()
62
+ : operation;
63
+ };
64
+
65
+ @variant(3)
66
+ export class PutOperation extends Operation {
67
+ @field({ type: Uint8Array })
68
+ data: Uint8Array;
69
+
70
+ constructor(props: { data: Uint8Array }) {
71
+ super();
72
+ this.data = props.data;
73
+ }
74
+ }
75
+
76
+ export const isPutOperation = (
77
+ operation: Operation,
78
+ ): operation is PutOperation | PutWithKeyOperation => {
79
+ return (
80
+ operation instanceof PutOperation ||
81
+ operation instanceof PutWithKeyOperation
82
+ );
83
+ };
84
+
85
+ /**
86
+ * Delete a document at a key
87
+ */
88
+ @variant(4)
89
+ export class DeleteOperation extends Operation {
90
+ @field({ type: indexerTypes.IdKey })
91
+ key: indexerTypes.IdKey;
92
+
93
+ constructor(props: { key: indexerTypes.IdKey }) {
94
+ super();
95
+ this.key = props.key;
96
+ }
97
+ }
98
+
99
+ export const isDeleteOperation = (
100
+ operation: Operation,
101
+ ): operation is DeleteOperation | DeleteByStringKeyOperation => {
102
+ return (
103
+ operation instanceof DeleteOperation ||
104
+ operation instanceof DeleteByStringKeyOperation
105
+ );
106
+ };
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:
@@ -314,25 +317,47 @@ export class Documents<
314
317
 
315
318
  const key = indexerTypes.toId(keyValue);
316
319
 
317
- const existingDocument = (await this.index.getDetailed(key))?.[0]
318
- ?.results[0];
320
+ const existingDocument = (
321
+ await this.index.getDetailed(key, {
322
+ local: true,
323
+ remote: this.immutable,
324
+ })
325
+ )?.[0]?.results[0];
319
326
  if (existingDocument && existingDocument.context.head !== entry.hash) {
320
327
  // econd condition can false if we reset the operation log, while not resetting the index. For example when doing .recover
321
328
  if (this.immutable) {
322
- //Key already exist and this instance Documents can note overrite/edit'
323
- return false;
324
- }
329
+ // key already exist but pick the oldest entry
330
+ // this is because we can not overwrite same id if immutable
331
+ if (
332
+ existingDocument.context.created <
333
+ entry.meta.clock.timestamp.wallTime
334
+ ) {
335
+ return false;
336
+ }
325
337
 
326
- if (entry.meta.next.length !== 1) {
327
- return false;
328
- }
329
- let doc = await this.log.log.get(existingDocument.context.head);
330
- if (!doc) {
331
- logger.error("Failed to find Document from head");
332
- return false;
338
+ if (entry.meta.next.length > 0) {
339
+ return false; // can not append to immutable document
340
+ }
341
+
342
+ return putOperation;
343
+ } else {
344
+ if (entry.meta.next.length !== 1) {
345
+ return false;
346
+ }
347
+
348
+ const prevEntry = await this.log.log.entryIndex.get(
349
+ existingDocument.context.head,
350
+ );
351
+ if (!prevEntry) {
352
+ logger.error(
353
+ "Failed to find previous entry for document edit: " +
354
+ entry.hash,
355
+ );
356
+ return false;
357
+ }
358
+ const referenceHistoryCorrectly = await pointsToHistory(prevEntry);
359
+ return referenceHistoryCorrectly ? putOperation : false;
333
360
  }
334
- const referenceHistoryCorrectly = await pointsToHistory(doc);
335
- return referenceHistoryCorrectly ? putOperation : false;
336
361
  } else {
337
362
  if (entry.meta.next.length !== 0) {
338
363
  return false;
@@ -343,7 +368,10 @@ export class Documents<
343
368
  return false;
344
369
  }
345
370
  const existingDocument = (
346
- await this.index.getDetailed(operation.key)
371
+ await this.index.getDetailed(operation.key, {
372
+ local: true,
373
+ remote: this.immutable,
374
+ })
347
375
  )?.[0]?.results[0];
348
376
 
349
377
  if (!existingDocument) {
@@ -378,7 +406,10 @@ export class Documents<
378
406
 
379
407
  public async put(
380
408
  doc: T,
381
- options?: SharedAppendOptions<Operation> & { unique?: boolean },
409
+ options?: SharedAppendOptions<Operation> & {
410
+ unique?: boolean;
411
+ replicate?: boolean;
412
+ },
382
413
  ) {
383
414
  const keyValue = this.idResolver(doc);
384
415
 
@@ -399,7 +430,7 @@ export class Documents<
399
430
  : (
400
431
  await this._index.getDetailed(keyValue, {
401
432
  local: true,
402
- remote: { sync: true }, // only query remote if we know they exist
433
+ remote: { replicate: options?.replicate }, // only query remote if we know they exist
403
434
  })
404
435
  )?.[0]?.results[0];
405
436
 
@@ -433,6 +464,7 @@ export class Documents<
433
464
  onChange: (change) => {
434
465
  return this.handleChanges(change, { document: doc, operation });
435
466
  },
467
+ replicate: options?.replicate,
436
468
  });
437
469
 
438
470
  return appended;
@@ -446,7 +478,7 @@ export class Documents<
446
478
  const existing = (
447
479
  await this._index.getDetailed(key, {
448
480
  local: true,
449
- remote: { sync: true },
481
+ remote: { replicate: options?.replicate },
450
482
  })
451
483
  )?.[0]?.results[0];
452
484
 
@@ -454,6 +486,7 @@ export class Documents<
454
486
  throw new Error(`No entry with key '${key.primitive}' in the database`);
455
487
  }
456
488
 
489
+ const entry = await this._resolveEntry(existing.context.head);
457
490
  return this.log.append(
458
491
  new DeleteOperation({
459
492
  key,
@@ -461,7 +494,7 @@ export class Documents<
461
494
  {
462
495
  ...options,
463
496
  meta: {
464
- next: [await this._resolveEntry(existing.context.head)],
497
+ next: [entry],
465
498
  type: EntryType.CUT,
466
499
  ...options?.meta,
467
500
  },
@@ -482,7 +515,7 @@ export class Documents<
482
515
  }
483
516
 
484
517
  const sortedEntries = [
485
- ...change.added,
518
+ ...change.added.map((x) => x.entry),
486
519
  ...((await Promise.all(
487
520
  change.removed.map((x) =>
488
521
  x instanceof Entry ? x : this.log.log.entryIndex.get(x.hash),
@@ -520,7 +553,6 @@ export class Documents<
520
553
 
521
554
  // get index key from value
522
555
  const keyObject = this.idResolver(value);
523
-
524
556
  const key = indexerTypes.toId(keyObject);
525
557
 
526
558
  // document is already updated with more recent entry
@@ -549,8 +581,6 @@ export class Documents<
549
581
  isPutOperation(payload) ||
550
582
  removedSet.has(item.hash)
551
583
  ) {
552
- this._manuallySynced.delete(item.meta.gid);
553
-
554
584
  let value: T;
555
585
  let key: indexerTypes.IdKey;
556
586