@peerbit/log 3.0.34 → 4.0.0-55cebfe

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