@peerbit/log 3.0.34-efee9d3 → 4.0.0-2bc15a6

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 (99) hide show
  1. package/dist/benchmark/append.d.ts +2 -0
  2. package/dist/benchmark/append.d.ts.map +1 -0
  3. package/dist/benchmark/append.js +40 -0
  4. package/dist/benchmark/append.js.map +1 -0
  5. package/dist/benchmark/{index.d.ts.map → memory/index.d.ts.map} +1 -1
  6. package/dist/benchmark/memory/index.js +122 -0
  7. package/dist/benchmark/memory/index.js.map +1 -0
  8. package/dist/benchmark/memory/insert.d.ts +2 -0
  9. package/dist/benchmark/memory/insert.d.ts.map +1 -0
  10. package/dist/benchmark/memory/insert.js +59 -0
  11. package/dist/benchmark/memory/insert.js.map +1 -0
  12. package/dist/benchmark/memory/utils.d.ts +13 -0
  13. package/dist/benchmark/memory/utils.d.ts.map +1 -0
  14. package/dist/benchmark/memory/utils.js +2 -0
  15. package/dist/benchmark/memory/utils.js.map +1 -0
  16. package/dist/benchmark/payload.d.ts +2 -0
  17. package/dist/benchmark/payload.d.ts.map +1 -0
  18. package/dist/benchmark/{index.js → payload.js} +14 -14
  19. package/dist/benchmark/payload.js.map +1 -0
  20. package/dist/src/change.d.ts +2 -2
  21. package/dist/src/change.d.ts.map +1 -1
  22. package/dist/src/change.js +1 -1
  23. package/dist/src/change.js.map +1 -1
  24. package/dist/src/clock.d.ts +0 -24
  25. package/dist/src/clock.d.ts.map +1 -1
  26. package/dist/src/clock.js +28 -35
  27. package/dist/src/clock.js.map +1 -1
  28. package/dist/src/encoding.d.ts.map +1 -1
  29. package/dist/src/encoding.js +2 -2
  30. package/dist/src/encoding.js.map +1 -1
  31. package/dist/src/entry-index.d.ts +70 -17
  32. package/dist/src/entry-index.d.ts.map +1 -1
  33. package/dist/src/entry-index.js +281 -41
  34. package/dist/src/entry-index.js.map +1 -1
  35. package/dist/src/entry-with-refs.d.ts +1 -1
  36. package/dist/src/entry-with-refs.d.ts.map +1 -1
  37. package/dist/src/entry-with-refs.js +1 -1
  38. package/dist/src/entry-with-refs.js.map +1 -1
  39. package/dist/src/entry.d.ts +18 -15
  40. package/dist/src/entry.d.ts.map +1 -1
  41. package/dist/src/entry.js +62 -36
  42. package/dist/src/entry.js.map +1 -1
  43. package/dist/src/find-uniques.d.ts.map +1 -1
  44. package/dist/src/heads-cache.d.ts +1 -1
  45. package/dist/src/heads-cache.d.ts.map +1 -1
  46. package/dist/src/heads-cache.js +6 -7
  47. package/dist/src/heads-cache.js.map +1 -1
  48. package/dist/src/log-sorting.d.ts +27 -37
  49. package/dist/src/log-sorting.d.ts.map +1 -1
  50. package/dist/src/log-sorting.js +92 -74
  51. package/dist/src/log-sorting.js.map +1 -1
  52. package/dist/src/log.d.ts +71 -54
  53. package/dist/src/log.d.ts.map +1 -1
  54. package/dist/src/log.js +349 -468
  55. package/dist/src/log.js.map +1 -1
  56. package/dist/src/logger.d.ts.map +1 -1
  57. package/dist/src/logger.js.map +1 -1
  58. package/dist/src/snapshot.d.ts +2 -2
  59. package/dist/src/snapshot.d.ts.map +1 -1
  60. package/dist/src/snapshot.js +5 -5
  61. package/dist/src/snapshot.js.map +1 -1
  62. package/dist/src/trim.d.ts +9 -8
  63. package/dist/src/trim.d.ts.map +1 -1
  64. package/dist/src/trim.js +43 -40
  65. package/dist/src/trim.js.map +1 -1
  66. package/dist/src/utils.d.ts.map +1 -1
  67. package/dist/src/utils.js +1 -1
  68. package/dist/src/utils.js.map +1 -1
  69. package/package.json +15 -13
  70. package/src/change.ts +3 -2
  71. package/src/clock.ts +25 -19
  72. package/src/encoding.ts +3 -3
  73. package/src/entry-index.ts +451 -52
  74. package/src/entry-with-refs.ts +1 -1
  75. package/src/entry.ts +89 -72
  76. package/src/heads-cache.ts +27 -21
  77. package/src/log-sorting.ts +116 -94
  78. package/src/log.ts +465 -564
  79. package/src/logger.ts +1 -0
  80. package/src/snapshot.ts +10 -10
  81. package/src/trim.ts +75 -50
  82. package/src/utils.ts +6 -8
  83. package/dist/benchmark/index.js.map +0 -1
  84. package/dist/src/heads.d.ts +0 -70
  85. package/dist/src/heads.d.ts.map +0 -1
  86. package/dist/src/heads.js +0 -164
  87. package/dist/src/heads.js.map +0 -1
  88. package/dist/src/types.d.ts +0 -7
  89. package/dist/src/types.d.ts.map +0 -1
  90. package/dist/src/types.js +0 -21
  91. package/dist/src/types.js.map +0 -1
  92. package/dist/src/values.d.ts +0 -27
  93. package/dist/src/values.d.ts.map +0 -1
  94. package/dist/src/values.js +0 -134
  95. package/dist/src/values.js.map +0 -1
  96. package/src/heads.ts +0 -233
  97. package/src/types.ts +0 -10
  98. package/src/values.ts +0 -174
  99. /package/dist/benchmark/{index.d.ts → memory/index.d.ts} +0 -0
package/src/log.ts CHANGED
@@ -1,79 +1,80 @@
1
+ import { deserialize, field, fixedArray, variant } from "@dao-xyz/borsh";
2
+ import { type AnyStore } from "@peerbit/any-store";
3
+ import { type Blocks, cidifyString } from "@peerbit/blocks-interface";
1
4
  import {
5
+ type Identity,
2
6
  SignatureWithKey,
7
+ X25519Keypair,
3
8
  randomBytes,
4
9
  sha256Base64Sync,
5
- type Identity,
6
- X25519Keypair
7
10
  } from "@peerbit/crypto";
8
- import { Cache } from "@peerbit/cache";
9
- import { type AnyStore } from "@peerbit/any-store";
10
-
11
- import { EntryIndex } from "./entry-index.js";
12
- import * as LogError from "./log-errors.js";
13
- import * as Sorting from "./log-sorting.js";
14
- import { findUniques } from "./find-uniques.js";
15
- import {
16
- type EncryptionTemplateMaybeEncrypted,
17
- Entry,
18
- Payload,
19
- type CanAppend,
20
- EntryType
21
- } from "./entry.js";
11
+ import { type Indices } from "@peerbit/indexer-interface";
12
+ import { create } from "@peerbit/indexer-sqlite3";
13
+ import { type Keychain } from "@peerbit/keychain";
14
+ import { type Change } from "./change.js";
22
15
  import {
23
- HLC,
24
16
  LamportClock as Clock,
17
+ HLC,
25
18
  LamportClock,
26
- Timestamp
19
+ Timestamp,
27
20
  } from "./clock.js";
28
-
29
- import { deserialize, field, fixedArray, variant } from "@dao-xyz/borsh";
30
21
  import { type Encoding, NO_ENCODING } from "./encoding.js";
31
- import { type CacheUpdateOptions, HeadsIndex } from "./heads.js";
32
- import { type EntryNode, Values } from "./values.js";
33
- import { Trim, type TrimOptions } from "./trim.js";
34
- import { logger } from "./logger.js";
35
- import { type Change } from "./change.js";
22
+ import {
23
+ EntryIndex,
24
+ type MaybeResolveOptions,
25
+ type ResultsIterator,
26
+ type ReturnTypeFromResolveOptions,
27
+ } from "./entry-index.js";
36
28
  import { type EntryWithRefs } from "./entry-with-refs.js";
37
- import { type Blocks } from "@peerbit/blocks-interface";
38
- import { cidifyString } from "@peerbit/blocks";
39
- import { type Keychain } from "@peerbit/keychain";
29
+ import {
30
+ type CanAppend,
31
+ type EncryptionTemplateMaybeEncrypted,
32
+ Entry,
33
+ EntryType,
34
+ Payload,
35
+ ShallowEntry,
36
+ type ShallowOrFullEntry,
37
+ } from "./entry.js";
38
+ import { findUniques } from "./find-uniques.js";
39
+ import * as LogError from "./log-errors.js";
40
+ import * as Sorting from "./log-sorting.js";
41
+ import { Trim, type TrimOptions } from "./trim.js";
40
42
 
41
- const { LastWriteWins, NoZeroes } = Sorting;
43
+ const { LastWriteWins } = Sorting;
42
44
 
43
- export type LogEvents<T, R = undefined> = {
44
- onChange?: (change: Change<T>, reference?: R) => void;
45
+ export type LogEvents<T> = {
46
+ onChange?: (change: Change<T> /* , reference?: R */) => void;
45
47
  onGidRemoved?: (gids: string[]) => Promise<void> | void;
46
48
  };
47
49
 
48
50
  export type MemoryProperties = {
49
- cache?: AnyStore;
51
+ storage?: AnyStore;
52
+ indexer?: Indices;
50
53
  };
51
54
 
52
55
  export type LogProperties<T> = {
53
56
  keychain?: Keychain;
54
57
  encoding?: Encoding<T>;
55
58
  clock?: LamportClock;
56
- sortFn?: Sorting.ISortFunction;
59
+ sortFn?: Sorting.SortFn;
57
60
  trim?: TrimOptions;
58
61
  canAppend?: CanAppend<T>;
59
62
  };
60
63
 
61
64
  export type LogOptions<T> = LogProperties<T> & LogEvents<T> & MemoryProperties;
62
65
 
63
- const ENTRY_CACHE_MAX = 1000; // TODO as param
64
-
65
66
  export type AppendOptions<T> = {
66
67
  meta?: {
67
68
  type?: EntryType;
68
69
  gidSeed?: Uint8Array;
69
70
  data?: Uint8Array;
70
71
  timestamp?: Timestamp;
71
- next?: Entry<any>[];
72
+ next?: Entry<any>[] | ShallowEntry[];
72
73
  };
73
74
 
74
75
  identity?: Identity;
75
76
  signers?: ((
76
- data: Uint8Array
77
+ data: Uint8Array,
77
78
  ) => Promise<SignatureWithKey> | SignatureWithKey)[];
78
79
 
79
80
  trim?: TrimOptions;
@@ -87,15 +88,32 @@ export type AppendOptions<T> = {
87
88
 
88
89
  type OnChange<T> = (
89
90
  change: Change<T>,
90
- reference?: undefined
91
+ reference?: undefined,
91
92
  ) => void | Promise<void>;
92
93
 
94
+ export type JoinableEntry = {
95
+ meta: {
96
+ clock: {
97
+ timestamp: Timestamp;
98
+ };
99
+ next: string[];
100
+ gid: string;
101
+ type: EntryType;
102
+ };
103
+ hash: string;
104
+ };
105
+
106
+ export const ENTRY_JOIN_SHAPE = {
107
+ hash: true,
108
+ meta: { type: true, next: true, gid: true, clock: true },
109
+ } as const;
110
+
93
111
  @variant(0)
94
112
  export class Log<T> {
95
113
  @field({ type: fixedArray("u8", 32) })
96
114
  private _id: Uint8Array;
97
115
 
98
- private _sortFn!: Sorting.ISortFunction;
116
+ /* private _sortFn!: Sorting.ISortFunction; */
99
117
  private _storage!: Blocks;
100
118
  private _hlc!: HLC;
101
119
 
@@ -104,21 +122,22 @@ export class Log<T> {
104
122
 
105
123
  // Keeping track of entries
106
124
  private _entryIndex!: EntryIndex<T>;
107
- private _headsIndex!: HeadsIndex<T>;
108
- private _values!: Values<T>;
109
-
125
+ /* private _headsIndex!: HeadsIndex<T>;
126
+ private _values!: Values<T>;
127
+ */
110
128
  // Index of all next pointers in this log
111
- private _nextsIndex!: Map<string, Set<string>>;
129
+ /* private _nextsIndex!: Map<string, Set<string>>; */
112
130
  private _keychain?: Keychain;
113
131
  private _encoding!: Encoding<T>;
114
132
  private _trim!: Trim<T>;
115
- private _entryCache!: Cache<Entry<T>>;
116
-
117
133
  private _canAppend?: CanAppend<T>;
118
134
  private _onChange?: OnChange<T>;
119
135
  private _closed = true;
120
- private _memory?: AnyStore;
136
+ private _closeController!: AbortController;
137
+ private _loadedOnce = false;
138
+ private _indexer!: Indices;
121
139
  private _joining!: Map<string, Promise<any>>; // entry hashes that are currently joining into this log
140
+ private _sortFn!: Sorting.SortFn;
122
141
 
123
142
  constructor(properties?: { id?: Uint8Array }) {
124
143
  this._id = properties?.id || randomBytes(32);
@@ -137,20 +156,16 @@ export class Log<T> {
137
156
  throw new Error("Already open");
138
157
  }
139
158
 
140
- const { encoding, trim, keychain, cache, onGidRemoved } = options;
141
- let { sortFn } = options;
159
+ this._closeController = new AbortController();
142
160
 
143
- if (sortFn == null) {
144
- sortFn = LastWriteWins;
145
- }
146
- sortFn = sortFn as Sorting.ISortFunction;
161
+ const { encoding, trim, keychain, indexer, onGidRemoved, sortFn } = options;
162
+
163
+ // TODO do correctly with tie breaks
164
+ this._sortFn = sortFn || LastWriteWins;
147
165
 
148
- this._sortFn = NoZeroes(sortFn);
149
166
  this._storage = store;
150
- this._memory = cache;
151
- if (this._memory && (await this._memory.status()) !== "open") {
152
- await this._memory.open();
153
- }
167
+ this._indexer = indexer || (await create());
168
+ await this._indexer.start?.();
154
169
 
155
170
  this._encoding = encoding || NO_ENCODING;
156
171
  this._joining = new Map();
@@ -164,50 +179,37 @@ export class Log<T> {
164
179
  // Clock
165
180
  this._hlc = new HLC();
166
181
 
167
- this._nextsIndex = new Map();
168
182
  const id = this.id;
169
183
  if (!id) {
170
184
  throw new Error("Id not set");
171
185
  }
172
- this._headsIndex = new HeadsIndex(id);
173
- await this._headsIndex.init(this, { onGidRemoved });
174
- this._entryCache = new Cache({ max: ENTRY_CACHE_MAX });
186
+
175
187
  this._entryIndex = new EntryIndex({
176
188
  store: this._storage,
177
189
  init: (e) => e.init(this),
178
- cache: this._entryCache
190
+ onGidRemoved,
191
+ index: await (
192
+ await this._indexer.scope("heads")
193
+ ).init({ schema: ShallowEntry }),
194
+ publicKey: this._identity.publicKey,
195
+ sort: this._sortFn,
179
196
  });
180
- this._values = new Values(this._entryIndex, this._sortFn);
197
+ await this._entryIndex.init();
198
+ /* this._values = new Values(this._entryIndex, this._sortFn); */
199
+
181
200
  this._trim = new Trim(
182
201
  {
183
- headsIndex: this._headsIndex,
184
- deleteNode: async (node: EntryNode) => {
185
- // TODO check if we have before delete?
186
- const entry = await this.get(node.value);
187
- //f (!!entry)
188
- const a = this.values.length;
189
- if (entry) {
190
- this.values.deleteNode(node);
191
- await Promise.all([
192
- this.headsIndex.del(this.entryIndex.getShallow(node.value)!),
193
- this.entryIndex.delete(node.value)
194
- ]);
195
- this.nextsIndex.delete(node.value);
196
- await this.blocks.rm(node.value);
197
- }
198
- const b = this.values.length;
199
- if (a === b) {
200
- /* throw new Error(
201
- "Unexpected miss match between log size and entry index size: " +
202
- this.values.length +
203
- this.entryIndex._index.size
204
- ); */
205
- }
206
- return entry;
202
+ index: this._entryIndex,
203
+ deleteNode: async (node: ShallowEntry) => {
204
+ const resolved = await this.get(node.hash);
205
+ await this._entryIndex.delete(node.hash);
206
+ await this._storage.rm(node.hash);
207
+ return resolved;
207
208
  },
208
- values: () => this.values
209
+ sortFn: this._sortFn,
210
+ getLength: () => this.length,
209
211
  },
210
- trim
212
+ trim,
211
213
  );
212
214
 
213
215
  this._canAppend = async (entry) => {
@@ -221,6 +223,7 @@ export class Log<T> {
221
223
 
222
224
  this._onChange = options?.onChange;
223
225
  this._closed = false;
226
+ this._closeController = new AbortController();
224
227
  }
225
228
 
226
229
  private _idString: string | undefined;
@@ -251,15 +254,12 @@ export class Log<T> {
251
254
  * Returns the length of the log.
252
255
  */
253
256
  get length() {
254
- return this.values.length;
255
- }
256
-
257
- get values(): Values<T> {
258
- if (this.closed) {
257
+ if (this._closed) {
259
258
  throw new Error("Closed");
260
259
  }
261
- return this._values;
260
+ return this._entryIndex.length;
262
261
  }
262
+
263
263
  get canAppend() {
264
264
  return this._canAppend;
265
265
  }
@@ -271,41 +271,24 @@ export class Log<T> {
271
271
  */
272
272
 
273
273
  has(cid: string) {
274
- return this._entryIndex._index.has(cid);
274
+ return this._entryIndex.has(cid);
275
275
  }
276
276
  /**
277
277
  * Get all entries sorted. Don't use this method anywhere where performance matters
278
278
  */
279
- toArray(): Promise<Entry<T>[]> {
279
+ async toArray(): Promise<Entry<T>[]> {
280
280
  // we call init, because the values might be unitialized
281
- return this._values.toArray().then((arr) => arr.map((x) => x.init(this)));
281
+ return this.entryIndex.query([], this.sortFn.sort, true).all();
282
282
  }
283
283
 
284
284
  /**
285
285
  * Returns the head index
286
286
  */
287
- get headsIndex(): HeadsIndex<T> {
288
- return this._headsIndex;
289
- }
290
-
291
- get memory(): AnyStore | undefined {
292
- return this._memory;
293
- }
294
287
 
295
- async getHeads(): Promise<Entry<T>[]> {
296
- const heads: Promise<Entry<T> | undefined>[] = new Array(
297
- this.headsIndex.index.size
298
- );
299
- let i = 0;
300
- for (const hash of this.headsIndex.index) {
301
- heads[i++] = this._entryIndex.get(hash).then((x) => x?.init(this));
302
- }
303
- const resolved = await Promise.all(heads);
304
- const defined = resolved.filter((x): x is Entry<T> => !!x);
305
- if (defined.length !== resolved.length) {
306
- logger.error("Failed to resolve all heads");
307
- }
308
- return defined;
288
+ getHeads<R extends MaybeResolveOptions = false>(
289
+ resolve: R = false as R,
290
+ ): ResultsIterator<ReturnTypeFromResolveOptions<R, T>> {
291
+ return this.entryIndex.getHeads(undefined, resolve);
309
292
  }
310
293
 
311
294
  /**
@@ -341,10 +324,6 @@ export class Log<T> {
341
324
  return this._storage;
342
325
  }
343
326
 
344
- get nextsIndex(): Map<string, Set<string>> {
345
- return this._nextsIndex;
346
- }
347
-
348
327
  get entryIndex(): EntryIndex<T> {
349
328
  return this._entryIndex;
350
329
  }
@@ -374,88 +353,103 @@ export class Log<T> {
374
353
  }
375
354
 
376
355
  /**
377
- * Find an entry.
356
+ * Get an entry.
378
357
  * @param {string} [hash] The hashes of the entry
379
358
  */
380
359
  get(
381
360
  hash: string,
382
- options?: { timeout?: number }
361
+ options?: { timeout?: number },
383
362
  ): Promise<Entry<T> | undefined> {
384
- return this._entryIndex.get(hash, options);
363
+ return this._entryIndex.get(
364
+ hash,
365
+ options ? { type: "full", timeout: options.timeout } : undefined,
366
+ );
385
367
  }
386
368
 
387
- async traverse(
388
- rootEntries: Entry<T>[],
389
- amount = -1,
390
- endHash?: string
391
- ): Promise<{ [key: string]: Entry<T> }> {
392
- // Sort the given given root entries and use as the starting stack
393
- let stack: Entry<T>[] = rootEntries.sort(this._sortFn).reverse();
394
-
395
- // Cache for checking if we've processed an entry already
396
- let traversed: { [key: string]: boolean } = {};
397
- // End result
398
- const result: { [key: string]: Entry<T> } = {};
399
- let count = 0;
400
- // Named function for getting an entry from the log
401
- const getEntry = (e: string) => this.get(e);
402
-
403
- // Add an entry to the stack and traversed nodes index
404
- const addToStack = (entry: Entry<T>) => {
405
- // If we've already processed the Entry<T>, don't add it to the stack
406
- if (!entry || traversed[entry.hash]) {
407
- return;
408
- }
409
-
410
- // Add the entry in front of the stack and sort
411
- stack = [entry, ...stack].sort(this._sortFn).reverse();
412
- // Add to the cache of processed entries
413
- traversed[entry.hash] = true;
414
- };
415
-
416
- const addEntry = (rootEntry: Entry<T>) => {
417
- result[rootEntry.hash] = rootEntry;
418
- traversed[rootEntry.hash] = true;
419
- count++;
420
- };
369
+ /**
370
+ * Get a entry with shallow representation
371
+ * @param {string} [hash] The hashes of the entry
372
+ */
373
+ async getShallow(
374
+ hash: string,
375
+ options?: { timeout?: number },
376
+ ): Promise<ShallowEntry | undefined> {
377
+ return (await this._entryIndex.getShallow(hash))?.value;
378
+ }
421
379
 
422
- // Start traversal
423
- // Process stack until it's empty (traversed the full log)
424
- // or when we have the requested amount of entries
425
- // If requested entry amount is -1, traverse all
426
- while (stack.length > 0 && (count < amount || amount < 0)) {
427
- // eslint-disable-line no-unmodified-loop-condition
428
- // Get the next element from the stack
429
- const entry = stack.shift();
430
- if (!entry) {
431
- throw new Error("Unexpected");
380
+ /*
381
+ async traverse(
382
+ rootEntries: Entry<T>[],
383
+ amount = -1,
384
+ endHash?: string
385
+ ): Promise<{ [key: string]: Entry<T> }> {
386
+ // Sort the given given root entries and use as the starting stack
387
+ let stack: Entry<T>[] = rootEntries.sort(this._sortFn).reverse();
388
+
389
+ // Cache for checking if we've processed an entry already
390
+ let traversed: { [key: string]: boolean } = {};
391
+ // End result
392
+ const result: { [key: string]: Entry<T> } = {};
393
+ let count = 0;
394
+ // Named function for getting an entry from the log
395
+ const getEntry = (e: string) => this.get(e);
396
+
397
+ // Add an entry to the stack and traversed nodes index
398
+ const addToStack = (entry: Entry<T>) => {
399
+ // If we've already processed the Entry<T>, don't add it to the stack
400
+ if (!entry || traversed[entry.hash]) {
401
+ return;
402
+ }
403
+
404
+ // Add the entry in front of the stack and sort
405
+ stack = [entry, ...stack].sort(this._sortFn).reverse();
406
+ // Add to the cache of processed entries
407
+ traversed[entry.hash] = true;
408
+ };
409
+
410
+ const addEntry = (rootEntry: Entry<T>) => {
411
+ result[rootEntry.hash] = rootEntry;
412
+ traversed[rootEntry.hash] = true;
413
+ count++;
414
+ };
415
+
416
+ // Start traversal
417
+ // Process stack until it's empty (traversed the full log)
418
+ // or when we have the requested amount of entries
419
+ // If requested entry amount is -1, traverse all
420
+ while (stack.length > 0 && (count < amount || amount < 0)) {
421
+ // eslint-disable-line no-unmodified-loop-condition
422
+ // Get the next element from the stack
423
+ const entry = stack.shift();
424
+ if (!entry) {
425
+ throw new Error("Unexpected");
426
+ }
427
+ // Add to the result
428
+ addEntry(entry);
429
+ // If it is the specified end hash, break out of the while loop
430
+ if (endHash && endHash === entry.hash) break;
431
+
432
+ // Add entry's next references to the stack
433
+ const entries = (await Promise.all(entry.next.map(getEntry))).filter(
434
+ (x) => !!x
435
+ ) as Entry<any>[];
436
+ entries.forEach(addToStack);
432
437
  }
433
- // Add to the result
434
- addEntry(entry);
435
- // If it is the specified end hash, break out of the while loop
436
- if (endHash && endHash === entry.hash) break;
437
-
438
- // Add entry's next references to the stack
439
- const entries = (await Promise.all(entry.next.map(getEntry))).filter(
440
- (x) => !!x
441
- ) as Entry<any>[];
442
- entries.forEach(addToStack);
438
+
439
+ stack = [];
440
+ traversed = {};
441
+ // End result
442
+ return result;
443
443
  }
444
-
445
- stack = [];
446
- traversed = {};
447
- // End result
448
- return result;
449
- }
450
-
444
+ */
451
445
  async getReferenceSamples(
452
446
  from: Entry<T>,
453
- options?: { pointerCount?: number; memoryLimit?: number }
447
+ options?: { pointerCount?: number; memoryLimit?: number },
454
448
  ): Promise<Entry<T>[]> {
455
449
  const hashes = new Set<string>();
456
450
  const pointerCount = options?.pointerCount || 0;
457
451
  const memoryLimit = options?.memoryLimit;
458
- const maxDistance = Math.min(pointerCount, this._values.length);
452
+ const maxDistance = Math.min(pointerCount, this.entryIndex.length);
459
453
  if (maxDistance === 0) {
460
454
  return [];
461
455
  }
@@ -515,31 +509,36 @@ export class Log<T> {
515
509
 
516
510
  /**
517
511
  * Append an entry to the log.
518
- * @param {Entry} entry Entry to add
519
- * @return {Log} New Log containing the appended value
512
+ * @param {T} data The data to be appended
513
+ * @param {AppendOptions} [options] The options for the append
514
+ * @returns {{ entry: Entry<T>; removed: ShallowEntry[] }} The appended entry and an array of removed entries
520
515
  */
521
516
  async append(
522
517
  data: T,
523
- options: AppendOptions<T> = {}
524
- ): Promise<{ entry: Entry<T>; removed: Entry<T>[] }> {
518
+ options: AppendOptions<T> = {},
519
+ ): Promise<{ entry: Entry<T>; removed: ShallowOrFullEntry<T>[] }> {
525
520
  // Update the clock (find the latest clock)
526
521
  if (options.meta?.next) {
527
522
  for (const n of options.meta.next) {
528
523
  if (!n.hash)
529
524
  throw new Error(
530
- "Expecting nexts to already be saved. missing hash for one or more entries"
525
+ "Expecting nexts to already be saved. missing hash for one or more entries",
531
526
  );
532
527
  }
533
528
  }
534
529
 
535
530
  await this.load({ reload: false });
536
531
 
537
- const nexts: Entry<any>[] = options.meta?.next || (await this.getHeads());
532
+ const nexts: Sorting.SortableEntry[] =
533
+ options.meta?.next ||
534
+ (await this.entryIndex
535
+ .getHeads(undefined, { type: "shape", shape: Sorting.ENTRY_SORT_SHAPE })
536
+ .all());
538
537
 
539
538
  // Calculate max time for log/graph
540
539
  const clock = new Clock({
541
540
  id: this._identity.publicKey.bytes,
542
- timestamp: options?.meta?.timestamp || this._hlc.now()
541
+ timestamp: options?.meta?.timestamp || this._hlc.now(),
543
542
  });
544
543
 
545
544
  const entry = await Entry.create<T>({
@@ -552,45 +551,52 @@ export class Log<T> {
552
551
  type: options.meta?.type,
553
552
  gidSeed: options.meta?.gidSeed,
554
553
  data: options.meta?.data,
555
- next: nexts
554
+ next: nexts,
556
555
  },
557
556
 
558
557
  encoding: this._encoding,
559
558
  encryption: options.encryption
560
559
  ? {
561
- keypair: options.encryption.keypair,
562
- receiver: {
563
- ...options.encryption.receiver
560
+ keypair: options.encryption.keypair,
561
+ receiver: {
562
+ ...options.encryption.receiver,
563
+ },
564
564
  }
565
- }
566
565
  : undefined,
567
- canAppend: options.canAppend || this._canAppend
566
+ canAppend: options.canAppend || this._canAppend,
568
567
  });
569
568
 
570
569
  if (!entry.hash) {
571
570
  throw new Error("Unexpected");
572
571
  }
573
572
 
574
- for (const e of nexts) {
575
- if (!this.has(e.hash)) {
576
- await this.join([e]);
577
- }
578
-
579
- let nextIndexSet = this._nextsIndex.get(e.hash);
580
- if (!nextIndexSet) {
581
- nextIndexSet = new Set();
582
- nextIndexSet.add(entry.hash);
583
- this._nextsIndex.set(e.hash, nextIndexSet);
584
- } else {
585
- nextIndexSet.add(entry.hash);
573
+ if (entry.meta.type !== EntryType.CUT) {
574
+ for (const e of nexts) {
575
+ if (!(await this.has(e.hash))) {
576
+ let entry: Entry<any>;
577
+ if (e instanceof Entry) {
578
+ entry = e;
579
+ } else {
580
+ let resolved = await this.entryIndex.get(e.hash);
581
+ if (!resolved) {
582
+ // eslint-disable-next-line no-console
583
+ console.warn("Unexpected missing entry when joining", e.hash);
584
+ continue;
585
+ }
586
+ entry = resolved;
587
+ }
588
+ await this.join([entry]);
589
+ }
586
590
  }
587
591
  }
588
592
 
589
- await this._entryIndex.set(entry, false); // save === false, because its already saved when Entry.create
590
- await this._headsIndex.put(entry, { cache: { update: false } }); // we will update the cache a few lines later *
591
- await this._values.put(entry);
593
+ await this.entryIndex.put(entry, {
594
+ unique: true,
595
+ isHead: true,
596
+ toMultiHash: false,
597
+ });
592
598
 
593
- const removed = await this.processEntry(entry);
599
+ const removed: ShallowOrFullEntry<T>[] = await this.processEntry(entry);
594
600
 
595
601
  entry.init({ encoding: this._encoding, keychain: this._keychain });
596
602
 
@@ -604,29 +610,23 @@ export class Log<T> {
604
610
 
605
611
  const changes: Change<T> = {
606
612
  added: [entry],
607
- removed: removed
613
+ removed,
608
614
  };
609
615
 
610
- await this._headsIndex.updateHeadsCache(changes); // * here
611
616
  await (options?.onChange || this._onChange)?.(changes);
612
617
  return { entry, removed };
613
618
  }
614
619
 
615
- async reset(entries: Entry<T>[]) {
616
- this._nextsIndex = new Map();
617
- this._entryIndex = new EntryIndex({
618
- store: this._storage,
619
- init: (e) => e.init(this),
620
- cache: this._entryCache
621
- });
622
- await this.headsIndex.reset([]);
623
- this._values = new Values(this._entryIndex, this._sortFn, []);
624
- await this.join(entries);
620
+ async reset(entries?: Entry<T>[]) {
621
+ const heads = await this.getHeads(true).all();
622
+ await this._entryIndex.clear();
623
+ await this._onChange?.({ added: [], removed: heads });
624
+ await this.join(entries || heads);
625
625
  }
626
626
 
627
627
  async remove(
628
- entry: Entry<T> | Entry<T>[],
629
- options?: { recursively?: boolean }
628
+ entry: ShallowOrFullEntry<T> | ShallowOrFullEntry<T>[],
629
+ options?: { recursively?: boolean },
630
630
  ): Promise<Change<T>> {
631
631
  await this.load({ reload: false });
632
632
  const entries = Array.isArray(entry) ? entry : [entry];
@@ -634,32 +634,29 @@ export class Log<T> {
634
634
  if (entries.length === 0) {
635
635
  return {
636
636
  added: [],
637
- removed: []
637
+ removed: [],
638
638
  };
639
639
  }
640
640
 
641
+ const change: Change<T> = {
642
+ added: [],
643
+ removed: Array.isArray(entry) ? entry : [entry],
644
+ };
645
+
646
+ await this._onChange?.(change);
647
+
641
648
  if (options?.recursively) {
642
649
  await this.deleteRecursively(entry);
643
650
  } else {
644
651
  for (const entry of entries) {
645
- await this.delete(entry);
652
+ await this.delete(entry.hash);
646
653
  }
647
654
  }
648
655
 
649
- const change: Change<T> = {
650
- added: [],
651
- removed: Array.isArray(entry) ? entry : [entry]
652
- };
653
-
654
- /* await Promise.all([
655
- this._logCache?.queue(change),
656
- this._onUpdate(change),
657
- ]); */
658
- await this._onChange?.(change);
659
656
  return change;
660
657
  }
661
658
 
662
- iterator(options?: {
659
+ /* iterator(options?: {
663
660
  from?: "tail" | "head";
664
661
  amount?: number;
665
662
  }): IterableIterator<string> {
@@ -680,277 +677,214 @@ export class Log<T> {
680
677
  next = nextFn(next);
681
678
  }
682
679
  })();
683
- }
680
+ } */
684
681
 
685
682
  async trim(option: TrimOptions | undefined = this._trim.options) {
686
683
  return this._trim.trim(option);
687
684
  }
688
685
 
689
- /**
690
- *
691
- * @param entries
692
- * @returns change
693
- */
694
- /* async sync(
695
- entries: (EntryWithRefs<T> | Entry<T> | string)[],
696
- options: {
697
- canAppend?: CanAppend<T>;
698
- onChange?: (change: Change<T>) => void | Promise<void>;
699
- timeout?: number;
700
- } = {}
701
- ): Promise<void> {
702
-
703
-
704
- logger.debug(`Sync request #${entries.length}`);
705
- const entriesToJoin: (Entry<T> | string)[] = [];
706
- for (const e of entries) {
707
- if (e instanceof Entry || typeof e === "string") {
708
- entriesToJoin.push(e);
709
- } else {
710
- for (const ref of e.references) {
711
- entriesToJoin.push(ref);
712
- }
713
- entriesToJoin.push(e.entry);
714
- }
715
- }
716
-
717
- await this.join(entriesToJoin, {
718
- canAppend: (entry) => {
719
- const canAppend = options?.canAppend || this.canAppend;
720
- return !canAppend || canAppend(entry);
721
- },
722
- onChange: (change) => {
723
- options?.onChange?.(change);
724
- return this._onChange?.({
725
- added: change.added,
726
- removed: change.removed,
727
- });
728
- },
729
- timeout: options.timeout,
730
- });
731
- } */
732
-
733
686
  async join(
734
- entriesOrLog: (string | Entry<T> | EntryWithRefs<T>)[] | Log<T>,
687
+ entriesOrLog:
688
+ | (string | Entry<T> | ShallowEntry | EntryWithRefs<T>)[]
689
+ | Log<T>
690
+ | ResultsIterator<Entry<any>>,
735
691
  options?: {
736
692
  verifySignatures?: boolean;
737
693
  trim?: TrimOptions;
738
694
  timeout?: number;
739
- } & CacheUpdateOptions
695
+ },
740
696
  ): Promise<void> {
741
- const definedOptions = {
742
- ...options,
743
- cache: options?.cache ?? {
744
- update: true
697
+ let entries: Entry<T>[];
698
+ let references: Map<string, Entry<T>> = new Map();
699
+
700
+ if (entriesOrLog instanceof Log) {
701
+ if (entriesOrLog.entryIndex.length === 0) return;
702
+ entries = await entriesOrLog.toArray();
703
+ for (const element of entries) {
704
+ references.set(element.hash, element);
705
+ }
706
+ } else if (Array.isArray(entriesOrLog)) {
707
+ if (entriesOrLog.length === 0) {
708
+ return;
745
709
  }
746
- };
747
-
748
- await this.load({ reload: false });
749
- if (entriesOrLog.length === 0) {
750
- return;
751
- }
752
- /* const joinLength = options?.length ?? Number.MAX_SAFE_INTEGER; TODO */
753
- const visited = new Set<string>();
754
- const nextRefs: Map<string, Entry<T>[]> = new Map();
755
- const entriesBottomUp: Entry<T>[] = [];
756
- const stack: string[] = [];
757
- const resolvedEntries: Map<string, Entry<T>> = new Map();
758
- const entries = Array.isArray(entriesOrLog)
759
- ? entriesOrLog
760
- : await entriesOrLog.values.toArray();
761
-
762
- // Build a list of already resolved entries, and filter out already joined entries
763
- for (const e of entries) {
764
- // TODO, do this less ugly
765
- let hash: string;
766
- if (e instanceof Entry) {
767
- hash = e.hash;
768
- resolvedEntries.set(e.hash, e);
769
- if (this.has(hash)) {
770
- continue;
771
- }
772
- stack.push(hash);
773
- } else if (typeof e === "string") {
774
- hash = e;
775
710
 
776
- if (this.has(hash)) {
777
- continue;
778
- }
779
- stack.push(hash);
780
- } else {
781
- hash = e.entry.hash;
782
- resolvedEntries.set(e.entry.hash, e.entry);
783
- if (this.has(hash)) {
784
- continue;
785
- }
786
- stack.push(hash);
711
+ entries = [];
712
+ for (const element of entriesOrLog) {
713
+ if (element instanceof Entry) {
714
+ entries.push(element);
715
+ references.set(element.hash, element);
716
+ } else if (typeof element === "string") {
717
+ let entry = await Entry.fromMultihash<T>(this._storage, element, {
718
+ timeout: options?.timeout,
719
+ });
720
+ if (!entry) {
721
+ throw new Error("Missing entry in join by hash: " + element);
722
+ }
723
+ entries.push(entry);
724
+ } else if (element instanceof ShallowEntry) {
725
+ let entry = await Entry.fromMultihash<T>(
726
+ this._storage,
727
+ element.hash,
728
+ {
729
+ timeout: options?.timeout,
730
+ },
731
+ );
732
+ if (!entry) {
733
+ throw new Error("Missing entry in join by hash: " + element.hash);
734
+ }
735
+ entries.push(entry);
736
+ } else {
737
+ entries.push(element.entry);
738
+ references.set(element.entry.hash, element.entry);
787
739
 
788
- for (const e2 of e.references) {
789
- resolvedEntries.set(e2.hash, e2);
790
- if (this.has(e2.hash)) {
791
- continue;
740
+ for (const ref of element.references) {
741
+ references.set(ref.hash, ref);
792
742
  }
793
- stack.push(e2.hash);
794
743
  }
795
744
  }
745
+ } else {
746
+ let all = await entriesOrLog.all(); // TODO dont load all at once
747
+ if (all.length === 0) {
748
+ return;
749
+ }
750
+
751
+ entries = all;
796
752
  }
797
753
 
798
- const removedHeads: Entry<T>[] = [];
799
- for (const hash of stack) {
800
- if (visited.has(hash) || this.has(hash)) {
754
+ let heads: Map<string, boolean> = new Map();
755
+ for (const entry of entries) {
756
+ if (heads.has(entry.hash)) {
801
757
  continue;
802
758
  }
803
- visited.add(hash);
804
-
805
- const entry =
806
- resolvedEntries.get(hash) ||
807
- (await Entry.fromMultihash<T>(this._storage, hash, {
808
- timeout: options?.timeout
809
- }));
810
-
811
- entry.init(this);
812
- resolvedEntries.set(entry.hash, entry);
813
-
814
- let nexts: string[];
815
- if (
816
- entry.meta.type !== EntryType.CUT &&
817
- (nexts = await entry.getNext())
818
- ) {
819
- let isRoot = true;
820
- for (const next of nexts) {
821
- if (!this.has(next)) {
822
- isRoot = false;
823
- } else {
824
- if (this._headsIndex.has(next)) {
825
- const toRemove = (await this.get(next, options))!;
826
- await this._headsIndex.del(toRemove);
827
- removedHeads.push(toRemove);
828
- }
829
- }
830
- let nextIndexSet = nextRefs.get(next);
831
- if (!nextIndexSet) {
832
- nextIndexSet = [];
833
- nextIndexSet.push(entry);
834
- nextRefs.set(next, nextIndexSet);
835
- } else {
836
- nextIndexSet.push(entry);
837
- }
838
- if (!visited.has(next)) {
839
- stack.push(next);
840
- }
841
- }
842
- if (isRoot) {
843
- entriesBottomUp.push(entry);
844
- }
845
- } else {
846
- entriesBottomUp.push(entry);
759
+ heads.set(entry.hash, true);
760
+ for (const next of await entry.getNext()) {
761
+ heads.set(next, false);
847
762
  }
848
763
  }
849
764
 
850
- while (entriesBottomUp.length > 0) {
851
- const e = entriesBottomUp.shift()!;
852
- await this._joining.get(e.hash);
853
- const p = this.joinEntry(
854
- e,
855
- nextRefs,
856
- entriesBottomUp,
857
- definedOptions
858
- ).then(
859
- () => this._joining.delete(e.hash) // TODO, if head we run into problems with concurrency here!, we add heads at line 929 but resolve here
860
- );
861
- this._joining.set(e.hash, p);
765
+ for (const entry of entries) {
766
+ const p = this.joinRecursively(entry, {
767
+ references,
768
+ isHead: heads.get(entry.hash)!,
769
+ ...options,
770
+ });
771
+ this._joining.set(entry.hash, p);
772
+ p.finally(() => {
773
+ this._joining.delete(entry.hash);
774
+ });
862
775
  await p;
863
776
  }
864
777
  }
865
778
 
866
- private async joinEntry(
867
- e: Entry<T>,
868
- nextRefs: Map<string, Entry<T>[]>,
869
- stack: Entry<T>[],
870
- options?: {
779
+ /**
780
+ * Bottom up join of entries into the log
781
+ * @param entry
782
+ * @param options
783
+ * @returns
784
+ */
785
+
786
+ private async joinRecursively(
787
+ entry: Entry<T>,
788
+ options: {
871
789
  verifySignatures?: boolean;
872
790
  trim?: TrimOptions;
873
791
  length?: number;
874
- } & CacheUpdateOptions
875
- ): Promise<void> {
876
- if (this.length > (options?.length ?? Number.MAX_SAFE_INTEGER)) {
792
+ references?: Map<string, Entry<T>>;
793
+ isHead: boolean;
794
+ timeout?: number;
795
+ },
796
+ ) {
797
+ if (this.entryIndex.length > (options?.length ?? Number.MAX_SAFE_INTEGER)) {
877
798
  return;
878
799
  }
879
800
 
880
- if (!e.hash) {
801
+ if (!entry.hash) {
881
802
  throw new Error("Unexpected");
882
803
  }
883
804
 
884
- const headsWithGid = this.headsIndex.gids.get(e.gid);
885
- if (headsWithGid) {
886
- for (const [_k, v] of headsWithGid) {
887
- if (v.meta.type === EntryType.CUT && v.next.includes(e.hash)) {
888
- return; // already deleted
889
- }
890
- }
805
+ if (await this.has(entry.hash)) {
806
+ return;
891
807
  }
892
808
 
893
- if (!this.has(e.hash)) {
894
- if (options?.verifySignatures) {
895
- if (!(await e.verifySignatures())) {
896
- throw new Error('Invalid signature entry with hash "' + e.hash + '"');
897
- }
898
- }
809
+ entry.init(this);
899
810
 
900
- if (this?._canAppend && !(await this?._canAppend(e))) {
901
- return;
811
+ if (options?.verifySignatures) {
812
+ if (!(await entry.verifySignatures())) {
813
+ throw new Error(
814
+ 'Invalid signature entry with hash "' + entry.hash + '"',
815
+ );
902
816
  }
817
+ }
903
818
 
904
- // Update the internal entry index
905
- await this._entryIndex.set(e);
906
- await this._values.put(e);
907
-
908
- if (e.meta.type !== EntryType.CUT) {
909
- for (const a of e.next) {
910
- if (!this.has(a)) {
911
- await this.join([a]);
912
- }
819
+ if (this?._canAppend && !(await this?._canAppend(entry))) {
820
+ return;
821
+ }
913
822
 
914
- let nextIndexSet = this._nextsIndex.get(a);
915
- if (!nextIndexSet) {
916
- nextIndexSet = new Set();
917
- nextIndexSet.add(e.hash);
918
- this._nextsIndex.set(a, nextIndexSet);
919
- } else {
920
- nextIndexSet.add(a);
921
- }
823
+ const headsWithGid: JoinableEntry[] = await this.entryIndex
824
+ .getHeads(entry.gid, { type: "shape", shape: ENTRY_JOIN_SHAPE })
825
+ .all();
826
+ if (headsWithGid) {
827
+ for (const v of headsWithGid) {
828
+ // TODO second argument should be a time compare instead? what about next nexts?
829
+ // and check the cut entry is newer than the current 'entry'
830
+ if (
831
+ v.meta.type === EntryType.CUT &&
832
+ v.meta.next.includes(entry.hash) &&
833
+ Sorting.compare(entry, v, this._sortFn) < 0
834
+ ) {
835
+ return; // already deleted
922
836
  }
923
837
  }
838
+ }
924
839
 
925
- const clock = await e.getClock();
926
- this._hlc.update(clock.timestamp);
927
-
928
- const removed = await this.processEntry(e);
929
- const trimmed = await this.trim(options?.trim);
840
+ if (entry.meta.type !== EntryType.CUT) {
841
+ for (const a of entry.next) {
842
+ if (!(await this.has(a))) {
843
+ const nested =
844
+ options.references?.get(a) ||
845
+ (await Entry.fromMultihash<T>(this._storage, a, {
846
+ timeout: options?.timeout,
847
+ }));
848
+
849
+ if (!nested) {
850
+ throw new Error("Missing entry in joinRecursively: " + a);
851
+ }
930
852
 
931
- if (trimmed) {
932
- for (const entry of trimmed) {
933
- removed.push(entry);
853
+ const p = this.joinRecursively(
854
+ nested,
855
+ options.isHead ? { ...options, isHead: false } : options,
856
+ );
857
+ this._joining.set(nested.hash, p);
858
+ p.finally(() => {
859
+ this._joining.delete(nested.hash);
860
+ });
861
+ await p;
934
862
  }
935
863
  }
936
-
937
- await this?._onChange?.({ added: [e], removed: removed });
938
864
  }
939
865
 
940
- const forward = nextRefs.get(e.hash);
941
- if (forward) {
942
- if (this._headsIndex.has(e.hash)) {
943
- await this._headsIndex.del(e, options);
944
- }
945
- for (const en of forward) {
946
- stack.push(en);
866
+ const clock = await entry.getClock();
867
+ this._hlc.update(clock.timestamp);
868
+
869
+ await this._entryIndex.put(entry, {
870
+ unique: false,
871
+ isHead: options.isHead,
872
+ toMultiHash: true,
873
+ });
874
+
875
+ const removed: ShallowOrFullEntry<T>[] = await this.processEntry(entry);
876
+ const trimmed = await this.trim(options?.trim);
877
+
878
+ if (trimmed) {
879
+ for (const entry of trimmed) {
880
+ removed.push(entry);
947
881
  }
948
- } else {
949
- await this.headsIndex.put(e, options);
950
882
  }
883
+
884
+ await this?._onChange?.({ added: [entry], removed: removed });
951
885
  }
952
886
 
953
- private async processEntry(entry: Entry<T>) {
887
+ private async processEntry(entry: Entry<T>): Promise<ShallowEntry[]> {
954
888
  if (entry.meta.type === EntryType.CUT) {
955
889
  return this.deleteRecursively(entry, true);
956
890
  }
@@ -958,33 +892,40 @@ export class Log<T> {
958
892
  }
959
893
 
960
894
  /// TODO simplify methods below
961
- async deleteRecursively(from: Entry<any> | Entry<any>[], skipFirst = false) {
895
+ async deleteRecursively(
896
+ from: ShallowOrFullEntry<T> | ShallowOrFullEntry<T>[],
897
+ skipFirst = false,
898
+ ) {
962
899
  const stack = Array.isArray(from) ? [...from] : [from];
963
- const promises: Promise<void>[] = [];
900
+ const promises: (Promise<void> | void)[] = [];
964
901
  let counter = 0;
965
- const deleted: Entry<T>[] = [];
902
+ const deleted: ShallowEntry[] = [];
966
903
 
967
904
  while (stack.length > 0) {
968
905
  const entry = stack.pop()!;
969
- if ((counter > 0 || !skipFirst) && this.has(entry.hash)) {
970
- // TODO test last argument: It is for when multiple heads point to the same entry, hence we might visit it multiple times? or a concurrent delete process is doing it before us.
971
- this._trim.deleteFromCache(entry);
972
- await this._headsIndex.del(entry);
973
- await this._values.delete(entry);
974
- await this._entryIndex.delete(entry.hash);
975
-
976
- this._nextsIndex.delete(entry.hash);
977
- deleted.push(entry);
978
- promises.push(entry.delete(this._storage));
906
+ const skip = counter === 0 && skipFirst;
907
+ if (!skip) {
908
+ const has = await this.has(entry.hash);
909
+ if (has) {
910
+ // TODO test last argument: It is for when multiple heads point to the same entry, hence we might visit it multiple times? or a concurrent delete process is doing it before us.
911
+ const deletedEntry = await this.delete(entry.hash);
912
+ if (deletedEntry) {
913
+ /* this._nextsIndex.delete(entry.hash); */
914
+ deleted.push(deletedEntry);
915
+ }
916
+ }
979
917
  }
980
918
 
981
- for (const next of entry.next) {
982
- const nextFromNext = this._nextsIndex.get(next);
983
- if (nextFromNext) {
984
- nextFromNext.delete(entry.hash);
985
- }
919
+ for (const next of entry.meta.next) {
920
+ const nextFromNext = this.entryIndex.getHasNext(next);
921
+ const entriesThatHasNext = await nextFromNext.all();
986
922
 
987
- if (!nextFromNext || nextFromNext.size === 0) {
923
+ // if there are no entries which is not of "CUT" type, we can safely delete the next entry
924
+ // figureately speaking, these means where are cutting all branches to a stem, so we can delete the stem as well
925
+ let hasAlternativeNext = !!entriesThatHasNext.find(
926
+ (x) => x.meta.type !== EntryType.CUT,
927
+ );
928
+ if (!hasAlternativeNext) {
988
929
  const ne = await this.get(next);
989
930
  if (ne) {
990
931
  stack.push(ne);
@@ -997,34 +938,10 @@ export class Log<T> {
997
938
  return deleted;
998
939
  }
999
940
 
1000
- async delete(entry: Entry<any>) {
1001
- this._trim.deleteFromCache(entry);
1002
- await this._headsIndex.del(entry, {
1003
- cache: {
1004
- update: false
1005
- }
1006
- }); // cache is not updated here, but at *
1007
- await this._values.delete(entry);
1008
- await this._entryIndex.delete(entry.hash);
1009
- this._nextsIndex.delete(entry.hash);
1010
- const newHeads: string[] = [];
1011
- for (const next of entry.next) {
1012
- const ne = await this.get(next);
1013
- if (ne) {
1014
- const nexts = this._nextsIndex.get(next)!;
1015
- nexts.delete(entry.hash);
1016
- if (nexts.size === 0) {
1017
- await this._headsIndex.put(ne);
1018
- newHeads.push(ne.hash);
1019
- }
1020
- }
1021
- }
1022
- await this._headsIndex.updateHeadsCache({
1023
- added: newHeads,
1024
- removed: [entry.hash]
1025
- }); // * here
1026
-
1027
- return entry.delete(this._storage);
941
+ async delete(hash: string): Promise<ShallowEntry | undefined> {
942
+ await this._trim.deleteFromCache(hash);
943
+ const removedEntry = await this._entryIndex.delete(hash);
944
+ return removedEntry;
1028
945
  }
1029
946
 
1030
947
  /**
@@ -1037,7 +954,7 @@ export class Log<T> {
1037
954
  */
1038
955
  async toString(
1039
956
  payloadMapper: (payload: Payload<T>) => string = (payload) =>
1040
- (payload.getValue(this.encoding) as any).toString()
957
+ (payload.getValue(this.encoding) as any).toString(),
1041
958
  ): Promise<string> {
1042
959
  return (
1043
960
  await Promise.all(
@@ -1047,53 +964,42 @@ export class Log<T> {
1047
964
  .map(async (e, idx) => {
1048
965
  const parents: Entry<any>[] = Entry.findDirectChildren(
1049
966
  e,
1050
- await this.toArray()
967
+ await this.toArray(),
1051
968
  );
1052
969
  const len = parents.length;
1053
970
  let padding = new Array(Math.max(len - 1, 0));
1054
971
  padding = len > 1 ? padding.fill(" ") : padding;
1055
972
  padding = len > 0 ? padding.concat(["└─"]) : padding;
1056
- /* istanbul ignore next */
1057
973
  return (
1058
974
  padding.join("") +
1059
- (payloadMapper ? payloadMapper(e.payload) : e.payload)
975
+ (payloadMapper?.(e.payload) || (e.payload as any as string))
1060
976
  );
1061
- })
977
+ }),
1062
978
  )
1063
979
  ).join("\n");
1064
980
  }
1065
- async idle() {
1066
- await this._headsIndex.headsCache?.idle();
1067
- }
1068
981
 
1069
982
  async close() {
1070
983
  // Don't return early here if closed = true, because "load" might create processes that needs to be closed
1071
984
  this._closed = true; // closed = true before doing below, else we might try to open the headsIndex cache because it is closed as we assume log is still open
1072
- await this._entryCache?.clear();
1073
- await this._headsIndex?.close();
1074
- await this._memory?.close();
1075
- this._entryCache = undefined as any;
1076
- this._headsIndex = undefined as any;
1077
- this._memory = undefined as any;
1078
- this._values = undefined as any;
985
+ this._closeController.abort();
986
+ await this._indexer?.stop?.();
987
+ this._indexer = undefined as any;
988
+ this._loadedOnce = false;
1079
989
  }
1080
990
 
1081
991
  async drop() {
1082
992
  // Don't return early here if closed = true, because "load" might create processes that needs to be closed
1083
993
  this._closed = true; // closed = true before doing below, else we might try to open the headsIndex cache because it is closed as we assume log is still open
1084
-
1085
- await Promise.all(
1086
- [...this.entryIndex._index.values()].map((x) => this.blocks.rm(x.hash))
1087
- );
1088
- await this._headsIndex?.drop();
1089
- await this._entryCache?.clear();
1090
- await this._memory?.clear();
1091
- await this._memory?.close();
994
+ this._closeController.abort();
995
+ await this.entryIndex?.clear();
996
+ await this._indexer?.drop();
997
+ await this._indexer?.stop?.();
1092
998
  }
1093
999
 
1094
1000
  async recover() {
1095
1001
  // merge existing
1096
- const existing = await this.getHeads();
1002
+ const existing = await this.getHeads(true).all();
1097
1003
 
1098
1004
  const allHeads: Map<string, Entry<any>> = new Map();
1099
1005
  for (const head of existing) {
@@ -1129,47 +1035,42 @@ export class Log<T> {
1129
1035
  opts: {
1130
1036
  heads?: Entry<T>[];
1131
1037
  fetchEntryTimeout?: number;
1132
- reload: boolean;
1038
+ reset?: boolean;
1133
1039
  ignoreMissing?: boolean;
1134
- } = { reload: true }
1040
+ timeout?: number;
1041
+ reload?: boolean;
1042
+ } = {},
1135
1043
  ) {
1136
1044
  if (this.closed) {
1137
1045
  throw new Error("Closed");
1138
1046
  }
1139
1047
 
1048
+ if (this._loadedOnce && !opts.reload && !opts.reset) {
1049
+ return;
1050
+ }
1051
+
1052
+ this._loadedOnce = true;
1053
+
1140
1054
  const providedCustomHeads = Array.isArray(opts["heads"]);
1055
+
1141
1056
  const heads = providedCustomHeads
1142
1057
  ? (opts["heads"] as Array<Entry<T>>)
1143
- : await this.headsIndex.load({
1144
- replicate: true, // TODO this.replication.replicate(x) => true/false
1145
- timeout: opts.fetchEntryTimeout,
1146
- reload: opts.reload,
1147
- ignoreMissing: opts.ignoreMissing,
1148
- cache: { update: true, reset: true }
1149
- });
1058
+ : await this._entryIndex
1059
+ .getHeads(undefined, {
1060
+ type: "full",
1061
+ signal: this._closeController.signal,
1062
+ ignoreMissing: opts.ignoreMissing,
1063
+ timeout: opts.timeout,
1064
+ })
1065
+ .all();
1150
1066
 
1151
1067
  if (heads) {
1152
1068
  // Load the log
1153
- if (providedCustomHeads) {
1154
- await this.reset(heads);
1069
+ if (providedCustomHeads || opts.reset) {
1070
+ await this.reset(heads as any as Entry<any>[]);
1155
1071
  } else {
1156
- /*
1157
- TODO feat amount load
1158
- const amount = (opts as { amount?: number }).amount;
1159
- if (amount != null && amount >= 0 && amount < heads.length) {
1160
- throw new Error(
1161
- "You are not loading all heads, this will lead to unexpected behaviours on write. Please load at least load: " +
1162
- amount +
1163
- " entries"
1164
- );
1165
- } */
1166
-
1167
1072
  await this.join(heads instanceof Entry ? [heads] : heads, {
1168
- /* length: amount, */
1169
1073
  timeout: opts?.fetchEntryTimeout,
1170
- cache: {
1171
- update: false
1172
- }
1173
1074
  });
1174
1075
  }
1175
1076
  }
@@ -1183,14 +1084,14 @@ export class Log<T> {
1183
1084
  id?: Uint8Array;
1184
1085
  /* length?: number; TODO */
1185
1086
  timeout?: number;
1186
- } & LogOptions<T> = { id: randomBytes(32) }
1087
+ } & LogOptions<T> = { id: randomBytes(32) },
1187
1088
  ): Promise<Log<T>> {
1188
1089
  const log = new Log<T>(options.id && { id: options.id });
1189
1090
  await log.open(store, identity, options);
1190
1091
  await log.join(!Array.isArray(entryOrHash) ? [entryOrHash] : entryOrHash, {
1191
1092
  timeout: options.timeout,
1192
1093
  trim: options.trim,
1193
- verifySignatures: true
1094
+ verifySignatures: true,
1194
1095
  });
1195
1096
  return log;
1196
1097
  }
@@ -1201,13 +1102,13 @@ export class Log<T> {
1201
1102
  * Finds entries that are the heads of this collection,
1202
1103
  * ie. entries that are not referenced by other entries.
1203
1104
  *
1204
- * @param {Array<Entry<T>>} entries Entries to search heads from
1105
+ * @param {Array<Entry<T>>} entries - Entries to search heads from
1205
1106
  * @returns {Array<Entry<T>>}
1206
1107
  */
1207
1108
  static findHeads<T>(entries: Entry<T>[]) {
1208
1109
  const indexReducer = (
1209
1110
  res: { [key: string]: string },
1210
- entry: Entry<any>
1111
+ entry: Entry<any>,
1211
1112
  ) => {
1212
1113
  const addToResult = (e: string) => (res[e] = entry.hash);
1213
1114
  entry.next.forEach(addToResult);
@@ -1256,7 +1157,7 @@ export class Log<T> {
1256
1157
  res: Entry<T>[],
1257
1158
  entries: Entry<T>[],
1258
1159
  _idx: any,
1259
- _arr: any
1160
+ _arr: any,
1260
1161
  ) => res.concat(findUniques(entries, "hash"));
1261
1162
  const exists = (e: string) => hashes[e] === undefined;
1262
1163
  const findFromReverseIndex = (e: string) => reverseIndex[e];
@@ -1280,7 +1181,7 @@ export class Log<T> {
1280
1181
  res: string[],
1281
1182
  entry: Entry<any>,
1282
1183
  idx: number,
1283
- arr: Entry<any>[]
1184
+ arr: Entry<any>[],
1284
1185
  ) => {
1285
1186
  const addToResult = (e: string) => {
1286
1187
  /* istanbul ignore else */