@peerbit/log 3.0.34-aa577a5 → 3.0.34-cccc078

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/dist/src/log.js CHANGED
@@ -8,50 +8,53 @@ var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
10
  var Log_1;
11
- import { SignatureWithKey, randomBytes, sha256Base64Sync, X25519Keypair } from "@peerbit/crypto";
12
- import { Cache } from "@peerbit/cache";
11
+ import { deserialize, field, fixedArray, variant } from "@dao-xyz/borsh";
13
12
  import {} from "@peerbit/any-store";
14
- import { EntryIndex } from "./entry-index.js";
13
+ import { cidifyString } from "@peerbit/blocks-interface";
14
+ import { SignatureWithKey, X25519Keypair, randomBytes, sha256Base64Sync, } from "@peerbit/crypto";
15
+ import {} from "@peerbit/indexer-interface";
16
+ import { create } from "@peerbit/indexer-sqlite3";
17
+ import {} from "@peerbit/keychain";
18
+ import {} from "./change.js";
19
+ import { LamportClock as Clock, HLC, LamportClock, Timestamp, } from "./clock.js";
20
+ import { NO_ENCODING } from "./encoding.js";
21
+ import { EntryIndex, } from "./entry-index.js";
22
+ import {} from "./entry-with-refs.js";
23
+ import { Entry, EntryType, Payload, ShallowEntry, } from "./entry.js";
24
+ import { findUniques } from "./find-uniques.js";
15
25
  import * as LogError from "./log-errors.js";
16
26
  import * as Sorting from "./log-sorting.js";
17
- import { findUniques } from "./find-uniques.js";
18
- import { Entry, Payload, EntryType } from "./entry.js";
19
- import { HLC, LamportClock as Clock, LamportClock, Timestamp } from "./clock.js";
20
- import { deserialize, field, fixedArray, variant } from "@dao-xyz/borsh";
21
- import { NO_ENCODING } from "./encoding.js";
22
- import { HeadsIndex } from "./heads.js";
23
- import { Values } from "./values.js";
24
27
  import { Trim } from "./trim.js";
25
- import { logger } from "./logger.js";
26
- import {} from "./change.js";
27
- import {} from "./entry-with-refs.js";
28
- import {} from "@peerbit/blocks-interface";
29
- import { cidifyString } from "@peerbit/blocks";
30
- import {} from "@peerbit/keychain";
31
- const { LastWriteWins, NoZeroes } = Sorting;
32
- const ENTRY_CACHE_MAX = 1000; // TODO as param
28
+ const { LastWriteWins } = Sorting;
29
+ export const ENTRY_JOIN_SHAPE = {
30
+ hash: true,
31
+ meta: { type: true, next: true, gid: true, clock: true },
32
+ };
33
33
  let Log = Log_1 = class Log {
34
34
  _id;
35
- _sortFn;
35
+ /* private _sortFn!: Sorting.ISortFunction; */
36
36
  _storage;
37
37
  _hlc;
38
38
  // Identity
39
39
  _identity;
40
40
  // Keeping track of entries
41
41
  _entryIndex;
42
- _headsIndex;
43
- _values;
42
+ /* private _headsIndex!: HeadsIndex<T>;
43
+ private _values!: Values<T>;
44
+ */
44
45
  // Index of all next pointers in this log
45
- _nextsIndex;
46
+ /* private _nextsIndex!: Map<string, Set<string>>; */
46
47
  _keychain;
47
48
  _encoding;
48
49
  _trim;
49
- _entryCache;
50
50
  _canAppend;
51
51
  _onChange;
52
52
  _closed = true;
53
- _memory;
53
+ _closeController;
54
+ _loadedOnce = false;
55
+ _indexer;
54
56
  _joining; // entry hashes that are currently joining into this log
57
+ _sortFn;
55
58
  constructor(properties) {
56
59
  this._id = properties?.id || randomBytes(32);
57
60
  }
@@ -65,18 +68,13 @@ let Log = Log_1 = class Log {
65
68
  if (this.closed === false) {
66
69
  throw new Error("Already open");
67
70
  }
68
- const { encoding, trim, keychain, cache, onGidRemoved } = options;
69
- let { sortFn } = options;
70
- if (sortFn == null) {
71
- sortFn = LastWriteWins;
72
- }
73
- sortFn = sortFn;
74
- this._sortFn = NoZeroes(sortFn);
71
+ this._closeController = new AbortController();
72
+ const { encoding, trim, keychain, indexer, onGidRemoved, sortFn } = options;
73
+ // TODO do correctly with tie breaks
74
+ this._sortFn = sortFn || LastWriteWins;
75
75
  this._storage = store;
76
- this._memory = cache;
77
- if (this._memory && (await this._memory.status()) !== "open") {
78
- await this._memory.open();
79
- }
76
+ this._indexer = indexer || (await create());
77
+ await this._indexer.start?.();
80
78
  this._encoding = encoding || NO_ENCODING;
81
79
  this._joining = new Map();
82
80
  // Identity
@@ -85,47 +83,30 @@ let Log = Log_1 = class Log {
85
83
  this._keychain = keychain;
86
84
  // Clock
87
85
  this._hlc = new HLC();
88
- this._nextsIndex = new Map();
89
86
  const id = this.id;
90
87
  if (!id) {
91
88
  throw new Error("Id not set");
92
89
  }
93
- this._headsIndex = new HeadsIndex(id);
94
- await this._headsIndex.init(this, { onGidRemoved });
95
- this._entryCache = new Cache({ max: ENTRY_CACHE_MAX });
96
90
  this._entryIndex = new EntryIndex({
97
91
  store: this._storage,
98
92
  init: (e) => e.init(this),
99
- cache: this._entryCache
93
+ onGidRemoved,
94
+ index: await (await this._indexer.scope("heads")).init({ schema: ShallowEntry }),
95
+ publicKey: this._identity.publicKey,
96
+ sort: this._sortFn,
100
97
  });
101
- this._values = new Values(this._entryIndex, this._sortFn);
98
+ await this._entryIndex.init();
99
+ /* this._values = new Values(this._entryIndex, this._sortFn); */
102
100
  this._trim = new Trim({
103
- headsIndex: this._headsIndex,
101
+ index: this._entryIndex,
104
102
  deleteNode: async (node) => {
105
- // TODO check if we have before delete?
106
- const entry = await this.get(node.value);
107
- //f (!!entry)
108
- const a = this.values.length;
109
- if (entry) {
110
- this.values.deleteNode(node);
111
- await Promise.all([
112
- this.headsIndex.del(this.entryIndex.getShallow(node.value)),
113
- this.entryIndex.delete(node.value)
114
- ]);
115
- this.nextsIndex.delete(node.value);
116
- await this.blocks.rm(node.value);
117
- }
118
- const b = this.values.length;
119
- if (a === b) {
120
- /* throw new Error(
121
- "Unexpected miss match between log size and entry index size: " +
122
- this.values.length +
123
- this.entryIndex._index.size
124
- ); */
125
- }
126
- return entry;
103
+ const resolved = await this.get(node.hash);
104
+ await this._entryIndex.delete(node.hash);
105
+ await this._storage.rm(node.hash);
106
+ return resolved;
127
107
  },
128
- values: () => this.values
108
+ sortFn: this._sortFn,
109
+ getLength: () => this.length,
129
110
  }, trim);
130
111
  this._canAppend = async (entry) => {
131
112
  if (options?.canAppend) {
@@ -137,6 +118,7 @@ let Log = Log_1 = class Log {
137
118
  };
138
119
  this._onChange = options?.onChange;
139
120
  this._closed = false;
121
+ this._closeController = new AbortController();
140
122
  }
141
123
  _idString;
142
124
  get idString() {
@@ -162,13 +144,10 @@ let Log = Log_1 = class Log {
162
144
  * Returns the length of the log.
163
145
  */
164
146
  get length() {
165
- return this.values.length;
166
- }
167
- get values() {
168
- if (this.closed) {
147
+ if (this._closed) {
169
148
  throw new Error("Closed");
170
149
  }
171
- return this._values;
150
+ return this._entryIndex.length;
172
151
  }
173
152
  get canAppend() {
174
153
  return this._canAppend;
@@ -179,36 +158,20 @@ let Log = Log_1 = class Log {
179
158
  * @returns {boolean}
180
159
  */
181
160
  has(cid) {
182
- return this._entryIndex._index.has(cid);
161
+ return this._entryIndex.has(cid);
183
162
  }
184
163
  /**
185
164
  * Get all entries sorted. Don't use this method anywhere where performance matters
186
165
  */
187
- toArray() {
166
+ async toArray() {
188
167
  // we call init, because the values might be unitialized
189
- return this._values.toArray().then((arr) => arr.map((x) => x.init(this)));
168
+ return this.entryIndex.query([], this.sortFn.sort, true).all();
190
169
  }
191
170
  /**
192
171
  * Returns the head index
193
172
  */
194
- get headsIndex() {
195
- return this._headsIndex;
196
- }
197
- get memory() {
198
- return this._memory;
199
- }
200
- async getHeads() {
201
- const heads = new Array(this.headsIndex.index.size);
202
- let i = 0;
203
- for (const hash of this.headsIndex.index) {
204
- heads[i++] = this._entryIndex.get(hash).then((x) => x?.init(this));
205
- }
206
- const resolved = await Promise.all(heads);
207
- const defined = resolved.filter((x) => !!x);
208
- if (defined.length !== resolved.length) {
209
- logger.error("Failed to resolve all heads");
210
- }
211
- return defined;
173
+ getHeads(resolve = false) {
174
+ return this.entryIndex.getHeads(undefined, resolve);
212
175
  }
213
176
  /**
214
177
  * Returns an array of Entry objects that reference entries which
@@ -238,9 +201,6 @@ let Log = Log_1 = class Log {
238
201
  get blocks() {
239
202
  return this._storage;
240
203
  }
241
- get nextsIndex() {
242
- return this._nextsIndex;
243
- }
244
204
  get entryIndex() {
245
205
  return this._entryIndex;
246
206
  }
@@ -264,68 +224,89 @@ let Log = Log_1 = class Log {
264
224
  this._identity = identity;
265
225
  }
266
226
  /**
267
- * Find an entry.
227
+ * Get an entry.
268
228
  * @param {string} [hash] The hashes of the entry
269
229
  */
270
230
  get(hash, options) {
271
- return this._entryIndex.get(hash, options);
231
+ return this._entryIndex.get(hash, options ? { type: "full", timeout: options.timeout } : undefined);
272
232
  }
273
- async traverse(rootEntries, amount = -1, endHash) {
274
- // Sort the given given root entries and use as the starting stack
275
- let stack = rootEntries.sort(this._sortFn).reverse();
276
- // Cache for checking if we've processed an entry already
277
- let traversed = {};
278
- // End result
279
- const result = {};
280
- let count = 0;
281
- // Named function for getting an entry from the log
282
- const getEntry = (e) => this.get(e);
283
- // Add an entry to the stack and traversed nodes index
284
- const addToStack = (entry) => {
285
- // If we've already processed the Entry<T>, don't add it to the stack
286
- if (!entry || traversed[entry.hash]) {
287
- return;
288
- }
289
- // Add the entry in front of the stack and sort
290
- stack = [entry, ...stack].sort(this._sortFn).reverse();
291
- // Add to the cache of processed entries
292
- traversed[entry.hash] = true;
293
- };
294
- const addEntry = (rootEntry) => {
295
- result[rootEntry.hash] = rootEntry;
296
- traversed[rootEntry.hash] = true;
297
- count++;
298
- };
299
- // Start traversal
300
- // Process stack until it's empty (traversed the full log)
301
- // or when we have the requested amount of entries
302
- // If requested entry amount is -1, traverse all
303
- while (stack.length > 0 && (count < amount || amount < 0)) {
304
- // eslint-disable-line no-unmodified-loop-condition
305
- // Get the next element from the stack
306
- const entry = stack.shift();
307
- if (!entry) {
308
- throw new Error("Unexpected");
309
- }
310
- // Add to the result
311
- addEntry(entry);
312
- // If it is the specified end hash, break out of the while loop
313
- if (endHash && endHash === entry.hash)
314
- break;
315
- // Add entry's next references to the stack
316
- const entries = (await Promise.all(entry.next.map(getEntry))).filter((x) => !!x);
317
- entries.forEach(addToStack);
233
+ /**
234
+ * Get a entry with shallow representation
235
+ * @param {string} [hash] The hashes of the entry
236
+ */
237
+ async getShallow(hash, options) {
238
+ return (await this._entryIndex.getShallow(hash))?.value;
239
+ }
240
+ /*
241
+ async traverse(
242
+ rootEntries: Entry<T>[],
243
+ amount = -1,
244
+ endHash?: string
245
+ ): Promise<{ [key: string]: Entry<T> }> {
246
+ // Sort the given given root entries and use as the starting stack
247
+ let stack: Entry<T>[] = rootEntries.sort(this._sortFn).reverse();
248
+
249
+ // Cache for checking if we've processed an entry already
250
+ let traversed: { [key: string]: boolean } = {};
251
+ // End result
252
+ const result: { [key: string]: Entry<T> } = {};
253
+ let count = 0;
254
+ // Named function for getting an entry from the log
255
+ const getEntry = (e: string) => this.get(e);
256
+
257
+ // Add an entry to the stack and traversed nodes index
258
+ const addToStack = (entry: Entry<T>) => {
259
+ // If we've already processed the Entry<T>, don't add it to the stack
260
+ if (!entry || traversed[entry.hash]) {
261
+ return;
262
+ }
263
+
264
+ // Add the entry in front of the stack and sort
265
+ stack = [entry, ...stack].sort(this._sortFn).reverse();
266
+ // Add to the cache of processed entries
267
+ traversed[entry.hash] = true;
268
+ };
269
+
270
+ const addEntry = (rootEntry: Entry<T>) => {
271
+ result[rootEntry.hash] = rootEntry;
272
+ traversed[rootEntry.hash] = true;
273
+ count++;
274
+ };
275
+
276
+ // Start traversal
277
+ // Process stack until it's empty (traversed the full log)
278
+ // or when we have the requested amount of entries
279
+ // If requested entry amount is -1, traverse all
280
+ while (stack.length > 0 && (count < amount || amount < 0)) {
281
+ // eslint-disable-line no-unmodified-loop-condition
282
+ // Get the next element from the stack
283
+ const entry = stack.shift();
284
+ if (!entry) {
285
+ throw new Error("Unexpected");
286
+ }
287
+ // Add to the result
288
+ addEntry(entry);
289
+ // If it is the specified end hash, break out of the while loop
290
+ if (endHash && endHash === entry.hash) break;
291
+
292
+ // Add entry's next references to the stack
293
+ const entries = (await Promise.all(entry.next.map(getEntry))).filter(
294
+ (x) => !!x
295
+ ) as Entry<any>[];
296
+ entries.forEach(addToStack);
297
+ }
298
+
299
+ stack = [];
300
+ traversed = {};
301
+ // End result
302
+ return result;
318
303
  }
319
- stack = [];
320
- traversed = {};
321
- // End result
322
- return result;
323
- }
304
+ */
324
305
  async getReferenceSamples(from, options) {
325
306
  const hashes = new Set();
326
307
  const pointerCount = options?.pointerCount || 0;
327
308
  const memoryLimit = options?.memoryLimit;
328
- const maxDistance = Math.min(pointerCount, this._values.length);
309
+ const maxDistance = Math.min(pointerCount, this.entryIndex.length);
329
310
  if (maxDistance === 0) {
330
311
  return [];
331
312
  }
@@ -383,8 +364,9 @@ let Log = Log_1 = class Log {
383
364
  }
384
365
  /**
385
366
  * Append an entry to the log.
386
- * @param {Entry} entry Entry to add
387
- * @return {Log} New Log containing the appended value
367
+ * @param {T} data The data to be appended
368
+ * @param {AppendOptions} [options] The options for the append
369
+ * @returns {{ entry: Entry<T>; removed: ShallowEntry[] }} The appended entry and an array of removed entries
388
370
  */
389
371
  async append(data, options = {}) {
390
372
  // Update the clock (find the latest clock)
@@ -395,11 +377,14 @@ let Log = Log_1 = class Log {
395
377
  }
396
378
  }
397
379
  await this.load({ reload: false });
398
- const nexts = options.meta?.next || (await this.getHeads());
380
+ const nexts = options.meta?.next ||
381
+ (await this.entryIndex
382
+ .getHeads(undefined, { type: "shape", shape: Sorting.ENTRY_SORT_SHAPE })
383
+ .all());
399
384
  // Calculate max time for log/graph
400
385
  const clock = new Clock({
401
386
  id: this._identity.publicKey.bytes,
402
- timestamp: options?.meta?.timestamp || this._hlc.now()
387
+ timestamp: options?.meta?.timestamp || this._hlc.now(),
403
388
  });
404
389
  const entry = await Entry.create({
405
390
  store: this._storage,
@@ -411,39 +396,47 @@ let Log = Log_1 = class Log {
411
396
  type: options.meta?.type,
412
397
  gidSeed: options.meta?.gidSeed,
413
398
  data: options.meta?.data,
414
- next: nexts
399
+ next: nexts,
415
400
  },
416
401
  encoding: this._encoding,
417
402
  encryption: options.encryption
418
403
  ? {
419
404
  keypair: options.encryption.keypair,
420
405
  receiver: {
421
- ...options.encryption.receiver
422
- }
406
+ ...options.encryption.receiver,
407
+ },
423
408
  }
424
409
  : undefined,
425
- canAppend: options.canAppend || this._canAppend
410
+ canAppend: options.canAppend || this._canAppend,
426
411
  });
427
412
  if (!entry.hash) {
428
413
  throw new Error("Unexpected");
429
414
  }
430
- for (const e of nexts) {
431
- if (!this.has(e.hash)) {
432
- await this.join([e]);
433
- }
434
- let nextIndexSet = this._nextsIndex.get(e.hash);
435
- if (!nextIndexSet) {
436
- nextIndexSet = new Set();
437
- nextIndexSet.add(entry.hash);
438
- this._nextsIndex.set(e.hash, nextIndexSet);
439
- }
440
- else {
441
- nextIndexSet.add(entry.hash);
415
+ if (entry.meta.type !== EntryType.CUT) {
416
+ for (const e of nexts) {
417
+ if (!(await this.has(e.hash))) {
418
+ let entry;
419
+ if (e instanceof Entry) {
420
+ entry = e;
421
+ }
422
+ else {
423
+ let resolved = await this.entryIndex.get(e.hash);
424
+ if (!resolved) {
425
+ // eslint-disable-next-line no-console
426
+ console.warn("Unexpected missing entry when joining", e.hash);
427
+ continue;
428
+ }
429
+ entry = resolved;
430
+ }
431
+ await this.join([entry]);
432
+ }
442
433
  }
443
434
  }
444
- await this._entryIndex.set(entry, false); // save === false, because its already saved when Entry.create
445
- await this._headsIndex.put(entry, { cache: { update: false } }); // we will update the cache a few lines later *
446
- await this._values.put(entry);
435
+ await this.entryIndex.put(entry, {
436
+ unique: true,
437
+ isHead: true,
438
+ toMultiHash: false,
439
+ });
447
440
  const removed = await this.processEntry(entry);
448
441
  entry.init({ encoding: this._encoding, keychain: this._keychain });
449
442
  const trimmed = await this.trim(options?.trim);
@@ -454,22 +447,16 @@ let Log = Log_1 = class Log {
454
447
  }
455
448
  const changes = {
456
449
  added: [entry],
457
- removed: removed
450
+ removed,
458
451
  };
459
- await this._headsIndex.updateHeadsCache(changes); // * here
460
452
  await (options?.onChange || this._onChange)?.(changes);
461
453
  return { entry, removed };
462
454
  }
463
455
  async reset(entries) {
464
- this._nextsIndex = new Map();
465
- this._entryIndex = new EntryIndex({
466
- store: this._storage,
467
- init: (e) => e.init(this),
468
- cache: this._entryCache
469
- });
470
- await this.headsIndex.reset([]);
471
- this._values = new Values(this._entryIndex, this._sortFn, []);
472
- await this.join(entries);
456
+ const heads = await this.getHeads(true).all();
457
+ await this._entryIndex.clear();
458
+ await this._onChange?.({ added: [], removed: heads });
459
+ await this.join(entries || heads);
473
460
  }
474
461
  async remove(entry, options) {
475
462
  await this.load({ reload: false });
@@ -477,269 +464,200 @@ let Log = Log_1 = class Log {
477
464
  if (entries.length === 0) {
478
465
  return {
479
466
  added: [],
480
- removed: []
467
+ removed: [],
481
468
  };
482
469
  }
470
+ const change = {
471
+ added: [],
472
+ removed: Array.isArray(entry) ? entry : [entry],
473
+ };
474
+ await this._onChange?.(change);
483
475
  if (options?.recursively) {
484
476
  await this.deleteRecursively(entry);
485
477
  }
486
478
  else {
487
479
  for (const entry of entries) {
488
- await this.delete(entry);
480
+ await this.delete(entry.hash);
489
481
  }
490
482
  }
491
- const change = {
492
- added: [],
493
- removed: Array.isArray(entry) ? entry : [entry]
494
- };
495
- /* await Promise.all([
496
- this._logCache?.queue(change),
497
- this._onUpdate(change),
498
- ]); */
499
- await this._onChange?.(change);
500
483
  return change;
501
484
  }
502
- iterator(options) {
485
+ /* iterator(options?: {
486
+ from?: "tail" | "head";
487
+ amount?: number;
488
+ }): IterableIterator<string> {
503
489
  const from = options?.from || "tail";
504
490
  const amount = typeof options?.amount === "number" ? options?.amount : -1;
505
491
  let next = from === "tail" ? this._values.tail : this._values.head;
506
- const nextFn = from === "tail" ? (e) => e.prev : (e) => e.next;
492
+ const nextFn = from === "tail" ? (e: any) => e.prev : (e: any) => e.next;
507
493
  return (function* () {
508
494
  let counter = 0;
509
495
  while (next) {
510
496
  if (amount >= 0 && counter >= amount) {
511
497
  return;
512
498
  }
499
+
513
500
  yield next.value;
514
501
  counter++;
502
+
515
503
  next = nextFn(next);
516
504
  }
517
505
  })();
518
- }
506
+ } */
519
507
  async trim(option = this._trim.options) {
520
508
  return this._trim.trim(option);
521
509
  }
522
- /**
523
- *
524
- * @param entries
525
- * @returns change
526
- */
527
- /* async sync(
528
- entries: (EntryWithRefs<T> | Entry<T> | string)[],
529
- options: {
530
- canAppend?: CanAppend<T>;
531
- onChange?: (change: Change<T>) => void | Promise<void>;
532
- timeout?: number;
533
- } = {}
534
- ): Promise<void> {
535
-
536
-
537
- logger.debug(`Sync request #${entries.length}`);
538
- const entriesToJoin: (Entry<T> | string)[] = [];
539
- for (const e of entries) {
540
- if (e instanceof Entry || typeof e === "string") {
541
- entriesToJoin.push(e);
542
- } else {
543
- for (const ref of e.references) {
544
- entriesToJoin.push(ref);
545
- }
546
- entriesToJoin.push(e.entry);
547
- }
548
- }
549
-
550
- await this.join(entriesToJoin, {
551
- canAppend: (entry) => {
552
- const canAppend = options?.canAppend || this.canAppend;
553
- return !canAppend || canAppend(entry);
554
- },
555
- onChange: (change) => {
556
- options?.onChange?.(change);
557
- return this._onChange?.({
558
- added: change.added,
559
- removed: change.removed,
560
- });
561
- },
562
- timeout: options.timeout,
563
- });
564
- } */
565
510
  async join(entriesOrLog, options) {
566
- const definedOptions = {
567
- ...options,
568
- cache: options?.cache ?? {
569
- update: true
511
+ let entries;
512
+ let references = new Map();
513
+ if (entriesOrLog instanceof Log_1) {
514
+ if (entriesOrLog.entryIndex.length === 0)
515
+ return;
516
+ entries = await entriesOrLog.toArray();
517
+ for (const element of entries) {
518
+ references.set(element.hash, element);
570
519
  }
571
- };
572
- await this.load({ reload: false });
573
- if (entriesOrLog.length === 0) {
574
- return;
575
520
  }
576
- /* const joinLength = options?.length ?? Number.MAX_SAFE_INTEGER; TODO */
577
- const visited = new Set();
578
- const nextRefs = new Map();
579
- const entriesBottomUp = [];
580
- const stack = [];
581
- const resolvedEntries = new Map();
582
- const entries = Array.isArray(entriesOrLog)
583
- ? entriesOrLog
584
- : await entriesOrLog.values.toArray();
585
- // Build a list of already resolved entries, and filter out already joined entries
586
- for (const e of entries) {
587
- // TODO, do this less ugly
588
- let hash;
589
- if (e instanceof Entry) {
590
- hash = e.hash;
591
- resolvedEntries.set(e.hash, e);
592
- if (this.has(hash)) {
593
- continue;
594
- }
595
- stack.push(hash);
521
+ else if (Array.isArray(entriesOrLog)) {
522
+ if (entriesOrLog.length === 0) {
523
+ return;
596
524
  }
597
- else if (typeof e === "string") {
598
- hash = e;
599
- if (this.has(hash)) {
600
- continue;
525
+ entries = [];
526
+ for (const element of entriesOrLog) {
527
+ if (element instanceof Entry) {
528
+ entries.push(element);
529
+ references.set(element.hash, element);
601
530
  }
602
- stack.push(hash);
603
- }
604
- else {
605
- hash = e.entry.hash;
606
- resolvedEntries.set(e.entry.hash, e.entry);
607
- if (this.has(hash)) {
608
- continue;
531
+ else if (typeof element === "string") {
532
+ let entry = await Entry.fromMultihash(this._storage, element, {
533
+ timeout: options?.timeout,
534
+ });
535
+ if (!entry) {
536
+ throw new Error("Missing entry in join by hash: " + element);
537
+ }
538
+ entries.push(entry);
539
+ }
540
+ else if (element instanceof ShallowEntry) {
541
+ let entry = await Entry.fromMultihash(this._storage, element.hash, {
542
+ timeout: options?.timeout,
543
+ });
544
+ if (!entry) {
545
+ throw new Error("Missing entry in join by hash: " + element.hash);
546
+ }
547
+ entries.push(entry);
609
548
  }
610
- stack.push(hash);
611
- for (const e2 of e.references) {
612
- resolvedEntries.set(e2.hash, e2);
613
- if (this.has(e2.hash)) {
614
- continue;
549
+ else {
550
+ entries.push(element.entry);
551
+ references.set(element.entry.hash, element.entry);
552
+ for (const ref of element.references) {
553
+ references.set(ref.hash, ref);
615
554
  }
616
- stack.push(e2.hash);
617
555
  }
618
556
  }
619
557
  }
620
- const removedHeads = [];
621
- for (const hash of stack) {
622
- if (visited.has(hash) || this.has(hash)) {
623
- continue;
558
+ else {
559
+ let all = await entriesOrLog.all(); // TODO dont load all at once
560
+ if (all.length === 0) {
561
+ return;
624
562
  }
625
- visited.add(hash);
626
- const entry = resolvedEntries.get(hash) ||
627
- (await Entry.fromMultihash(this._storage, hash, {
628
- timeout: options?.timeout
629
- }));
630
- entry.init(this);
631
- resolvedEntries.set(entry.hash, entry);
632
- let nexts;
633
- if (entry.meta.type !== EntryType.CUT &&
634
- (nexts = await entry.getNext())) {
635
- let isRoot = true;
636
- for (const next of nexts) {
637
- if (!this.has(next)) {
638
- isRoot = false;
639
- }
640
- else {
641
- if (this._headsIndex.has(next)) {
642
- const toRemove = (await this.get(next, options));
643
- await this._headsIndex.del(toRemove);
644
- removedHeads.push(toRemove);
645
- }
646
- }
647
- let nextIndexSet = nextRefs.get(next);
648
- if (!nextIndexSet) {
649
- nextIndexSet = [];
650
- nextIndexSet.push(entry);
651
- nextRefs.set(next, nextIndexSet);
652
- }
653
- else {
654
- nextIndexSet.push(entry);
655
- }
656
- if (!visited.has(next)) {
657
- stack.push(next);
658
- }
659
- }
660
- if (isRoot) {
661
- entriesBottomUp.push(entry);
662
- }
563
+ entries = all;
564
+ }
565
+ let heads = new Map();
566
+ for (const entry of entries) {
567
+ if (heads.has(entry.hash)) {
568
+ continue;
663
569
  }
664
- else {
665
- entriesBottomUp.push(entry);
570
+ heads.set(entry.hash, true);
571
+ for (const next of await entry.getNext()) {
572
+ heads.set(next, false);
666
573
  }
667
574
  }
668
- while (entriesBottomUp.length > 0) {
669
- const e = entriesBottomUp.shift();
670
- await this._joining.get(e.hash);
671
- const p = this.joinEntry(e, nextRefs, entriesBottomUp, definedOptions).then(() => this._joining.delete(e.hash) // TODO, if head we run into problems with concurrency here!, we add heads at line 929 but resolve here
672
- );
673
- this._joining.set(e.hash, p);
575
+ for (const entry of entries) {
576
+ const p = this.joinRecursively(entry, {
577
+ references,
578
+ isHead: heads.get(entry.hash),
579
+ ...options,
580
+ });
581
+ this._joining.set(entry.hash, p);
582
+ p.finally(() => {
583
+ this._joining.delete(entry.hash);
584
+ });
674
585
  await p;
675
586
  }
676
587
  }
677
- async joinEntry(e, nextRefs, stack, options) {
678
- if (this.length > (options?.length ?? Number.MAX_SAFE_INTEGER)) {
588
+ /**
589
+ * Bottom up join of entries into the log
590
+ * @param entry
591
+ * @param options
592
+ * @returns
593
+ */
594
+ async joinRecursively(entry, options) {
595
+ if (this.entryIndex.length > (options?.length ?? Number.MAX_SAFE_INTEGER)) {
679
596
  return;
680
597
  }
681
- if (!e.hash) {
598
+ if (!entry.hash) {
682
599
  throw new Error("Unexpected");
683
600
  }
684
- const headsWithGid = this.headsIndex.gids.get(e.gid);
601
+ if (await this.has(entry.hash)) {
602
+ return;
603
+ }
604
+ entry.init(this);
605
+ if (options?.verifySignatures) {
606
+ if (!(await entry.verifySignatures())) {
607
+ throw new Error('Invalid signature entry with hash "' + entry.hash + '"');
608
+ }
609
+ }
610
+ if (this?._canAppend && !(await this?._canAppend(entry))) {
611
+ return;
612
+ }
613
+ const headsWithGid = await this.entryIndex
614
+ .getHeads(entry.gid, { type: "shape", shape: ENTRY_JOIN_SHAPE })
615
+ .all();
685
616
  if (headsWithGid) {
686
- for (const [_k, v] of headsWithGid) {
687
- if (v.meta.type === EntryType.CUT && v.next.includes(e.hash)) {
617
+ for (const v of headsWithGid) {
618
+ // TODO second argument should be a time compare instead? what about next nexts?
619
+ // and check the cut entry is newer than the current 'entry'
620
+ if (v.meta.type === EntryType.CUT &&
621
+ v.meta.next.includes(entry.hash) &&
622
+ Sorting.compare(entry, v, this._sortFn) < 0) {
688
623
  return; // already deleted
689
624
  }
690
625
  }
691
626
  }
692
- if (!this.has(e.hash)) {
693
- if (options?.verifySignatures) {
694
- if (!(await e.verifySignatures())) {
695
- throw new Error('Invalid signature entry with hash "' + e.hash + '"');
696
- }
697
- }
698
- if (this?._canAppend && !(await this?._canAppend(e))) {
699
- return;
700
- }
701
- // Update the internal entry index
702
- await this._entryIndex.set(e);
703
- await this._values.put(e);
704
- if (e.meta.type !== EntryType.CUT) {
705
- for (const a of e.next) {
706
- if (!this.has(a)) {
707
- await this.join([a]);
708
- }
709
- let nextIndexSet = this._nextsIndex.get(a);
710
- if (!nextIndexSet) {
711
- nextIndexSet = new Set();
712
- nextIndexSet.add(e.hash);
713
- this._nextsIndex.set(a, nextIndexSet);
714
- }
715
- else {
716
- nextIndexSet.add(a);
627
+ if (entry.meta.type !== EntryType.CUT) {
628
+ for (const a of entry.next) {
629
+ if (!(await this.has(a))) {
630
+ const nested = options.references?.get(a) ||
631
+ (await Entry.fromMultihash(this._storage, a, {
632
+ timeout: options?.timeout,
633
+ }));
634
+ if (!nested) {
635
+ throw new Error("Missing entry in joinRecursively: " + a);
717
636
  }
637
+ const p = this.joinRecursively(nested, options.isHead ? { ...options, isHead: false } : options);
638
+ this._joining.set(nested.hash, p);
639
+ p.finally(() => {
640
+ this._joining.delete(nested.hash);
641
+ });
642
+ await p;
718
643
  }
719
644
  }
720
- const clock = await e.getClock();
721
- this._hlc.update(clock.timestamp);
722
- const removed = await this.processEntry(e);
723
- const trimmed = await this.trim(options?.trim);
724
- if (trimmed) {
725
- for (const entry of trimmed) {
726
- removed.push(entry);
727
- }
728
- }
729
- await this?._onChange?.({ added: [e], removed: removed });
730
645
  }
731
- const forward = nextRefs.get(e.hash);
732
- if (forward) {
733
- if (this._headsIndex.has(e.hash)) {
734
- await this._headsIndex.del(e, options);
735
- }
736
- for (const en of forward) {
737
- stack.push(en);
646
+ const clock = await entry.getClock();
647
+ this._hlc.update(clock.timestamp);
648
+ await this._entryIndex.put(entry, {
649
+ unique: false,
650
+ isHead: options.isHead,
651
+ toMultiHash: true,
652
+ });
653
+ const removed = await this.processEntry(entry);
654
+ const trimmed = await this.trim(options?.trim);
655
+ if (trimmed) {
656
+ for (const entry of trimmed) {
657
+ removed.push(entry);
738
658
  }
739
659
  }
740
- else {
741
- await this.headsIndex.put(e, options);
742
- }
660
+ await this?._onChange?.({ added: [entry], removed: removed });
743
661
  }
744
662
  async processEntry(entry) {
745
663
  if (entry.meta.type === EntryType.CUT) {
@@ -755,22 +673,25 @@ let Log = Log_1 = class Log {
755
673
  const deleted = [];
756
674
  while (stack.length > 0) {
757
675
  const entry = stack.pop();
758
- if ((counter > 0 || !skipFirst) && this.has(entry.hash)) {
759
- // 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.
760
- this._trim.deleteFromCache(entry);
761
- await this._headsIndex.del(entry);
762
- await this._values.delete(entry);
763
- await this._entryIndex.delete(entry.hash);
764
- this._nextsIndex.delete(entry.hash);
765
- deleted.push(entry);
766
- promises.push(entry.delete(this._storage));
767
- }
768
- for (const next of entry.next) {
769
- const nextFromNext = this._nextsIndex.get(next);
770
- if (nextFromNext) {
771
- nextFromNext.delete(entry.hash);
676
+ const skip = counter === 0 && skipFirst;
677
+ if (!skip) {
678
+ const has = await this.has(entry.hash);
679
+ if (has) {
680
+ // 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.
681
+ const deletedEntry = await this.delete(entry.hash);
682
+ if (deletedEntry) {
683
+ /* this._nextsIndex.delete(entry.hash); */
684
+ deleted.push(deletedEntry);
685
+ }
772
686
  }
773
- if (!nextFromNext || nextFromNext.size === 0) {
687
+ }
688
+ for (const next of entry.meta.next) {
689
+ const nextFromNext = this.entryIndex.getHasNext(next);
690
+ const entriesThatHasNext = await nextFromNext.all();
691
+ // if there are no entries which is not of "CUT" type, we can safely delete the next entry
692
+ // figureately speaking, these means where are cutting all branches to a stem, so we can delete the stem as well
693
+ let hasAlternativeNext = !!entriesThatHasNext.find((x) => x.meta.type !== EntryType.CUT);
694
+ if (!hasAlternativeNext) {
774
695
  const ne = await this.get(next);
775
696
  if (ne) {
776
697
  stack.push(ne);
@@ -782,33 +703,10 @@ let Log = Log_1 = class Log {
782
703
  await Promise.all(promises);
783
704
  return deleted;
784
705
  }
785
- async delete(entry) {
786
- this._trim.deleteFromCache(entry);
787
- await this._headsIndex.del(entry, {
788
- cache: {
789
- update: false
790
- }
791
- }); // cache is not updated here, but at *
792
- await this._values.delete(entry);
793
- await this._entryIndex.delete(entry.hash);
794
- this._nextsIndex.delete(entry.hash);
795
- const newHeads = [];
796
- for (const next of entry.next) {
797
- const ne = await this.get(next);
798
- if (ne) {
799
- const nexts = this._nextsIndex.get(next);
800
- nexts.delete(entry.hash);
801
- if (nexts.size === 0) {
802
- await this._headsIndex.put(ne);
803
- newHeads.push(ne.hash);
804
- }
805
- }
806
- }
807
- await this._headsIndex.updateHeadsCache({
808
- added: newHeads,
809
- removed: [entry.hash]
810
- }); // * here
811
- return entry.delete(this._storage);
706
+ async delete(hash) {
707
+ await this._trim.deleteFromCache(hash);
708
+ const removedEntry = await this._entryIndex.delete(hash);
709
+ return removedEntry;
812
710
  }
813
711
  /**
814
712
  * Returns the log entries as a formatted string.
@@ -828,37 +726,29 @@ let Log = Log_1 = class Log {
828
726
  let padding = new Array(Math.max(len - 1, 0));
829
727
  padding = len > 1 ? padding.fill(" ") : padding;
830
728
  padding = len > 0 ? padding.concat(["└─"]) : padding;
831
- /* istanbul ignore next */
832
729
  return (padding.join("") +
833
- (payloadMapper ? payloadMapper(e.payload) : e.payload));
730
+ (payloadMapper?.(e.payload) || e.payload));
834
731
  }))).join("\n");
835
732
  }
836
- async idle() {
837
- await this._headsIndex.headsCache?.idle();
838
- }
839
733
  async close() {
840
734
  // Don't return early here if closed = true, because "load" might create processes that needs to be closed
841
735
  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
842
- await this._entryCache?.clear();
843
- await this._headsIndex?.close();
844
- await this._memory?.close();
845
- this._entryCache = undefined;
846
- this._headsIndex = undefined;
847
- this._memory = undefined;
848
- this._values = undefined;
736
+ this._closeController.abort();
737
+ await this._indexer?.stop?.();
738
+ this._indexer = undefined;
739
+ this._loadedOnce = false;
849
740
  }
850
741
  async drop() {
851
742
  // Don't return early here if closed = true, because "load" might create processes that needs to be closed
852
743
  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
853
- await Promise.all([...this.entryIndex._index.values()].map((x) => this.blocks.rm(x.hash)));
854
- await this._headsIndex?.drop();
855
- await this._entryCache?.clear();
856
- await this._memory?.clear();
857
- await this._memory?.close();
744
+ this._closeController.abort();
745
+ await this.entryIndex?.clear();
746
+ await this._indexer?.drop();
747
+ await this._indexer?.stop?.();
858
748
  }
859
749
  async recover() {
860
750
  // merge existing
861
- const existing = await this.getHeads();
751
+ const existing = await this.getHeads(true).all();
862
752
  const allHeads = new Map();
863
753
  for (const head of existing) {
864
754
  allHeads.set(head.hash, head);
@@ -887,42 +777,33 @@ let Log = Log_1 = class Log {
887
777
  // assume they are valid, (let access control reject them if not)
888
778
  await this.load({ reload: true, heads: [...allHeads.values()] });
889
779
  }
890
- async load(opts = { reload: true }) {
780
+ async load(opts = {}) {
891
781
  if (this.closed) {
892
782
  throw new Error("Closed");
893
783
  }
784
+ if (this._loadedOnce && !opts.reload && !opts.reset) {
785
+ return;
786
+ }
787
+ this._loadedOnce = true;
894
788
  const providedCustomHeads = Array.isArray(opts["heads"]);
895
789
  const heads = providedCustomHeads
896
790
  ? opts["heads"]
897
- : await this.headsIndex.load({
898
- replicate: true, // TODO this.replication.replicate(x) => true/false
899
- timeout: opts.fetchEntryTimeout,
900
- reload: opts.reload,
791
+ : await this._entryIndex
792
+ .getHeads(undefined, {
793
+ type: "full",
794
+ signal: this._closeController.signal,
901
795
  ignoreMissing: opts.ignoreMissing,
902
- cache: { update: true, reset: true }
903
- });
796
+ timeout: opts.timeout,
797
+ })
798
+ .all();
904
799
  if (heads) {
905
800
  // Load the log
906
- if (providedCustomHeads) {
801
+ if (providedCustomHeads || opts.reset) {
907
802
  await this.reset(heads);
908
803
  }
909
804
  else {
910
- /*
911
- TODO feat amount load
912
- const amount = (opts as { amount?: number }).amount;
913
- if (amount != null && amount >= 0 && amount < heads.length) {
914
- throw new Error(
915
- "You are not loading all heads, this will lead to unexpected behaviours on write. Please load at least load: " +
916
- amount +
917
- " entries"
918
- );
919
- } */
920
805
  await this.join(heads instanceof Entry ? [heads] : heads, {
921
- /* length: amount, */
922
806
  timeout: opts?.fetchEntryTimeout,
923
- cache: {
924
- update: false
925
- }
926
807
  });
927
808
  }
928
809
  }
@@ -933,7 +814,7 @@ let Log = Log_1 = class Log {
933
814
  await log.join(!Array.isArray(entryOrHash) ? [entryOrHash] : entryOrHash, {
934
815
  timeout: options.timeout,
935
816
  trim: options.trim,
936
- verifySignatures: true
817
+ verifySignatures: true,
937
818
  });
938
819
  return log;
939
820
  }
@@ -943,7 +824,7 @@ let Log = Log_1 = class Log {
943
824
  * Finds entries that are the heads of this collection,
944
825
  * ie. entries that are not referenced by other entries.
945
826
  *
946
- * @param {Array<Entry<T>>} entries Entries to search heads from
827
+ * @param {Array<Entry<T>>} entries - Entries to search heads from
947
828
  * @returns {Array<Entry<T>>}
948
829
  */
949
830
  static findHeads(entries) {