@peerbit/log 4.1.11 → 4.2.1

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 (41) hide show
  1. package/dist/benchmark/payload.js +98 -49
  2. package/dist/benchmark/payload.js.map +1 -1
  3. package/dist/src/clock.d.ts +1 -1
  4. package/dist/src/clock.d.ts.map +1 -1
  5. package/dist/src/clock.js +165 -115
  6. package/dist/src/clock.js.map +1 -1
  7. package/dist/src/entry-index.d.ts.map +1 -1
  8. package/dist/src/entry-index.js +4 -3
  9. package/dist/src/entry-index.js.map +1 -1
  10. package/dist/src/entry-shallow.js +119 -69
  11. package/dist/src/entry-shallow.js.map +1 -1
  12. package/dist/src/entry-v0.d.ts.map +1 -1
  13. package/dist/src/entry-v0.js +451 -381
  14. package/dist/src/entry-v0.js.map +1 -1
  15. package/dist/src/entry.d.ts +1 -1
  16. package/dist/src/heads-cache.d.ts +2 -3
  17. package/dist/src/heads-cache.d.ts.map +1 -1
  18. package/dist/src/heads-cache.js +128 -64
  19. package/dist/src/heads-cache.js.map +1 -1
  20. package/dist/src/log-sorting.d.ts +1 -1
  21. package/dist/src/log-sorting.d.ts.map +1 -1
  22. package/dist/src/log.js +838 -802
  23. package/dist/src/log.js.map +1 -1
  24. package/dist/src/logger.d.ts +1 -3
  25. package/dist/src/logger.d.ts.map +1 -1
  26. package/dist/src/logger.js +2 -3
  27. package/dist/src/logger.js.map +1 -1
  28. package/dist/src/payload.js +80 -43
  29. package/dist/src/payload.js.map +1 -1
  30. package/dist/src/snapshot.d.ts +1 -1
  31. package/dist/src/snapshot.d.ts.map +1 -1
  32. package/dist/src/snapshot.js +87 -45
  33. package/dist/src/snapshot.js.map +1 -1
  34. package/package.json +17 -17
  35. package/src/clock.ts +1 -5
  36. package/src/entry-index.ts +5 -3
  37. package/src/entry-v0.ts +6 -6
  38. package/src/heads-cache.ts +2 -4
  39. package/src/log-sorting.ts +1 -1
  40. package/src/logger.ts +2 -3
  41. package/src/snapshot.ts +8 -6
package/dist/src/log.js CHANGED
@@ -1,13 +1,37 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
6
27
  };
7
- var __metadata = (this && this.__metadata) || function (k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
9
34
  };
10
- var Log_1;
11
35
  import { deserialize, field, fixedArray, variant } from "@dao-xyz/borsh";
12
36
  import {} from "@peerbit/any-store";
13
37
  import { cidifyString } from "@peerbit/blocks-interface";
@@ -33,864 +57,876 @@ export const ENTRY_JOIN_SHAPE = {
33
57
  hash: true,
34
58
  meta: { type: true, next: true, gid: true, clock: true },
35
59
  };
36
- let Log = Log_1 = class Log {
37
- _id;
38
- /* private _sortFn!: Sorting.ISortFunction; */
39
- _storage;
40
- _hlc;
41
- // Identity
42
- _identity;
43
- // Keeping track of entries
44
- _entryIndex;
45
- /* private _headsIndex!: HeadsIndex<T>;
46
- private _values!: Values<T>;
47
- */
48
- // Index of all next pointers in this log
49
- /* private _nextsIndex!: Map<string, Set<string>>; */
50
- _keychain;
51
- _encoding;
52
- _trim;
53
- _canAppend;
54
- _onChange;
55
- _closed = true;
56
- _closeController;
57
- _loadedOnce = false;
58
- _indexer;
59
- _joining; // entry hashes that are currently joining into this log
60
- _sortFn;
61
- constructor(properties) {
62
- this._id = properties?.id || randomBytes(32);
63
- }
64
- async open(store, identity, options = {}) {
65
- if (store == null) {
66
- throw LogError.BlockStoreNotDefinedError();
67
- }
68
- if (identity == null) {
69
- throw new Error("Identity is required");
70
- }
71
- if (this.closed === false) {
72
- throw new Error("Already open");
73
- }
74
- this._closeController = new AbortController();
75
- const { encoding, trim, keychain, indexer, onGidRemoved, sortFn } = options;
76
- // TODO do correctly with tie breaks
77
- this._sortFn = sortFn || LastWriteWins;
78
- this._storage = store;
79
- this._indexer = indexer || (await create());
80
- await this._indexer.start?.();
81
- this._encoding = encoding || NO_ENCODING;
82
- this._joining = new Map();
60
+ let Log = (() => {
61
+ let _classDecorators = [variant(0)];
62
+ let _classDescriptor;
63
+ let _classExtraInitializers = [];
64
+ let _classThis;
65
+ let __id_decorators;
66
+ let __id_initializers = [];
67
+ let __id_extraInitializers = [];
68
+ var Log = class {
69
+ static { _classThis = this; }
70
+ static {
71
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
72
+ __id_decorators = [field({ type: fixedArray("u8", 32) })];
73
+ __esDecorate(null, null, __id_decorators, { kind: "field", name: "_id", static: false, private: false, access: { has: obj => "_id" in obj, get: obj => obj._id, set: (obj, value) => { obj._id = value; } }, metadata: _metadata }, __id_initializers, __id_extraInitializers);
74
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
75
+ Log = _classThis = _classDescriptor.value;
76
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
77
+ __runInitializers(_classThis, _classExtraInitializers);
78
+ }
79
+ _id = __runInitializers(this, __id_initializers, void 0);
80
+ /* private _sortFn!: Sorting.ISortFunction; */
81
+ _storage = __runInitializers(this, __id_extraInitializers);
82
+ _hlc;
83
83
  // Identity
84
- this._identity = identity;
85
- // encoder/decoder
86
- this._keychain = keychain;
87
- // Clock
88
- this._hlc = new HLC();
89
- const id = this.id;
90
- if (!id) {
91
- throw new Error("Id not set");
92
- }
93
- this._entryIndex = new EntryIndex({
94
- store: this._storage,
95
- init: (e) => e.init(this),
96
- onGidRemoved,
97
- index: await (await this._indexer.scope("heads")).init({ schema: ShallowEntry }),
98
- publicKey: this._identity.publicKey,
99
- sort: this._sortFn,
100
- });
101
- await this._entryIndex.init();
102
- /* this._values = new Values(this._entryIndex, this._sortFn); */
103
- this._trim = new Trim({
104
- index: this._entryIndex,
105
- deleteNode: async (node) => {
106
- const resolved = await this.get(node.hash);
107
- await this._entryIndex.delete(node.hash);
108
- await this._storage.rm(node.hash);
109
- return resolved;
110
- },
111
- sortFn: this._sortFn,
112
- getLength: () => this.length,
113
- }, trim);
114
- this._canAppend = async (entry) => {
115
- if (options?.canAppend) {
116
- if (!(await options.canAppend(entry))) {
117
- return false;
84
+ _identity;
85
+ // Keeping track of entries
86
+ _entryIndex;
87
+ /* private _headsIndex!: HeadsIndex<T>;
88
+ private _values!: Values<T>;
89
+ */
90
+ // Index of all next pointers in this log
91
+ /* private _nextsIndex!: Map<string, Set<string>>; */
92
+ _keychain;
93
+ _encoding;
94
+ _trim;
95
+ _canAppend;
96
+ _onChange;
97
+ _closed = true;
98
+ _closeController;
99
+ _loadedOnce = false;
100
+ _indexer;
101
+ _joining; // entry hashes that are currently joining into this log
102
+ _sortFn;
103
+ constructor(properties) {
104
+ this._id = properties?.id || randomBytes(32);
105
+ }
106
+ async open(store, identity, options = {}) {
107
+ if (store == null) {
108
+ throw LogError.BlockStoreNotDefinedError();
109
+ }
110
+ if (identity == null) {
111
+ throw new Error("Identity is required");
112
+ }
113
+ if (this.closed === false) {
114
+ throw new Error("Already open");
115
+ }
116
+ this._closeController = new AbortController();
117
+ const { encoding, trim, keychain, indexer, onGidRemoved, sortFn } = options;
118
+ // TODO do correctly with tie breaks
119
+ this._sortFn = sortFn || LastWriteWins;
120
+ this._storage = store;
121
+ this._indexer = indexer || (await create());
122
+ await this._indexer.start?.();
123
+ this._encoding = encoding || NO_ENCODING;
124
+ this._joining = new Map();
125
+ // Identity
126
+ this._identity = identity;
127
+ // encoder/decoder
128
+ this._keychain = keychain;
129
+ // Clock
130
+ this._hlc = new HLC();
131
+ const id = this.id;
132
+ if (!id) {
133
+ throw new Error("Id not set");
134
+ }
135
+ this._entryIndex = new EntryIndex({
136
+ store: this._storage,
137
+ init: (e) => e.init(this),
138
+ onGidRemoved,
139
+ index: await (await this._indexer.scope("heads")).init({ schema: ShallowEntry }),
140
+ publicKey: this._identity.publicKey,
141
+ sort: this._sortFn,
142
+ });
143
+ await this._entryIndex.init();
144
+ /* this._values = new Values(this._entryIndex, this._sortFn); */
145
+ this._trim = new Trim({
146
+ index: this._entryIndex,
147
+ deleteNode: async (node) => {
148
+ const resolved = await this.get(node.hash);
149
+ await this._entryIndex.delete(node.hash);
150
+ await this._storage.rm(node.hash);
151
+ return resolved;
152
+ },
153
+ sortFn: this._sortFn,
154
+ getLength: () => this.length,
155
+ }, trim);
156
+ this._canAppend = async (entry) => {
157
+ if (options?.canAppend) {
158
+ if (!(await options.canAppend(entry))) {
159
+ return false;
160
+ }
118
161
  }
162
+ return true;
163
+ };
164
+ this._onChange = options?.onChange;
165
+ this._closed = false;
166
+ this._closeController = new AbortController();
167
+ }
168
+ _idString;
169
+ get idString() {
170
+ if (!this.id) {
171
+ throw new Error("Id not set");
119
172
  }
120
- return true;
121
- };
122
- this._onChange = options?.onChange;
123
- this._closed = false;
124
- this._closeController = new AbortController();
125
- }
126
- _idString;
127
- get idString() {
128
- if (!this.id) {
129
- throw new Error("Id not set");
173
+ return this._idString || (this._idString = Log.createIdString(this.id));
130
174
  }
131
- return this._idString || (this._idString = Log_1.createIdString(this.id));
132
- }
133
- static createIdString(id) {
134
- return sha256Base64Sync(id);
135
- }
136
- get id() {
137
- return this._id;
138
- }
139
- set id(id) {
140
- if (this.closed === false) {
141
- throw new Error("Can not change id after open");
175
+ static createIdString(id) {
176
+ return sha256Base64Sync(id);
142
177
  }
143
- this._idString = undefined;
144
- this._id = id;
145
- }
146
- /**
147
- * Returns the length of the log.
148
- */
149
- get length() {
150
- if (this._closed) {
151
- throw new Error("Closed");
152
- }
153
- return this._entryIndex.length;
154
- }
155
- get canAppend() {
156
- return this._canAppend;
157
- }
158
- /**
159
- * Checks if a entry is part of the log
160
- * @param {string} hash The hash of the entry
161
- * @returns {boolean}
162
- */
163
- has(cid) {
164
- return this._entryIndex.has(cid);
165
- }
166
- /**
167
- * Get all entries sorted. Don't use this method anywhere where performance matters
168
- */
169
- async toArray() {
170
- // we call init, because the values might be unitialized
171
- return this.entryIndex.iterate([], this.sortFn.sort, true).all();
172
- }
173
- /**
174
- * Returns the head index
175
- */
176
- getHeads(resolve = false) {
177
- return this.entryIndex.getHeads(undefined, resolve);
178
- }
179
- /**
180
- * Returns an array of Entry objects that reference entries which
181
- * are not in the log currently.
182
- * @returns {Array<Entry<T>>}
183
- */
184
- async getTails() {
185
- return Log_1.findTails(await this.toArray());
186
- }
187
- /**
188
- * Returns an array of hashes that are referenced by entries which
189
- * are not in the log currently.
190
- * @returns {Array<string>} Array of hashes
191
- */
192
- async getTailHashes() {
193
- return Log_1.findTailHashes(await this.toArray());
194
- }
195
- /**
196
- * Get local HLC
197
- */
198
- get hlc() {
199
- return this._hlc;
200
- }
201
- get identity() {
202
- return this._identity;
203
- }
204
- get blocks() {
205
- return this._storage;
206
- }
207
- get entryIndex() {
208
- return this._entryIndex;
209
- }
210
- get keychain() {
211
- return this._keychain;
212
- }
213
- get encoding() {
214
- return this._encoding;
215
- }
216
- get sortFn() {
217
- return this._sortFn;
218
- }
219
- get closed() {
220
- return this._closed;
221
- }
222
- /**
223
- * Set the identity for the log
224
- * @param {Identity} [identity] The identity to be set
225
- */
226
- setIdentity(identity) {
227
- this._identity = identity;
228
- }
229
- /**
230
- * Get an entry.
231
- * @param {string} [hash] The hashes of the entry
232
- */
233
- get(hash, options) {
234
- return this._entryIndex.get(hash, options
235
- ? {
236
- type: "full",
237
- remote: options?.remote && {
238
- timeout: typeof options?.remote !== "boolean"
239
- ? options.remote.timeout
240
- : undefined,
241
- },
242
- ignoreMissing: true, // always return undefined instead of throwing errors on missing entries
243
- }
244
- : { type: "full", ignoreMissing: true });
245
- }
246
- /**
247
- * Get a entry with shallow representation
248
- * @param {string} [hash] The hashes of the entry
249
- */
250
- async getShallow(hash) {
251
- return (await this._entryIndex.getShallow(hash))?.value;
252
- }
253
- async getReferenceSamples(from, options) {
254
- const hashes = new Set();
255
- const pointerCount = options?.pointerCount || 0;
256
- const memoryLimit = options?.memoryLimit;
257
- const maxDistance = Math.min(pointerCount, this.entryIndex.length);
258
- if (maxDistance === 0) {
259
- return [];
178
+ get id() {
179
+ return this._id;
260
180
  }
261
- hashes.add(from.hash);
262
- let memoryCounter = from.payload.byteLength;
263
- if (from.meta.next?.length > 0 && pointerCount >= 2) {
264
- let next = new Set(from.meta.next);
265
- let prev = 2;
266
- outer: for (let i = 2; i <= maxDistance - 1; i *= 2) {
267
- for (let j = prev; j < i; j++) {
268
- if (next.size === 0) {
269
- break outer;
270
- }
271
- const nextNext = new Set();
272
- for (const n of next) {
273
- const nentry = await this.get(n);
274
- if (nentry) {
275
- for (const n2 of nentry.meta.next) {
276
- nextNext.add(n2);
181
+ set id(id) {
182
+ if (this.closed === false) {
183
+ throw new Error("Can not change id after open");
184
+ }
185
+ this._idString = undefined;
186
+ this._id = id;
187
+ }
188
+ /**
189
+ * Returns the length of the log.
190
+ */
191
+ get length() {
192
+ if (this._closed) {
193
+ throw new Error("Closed");
194
+ }
195
+ return this._entryIndex.length;
196
+ }
197
+ get canAppend() {
198
+ return this._canAppend;
199
+ }
200
+ /**
201
+ * Checks if a entry is part of the log
202
+ * @param {string} hash The hash of the entry
203
+ * @returns {boolean}
204
+ */
205
+ has(cid) {
206
+ return this._entryIndex.has(cid);
207
+ }
208
+ /**
209
+ * Get all entries sorted. Don't use this method anywhere where performance matters
210
+ */
211
+ async toArray() {
212
+ // we call init, because the values might be unitialized
213
+ return this.entryIndex.iterate([], this.sortFn.sort, true).all();
214
+ }
215
+ /**
216
+ * Returns the head index
217
+ */
218
+ getHeads(resolve = false) {
219
+ return this.entryIndex.getHeads(undefined, resolve);
220
+ }
221
+ /**
222
+ * Returns an array of Entry objects that reference entries which
223
+ * are not in the log currently.
224
+ * @returns {Array<Entry<T>>}
225
+ */
226
+ async getTails() {
227
+ return Log.findTails(await this.toArray());
228
+ }
229
+ /**
230
+ * Returns an array of hashes that are referenced by entries which
231
+ * are not in the log currently.
232
+ * @returns {Array<string>} Array of hashes
233
+ */
234
+ async getTailHashes() {
235
+ return Log.findTailHashes(await this.toArray());
236
+ }
237
+ /**
238
+ * Get local HLC
239
+ */
240
+ get hlc() {
241
+ return this._hlc;
242
+ }
243
+ get identity() {
244
+ return this._identity;
245
+ }
246
+ get blocks() {
247
+ return this._storage;
248
+ }
249
+ get entryIndex() {
250
+ return this._entryIndex;
251
+ }
252
+ get keychain() {
253
+ return this._keychain;
254
+ }
255
+ get encoding() {
256
+ return this._encoding;
257
+ }
258
+ get sortFn() {
259
+ return this._sortFn;
260
+ }
261
+ get closed() {
262
+ return this._closed;
263
+ }
264
+ /**
265
+ * Set the identity for the log
266
+ * @param {Identity} [identity] The identity to be set
267
+ */
268
+ setIdentity(identity) {
269
+ this._identity = identity;
270
+ }
271
+ /**
272
+ * Get an entry.
273
+ * @param {string} [hash] The hashes of the entry
274
+ */
275
+ get(hash, options) {
276
+ return this._entryIndex.get(hash, options
277
+ ? {
278
+ type: "full",
279
+ remote: options?.remote && {
280
+ timeout: typeof options?.remote !== "boolean"
281
+ ? options.remote.timeout
282
+ : undefined,
283
+ },
284
+ ignoreMissing: true, // always return undefined instead of throwing errors on missing entries
285
+ }
286
+ : { type: "full", ignoreMissing: true });
287
+ }
288
+ /**
289
+ * Get a entry with shallow representation
290
+ * @param {string} [hash] The hashes of the entry
291
+ */
292
+ async getShallow(hash) {
293
+ return (await this._entryIndex.getShallow(hash))?.value;
294
+ }
295
+ async getReferenceSamples(from, options) {
296
+ const hashes = new Set();
297
+ const pointerCount = options?.pointerCount || 0;
298
+ const memoryLimit = options?.memoryLimit;
299
+ const maxDistance = Math.min(pointerCount, this.entryIndex.length);
300
+ if (maxDistance === 0) {
301
+ return [];
302
+ }
303
+ hashes.add(from.hash);
304
+ let memoryCounter = from.payload.byteLength;
305
+ if (from.meta.next?.length > 0 && pointerCount >= 2) {
306
+ let next = new Set(from.meta.next);
307
+ let prev = 2;
308
+ outer: for (let i = 2; i <= maxDistance - 1; i *= 2) {
309
+ for (let j = prev; j < i; j++) {
310
+ if (next.size === 0) {
311
+ break outer;
312
+ }
313
+ const nextNext = new Set();
314
+ for (const n of next) {
315
+ const nentry = await this.get(n);
316
+ if (nentry) {
317
+ for (const n2 of nentry.meta.next) {
318
+ nextNext.add(n2);
319
+ }
277
320
  }
278
321
  }
322
+ next = nextNext;
279
323
  }
280
- next = nextNext;
281
- }
282
- prev = i;
283
- if (next) {
284
- for (const n of next) {
285
- if (!memoryLimit) {
286
- hashes.add(n);
287
- }
288
- else {
289
- const entry = await this.get(n);
290
- if (!entry) {
291
- break outer;
324
+ prev = i;
325
+ if (next) {
326
+ for (const n of next) {
327
+ if (!memoryLimit) {
328
+ hashes.add(n);
292
329
  }
293
- memoryCounter += entry.payload.byteLength;
294
- if (memoryCounter > memoryLimit) {
330
+ else {
331
+ const entry = await this.get(n);
332
+ if (!entry) {
333
+ break outer;
334
+ }
335
+ memoryCounter += entry.payload.byteLength;
336
+ if (memoryCounter > memoryLimit) {
337
+ break outer;
338
+ }
339
+ hashes.add(n);
340
+ }
341
+ if (hashes.size === pointerCount) {
295
342
  break outer;
296
343
  }
297
- hashes.add(n);
298
- }
299
- if (hashes.size === pointerCount) {
300
- break outer;
301
344
  }
302
345
  }
303
346
  }
304
347
  }
305
- }
306
- const ret = [];
307
- for (const hash of hashes) {
308
- const entry = await this.get(hash);
309
- if (entry) {
310
- ret.push(entry);
348
+ const ret = [];
349
+ for (const hash of hashes) {
350
+ const entry = await this.get(hash);
351
+ if (entry) {
352
+ ret.push(entry);
353
+ }
311
354
  }
312
- }
313
- return ret;
314
- }
315
- /**
316
- * Append an entry to the log.
317
- * @param {T} data The data to be appended
318
- * @param {AppendOptions} [options] The options for the append
319
- * @returns {{ entry: Entry<T>; removed: ShallowEntry[] }} The appended entry and an array of removed entries
320
- */
321
- async append(data, options = {}) {
322
- // Update the clock (find the latest clock)
323
- if (options.meta?.next) {
324
- for (const n of options.meta.next) {
325
- if (!n.hash)
326
- throw new Error("Expecting nexts to already be saved. missing hash for one or more entries");
327
- }
328
- }
329
- /* await this.load({ reload: false }); */
330
- const nexts = options.meta?.next ||
331
- (await this.entryIndex
332
- .getHeads(undefined, { type: "shape", shape: Sorting.ENTRY_SORT_SHAPE })
333
- .all());
334
- // Calculate max time for log/graph
335
- const clock = new Clock({
336
- id: this._identity.publicKey.bytes,
337
- timestamp: options?.meta?.timestamp || this._hlc.now(),
338
- });
339
- const entry = await EntryV0.create({
340
- store: this._storage,
341
- identity: options.identity || this._identity,
342
- signers: options.signers,
343
- data,
344
- meta: {
345
- clock,
346
- type: options.meta?.type,
347
- gidSeed: options.meta?.gidSeed,
348
- data: options.meta?.data,
349
- next: nexts,
350
- },
351
- encoding: this._encoding,
352
- encryption: options.encryption
353
- ? {
354
- keypair: options.encryption.keypair,
355
- receiver: {
356
- ...options.encryption.receiver,
357
- },
355
+ return ret;
356
+ }
357
+ /**
358
+ * Append an entry to the log.
359
+ * @param {T} data The data to be appended
360
+ * @param {AppendOptions} [options] The options for the append
361
+ * @returns {{ entry: Entry<T>; removed: ShallowEntry[] }} The appended entry and an array of removed entries
362
+ */
363
+ async append(data, options = {}) {
364
+ // Update the clock (find the latest clock)
365
+ if (options.meta?.next) {
366
+ for (const n of options.meta.next) {
367
+ if (!n.hash)
368
+ throw new Error("Expecting nexts to already be saved. missing hash for one or more entries");
358
369
  }
359
- : undefined,
360
- canAppend: options.canAppend || this._canAppend,
361
- });
362
- if (!entry.hash) {
363
- throw new Error("Unexpected");
364
- }
365
- if (entry.meta.type !== EntryType.CUT) {
366
- for (const e of nexts) {
367
- if (!(await this.has(e.hash))) {
368
- let entry;
369
- if (e instanceof Entry) {
370
- entry = e;
370
+ }
371
+ /* await this.load({ reload: false }); */
372
+ const nexts = options.meta?.next ||
373
+ (await this.entryIndex
374
+ .getHeads(undefined, { type: "shape", shape: Sorting.ENTRY_SORT_SHAPE })
375
+ .all());
376
+ // Calculate max time for log/graph
377
+ const clock = new Clock({
378
+ id: this._identity.publicKey.bytes,
379
+ timestamp: options?.meta?.timestamp || this._hlc.now(),
380
+ });
381
+ const entry = await EntryV0.create({
382
+ store: this._storage,
383
+ identity: options.identity || this._identity,
384
+ signers: options.signers,
385
+ data,
386
+ meta: {
387
+ clock,
388
+ type: options.meta?.type,
389
+ gidSeed: options.meta?.gidSeed,
390
+ data: options.meta?.data,
391
+ next: nexts,
392
+ },
393
+ encoding: this._encoding,
394
+ encryption: options.encryption
395
+ ? {
396
+ keypair: options.encryption.keypair,
397
+ receiver: {
398
+ ...options.encryption.receiver,
399
+ },
371
400
  }
372
- else {
373
- let resolved = await this.entryIndex.get(e.hash);
374
- if (!resolved) {
375
- // eslint-disable-next-line no-console
376
- console.warn("Unexpected missing entry when joining", e.hash);
377
- continue;
401
+ : undefined,
402
+ canAppend: options.canAppend || this._canAppend,
403
+ });
404
+ if (!entry.hash) {
405
+ throw new Error("Unexpected");
406
+ }
407
+ if (entry.meta.type !== EntryType.CUT) {
408
+ for (const e of nexts) {
409
+ if (!(await this.has(e.hash))) {
410
+ let entry;
411
+ if (e instanceof Entry) {
412
+ entry = e;
413
+ }
414
+ else {
415
+ let resolved = await this.entryIndex.get(e.hash);
416
+ if (!resolved) {
417
+ // eslint-disable-next-line no-console
418
+ console.warn("Unexpected missing entry when joining", e.hash);
419
+ continue;
420
+ }
421
+ entry = resolved;
378
422
  }
379
- entry = resolved;
423
+ await this.join([entry]);
380
424
  }
381
- await this.join([entry]);
382
425
  }
383
426
  }
384
- }
385
- await this.entryIndex.put(entry, {
386
- unique: true,
387
- isHead: true,
388
- toMultiHash: false,
389
- });
390
- const pendingDeletes = await this.processEntry(entry);
391
- entry.init({ encoding: this._encoding, keychain: this._keychain });
392
- const trimmed = await this.trim(options?.trim);
393
- if (trimmed) {
394
- for (const entry of trimmed) {
395
- pendingDeletes.push({ entry, fn: undefined });
396
- }
397
- }
398
- const removed = pendingDeletes.map((x) => x.entry);
399
- const changes = {
400
- added: [{ head: true, entry }],
401
- removed,
402
- };
403
- await (options?.onChange || this._onChange)?.(changes);
404
- await Promise.all(pendingDeletes.map((x) => x.fn?.()));
405
- return { entry, removed };
406
- }
407
- async remove(entry, options) {
408
- /* await this.load({ reload: false }); */
409
- const entries = Array.isArray(entry) ? entry : [entry];
410
- if (entries.length === 0) {
411
- return {
412
- added: [],
413
- removed: [],
427
+ await this.entryIndex.put(entry, {
428
+ unique: true,
429
+ isHead: true,
430
+ toMultiHash: false,
431
+ });
432
+ const pendingDeletes = await this.processEntry(entry);
433
+ entry.init({ encoding: this._encoding, keychain: this._keychain });
434
+ const trimmed = await this.trim(options?.trim);
435
+ if (trimmed) {
436
+ for (const entry of trimmed) {
437
+ pendingDeletes.push({ entry, fn: undefined });
438
+ }
439
+ }
440
+ const removed = pendingDeletes.map((x) => x.entry);
441
+ const changes = {
442
+ added: [{ head: true, entry }],
443
+ removed,
414
444
  };
415
- }
416
- let removed;
417
- if (options?.recursively) {
418
- removed = await this.prepareDeleteRecursively(entry);
419
- }
420
- else {
421
- removed = [];
422
- for (const entry of entries) {
423
- const deleteFn = await this.prepareDelete(entry.hash);
424
- deleteFn.entry &&
425
- removed.push({ entry: deleteFn.entry, fn: deleteFn.fn });
426
- }
427
- }
428
- const change = {
429
- added: [],
430
- removed: removed.map((x) => x.entry),
431
- };
432
- await this._onChange?.(change);
433
- // invoke deletions
434
- await Promise.all(removed.map((x) => x.fn()));
435
- return change;
436
- }
437
- async trim(option = this._trim.options) {
438
- return this._trim.trim(option);
439
- }
440
- async join(entriesOrLog, options) {
441
- let entries;
442
- let references = new Map();
443
- if (entriesOrLog instanceof Log_1) {
444
- if (entriesOrLog.entryIndex.length === 0)
445
- return;
446
- entries = await entriesOrLog.toArray();
447
- for (const element of entries) {
448
- references.set(element.hash, element);
445
+ await (options?.onChange || this._onChange)?.(changes);
446
+ await Promise.all(pendingDeletes.map((x) => x.fn?.()));
447
+ return { entry, removed };
448
+ }
449
+ async remove(entry, options) {
450
+ /* await this.load({ reload: false }); */
451
+ const entries = Array.isArray(entry) ? entry : [entry];
452
+ if (entries.length === 0) {
453
+ return {
454
+ added: [],
455
+ removed: [],
456
+ };
449
457
  }
450
- }
451
- else if (Array.isArray(entriesOrLog)) {
452
- if (entriesOrLog.length === 0) {
453
- return;
458
+ let removed;
459
+ if (options?.recursively) {
460
+ removed = await this.prepareDeleteRecursively(entry);
461
+ }
462
+ else {
463
+ removed = [];
464
+ for (const entry of entries) {
465
+ const deleteFn = await this.prepareDelete(entry.hash);
466
+ deleteFn.entry &&
467
+ removed.push({ entry: deleteFn.entry, fn: deleteFn.fn });
468
+ }
454
469
  }
455
- entries = [];
456
- for (const element of entriesOrLog) {
457
- if (element instanceof Entry) {
458
- entries.push(element);
470
+ const change = {
471
+ added: [],
472
+ removed: removed.map((x) => x.entry),
473
+ };
474
+ await this._onChange?.(change);
475
+ // invoke deletions
476
+ await Promise.all(removed.map((x) => x.fn()));
477
+ return change;
478
+ }
479
+ async trim(option = this._trim.options) {
480
+ return this._trim.trim(option);
481
+ }
482
+ async join(entriesOrLog, options) {
483
+ let entries;
484
+ let references = new Map();
485
+ if (entriesOrLog instanceof Log) {
486
+ if (entriesOrLog.entryIndex.length === 0)
487
+ return;
488
+ entries = await entriesOrLog.toArray();
489
+ for (const element of entries) {
459
490
  references.set(element.hash, element);
460
491
  }
461
- else if (typeof element === "string") {
462
- if ((await this.has(element)) && !options?.reset) {
463
- continue; // already in log
464
- }
465
- let entry = await Entry.fromMultihash(this._storage, element, {
466
- remote: {
467
- timeout: options?.timeout,
468
- },
469
- });
470
- if (!entry) {
471
- throw new Error("Missing entry in join by hash: " + element);
472
- }
473
- entries.push(entry);
492
+ }
493
+ else if (Array.isArray(entriesOrLog)) {
494
+ if (entriesOrLog.length === 0) {
495
+ return;
474
496
  }
475
- else if (element instanceof ShallowEntry) {
476
- if ((await this.has(element.hash)) && !options?.reset) {
477
- continue; // already in log
497
+ entries = [];
498
+ for (const element of entriesOrLog) {
499
+ if (element instanceof Entry) {
500
+ entries.push(element);
501
+ references.set(element.hash, element);
478
502
  }
479
- let entry = await Entry.fromMultihash(this._storage, element.hash, {
480
- remote: {
481
- timeout: options?.timeout,
482
- },
483
- });
484
- if (!entry) {
485
- throw new Error("Missing entry in join by hash: " + element.hash);
503
+ else if (typeof element === "string") {
504
+ if ((await this.has(element)) && !options?.reset) {
505
+ continue; // already in log
506
+ }
507
+ let entry = await Entry.fromMultihash(this._storage, element, {
508
+ remote: {
509
+ timeout: options?.timeout,
510
+ },
511
+ });
512
+ if (!entry) {
513
+ throw new Error("Missing entry in join by hash: " + element);
514
+ }
515
+ entries.push(entry);
486
516
  }
487
- entries.push(entry);
488
- }
489
- else {
490
- entries.push(element.entry);
491
- references.set(element.entry.hash, element.entry);
492
- for (const ref of element.references) {
493
- references.set(ref.hash, ref);
517
+ else if (element instanceof ShallowEntry) {
518
+ if ((await this.has(element.hash)) && !options?.reset) {
519
+ continue; // already in log
520
+ }
521
+ let entry = await Entry.fromMultihash(this._storage, element.hash, {
522
+ remote: {
523
+ timeout: options?.timeout,
524
+ },
525
+ });
526
+ if (!entry) {
527
+ throw new Error("Missing entry in join by hash: " + element.hash);
528
+ }
529
+ entries.push(entry);
530
+ }
531
+ else {
532
+ entries.push(element.entry);
533
+ references.set(element.entry.hash, element.entry);
534
+ for (const ref of element.references) {
535
+ references.set(ref.hash, ref);
536
+ }
494
537
  }
495
538
  }
496
539
  }
497
- }
498
- else {
499
- let all = await entriesOrLog.all(); // TODO dont load all at once
500
- if (all.length === 0) {
501
- return;
502
- }
503
- entries = all;
504
- }
505
- let heads = new Map();
506
- for (const entry of entries) {
507
- if (heads.has(entry.hash)) {
508
- continue;
509
- }
510
- heads.set(entry.hash, true);
511
- for (const next of await entry.getNext()) {
512
- heads.set(next, false);
513
- }
514
- }
515
- for (const entry of entries) {
516
- let isHead = heads.get(entry.hash);
517
- let prev = this._joining.get(entry.hash);
518
- if (prev) {
519
- await prev;
520
- }
521
540
  else {
522
- const p = this.joinRecursively(entry, {
523
- references,
524
- isHead,
525
- ...options,
526
- });
527
- this._joining.set(entry.hash, p);
528
- p.finally(() => {
529
- this._joining.delete(entry.hash);
530
- });
531
- await p;
541
+ let all = await entriesOrLog.all(); // TODO dont load all at once
542
+ if (all.length === 0) {
543
+ return;
544
+ }
545
+ entries = all;
532
546
  }
533
- }
534
- }
535
- /**
536
- * Bottom up join of entries into the log
537
- * @param entry
538
- * @param options
539
- * @returns
540
- */
541
- async joinRecursively(entry, options) {
542
- if (this.entryIndex.length > (options?.length ?? Number.MAX_SAFE_INTEGER)) {
543
- return false;
544
- }
545
- if (!entry.hash) {
546
- throw new Error("Unexpected");
547
- }
548
- if ((await this.has(entry.hash)) && !options.reset) {
549
- return false;
550
- }
551
- entry.init(this);
552
- if (options?.verifySignatures) {
553
- if (!(await entry.verifySignatures())) {
554
- throw new Error('Invalid signature entry with hash "' + entry.hash + '"');
555
- }
556
- }
557
- const headsWithGid = await this.entryIndex
558
- .getHeads(entry.meta.gid, { type: "shape", shape: ENTRY_JOIN_SHAPE })
559
- .all();
560
- if (headsWithGid) {
561
- for (const v of headsWithGid) {
562
- // TODO second argument should be a time compare instead? what about next nexts?
563
- // and check the cut entry is newer than the current 'entry'
564
- if (v.meta.type === EntryType.CUT &&
565
- v.meta.next.includes(entry.hash) &&
566
- Sorting.compare(entry, v, this._sortFn) < 0) {
567
- return false; // already deleted
547
+ let heads = new Map();
548
+ for (const entry of entries) {
549
+ if (heads.has(entry.hash)) {
550
+ continue;
551
+ }
552
+ heads.set(entry.hash, true);
553
+ for (const next of await entry.getNext()) {
554
+ heads.set(next, false);
568
555
  }
569
556
  }
570
- }
571
- if (entry.meta.type !== EntryType.CUT) {
572
- for (const a of entry.meta.next) {
573
- const prev = this._joining.get(a);
557
+ for (const entry of entries) {
558
+ let isHead = heads.get(entry.hash);
559
+ let prev = this._joining.get(entry.hash);
574
560
  if (prev) {
575
561
  await prev;
576
562
  }
577
- else if (!(await this.has(a)) || options.reset) {
578
- const nested = options.references?.get(a) ||
579
- (await Entry.fromMultihash(this._storage, a, {
580
- remote: { timeout: options?.remote?.timeout },
581
- }));
582
- if (!nested) {
583
- throw new Error("Missing entry in joinRecursively: " + a);
584
- }
585
- const p = this.joinRecursively(nested, options.isHead ? { ...options, isHead: false } : options);
586
- this._joining.set(nested.hash, p);
563
+ else {
564
+ const p = this.joinRecursively(entry, {
565
+ references,
566
+ isHead,
567
+ ...options,
568
+ });
569
+ this._joining.set(entry.hash, p);
587
570
  p.finally(() => {
588
- this._joining.delete(nested.hash);
571
+ this._joining.delete(entry.hash);
589
572
  });
590
573
  await p;
591
574
  }
592
575
  }
593
576
  }
594
- if (this?._canAppend && !(await this?._canAppend(entry))) {
595
- return false;
596
- }
597
- const clock = await entry.getClock();
598
- this._hlc.update(clock.timestamp);
599
- await this._entryIndex.put(entry, {
600
- unique: false,
601
- isHead: options.isHead,
602
- toMultiHash: true,
603
- });
604
- const pendingDeletes = await this.processEntry(entry);
605
- const trimmed = await this.trim(options?.trim);
606
- if (trimmed) {
607
- for (const entry of trimmed) {
608
- pendingDeletes.push({ entry, fn: undefined });
609
- }
610
- }
611
- const removed = pendingDeletes.map((x) => x.entry);
612
- await options?.onChange?.({
613
- added: [{ head: options.isHead, entry }],
614
- removed: removed,
615
- });
616
- await this._onChange?.({
617
- added: [{ head: options.isHead, entry }],
618
- removed: removed,
619
- });
620
- await Promise.all(pendingDeletes.map((x) => x.fn?.()));
621
- return true;
622
- }
623
- async processEntry(entry) {
624
- if (entry.meta.type === EntryType.CUT) {
625
- return this.prepareDeleteRecursively(entry, true);
626
- }
627
- return [];
628
- }
629
- async deleteRecursively(from, skipFirst = false) {
630
- const toDelete = await this.prepareDeleteRecursively(from, skipFirst);
631
- const promises = toDelete.map(async (x) => {
632
- const removed = await x.fn();
633
- if (removed) {
634
- return removed;
635
- }
636
- return undefined;
637
- });
638
- const results = Promise.all(promises);
639
- return (await results).filter((x) => x);
640
- }
641
- /// TODO simplify methods below
642
- async prepareDeleteRecursively(from, skipFirst = false) {
643
- const stack = Array.isArray(from) ? [...from] : [from];
644
- const promises = [];
645
- let counter = 0;
646
- const toDelete = [];
647
- while (stack.length > 0) {
648
- const entry = stack.pop();
649
- const skip = counter === 0 && skipFirst;
650
- if (!skip) {
651
- const deleteFn = await this.prepareDelete(entry.hash);
652
- deleteFn.entry &&
653
- toDelete.push({ entry: deleteFn.entry, fn: deleteFn.fn });
654
- }
655
- for (const next of entry.meta.next) {
656
- const nextFromNext = this.entryIndex.getHasNext(next);
657
- const entriesThatHasNext = await nextFromNext.all();
658
- // if there are no entries which is not of "CUT" type, we can safely delete the next entry
659
- // figureately speaking, these means where are cutting all branches to a stem, so we can delete the stem as well
660
- let hasAlternativeNext = !!entriesThatHasNext.find((x) => x.meta.type !== EntryType.CUT && x.hash !== entry.hash);
661
- if (!hasAlternativeNext) {
662
- const ne = await this.get(next);
663
- if (ne) {
664
- stack.push(ne);
577
+ /**
578
+ * Bottom up join of entries into the log
579
+ * @param entry
580
+ * @param options
581
+ * @returns
582
+ */
583
+ async joinRecursively(entry, options) {
584
+ if (this.entryIndex.length > (options?.length ?? Number.MAX_SAFE_INTEGER)) {
585
+ return false;
586
+ }
587
+ if (!entry.hash) {
588
+ throw new Error("Unexpected");
589
+ }
590
+ if ((await this.has(entry.hash)) && !options.reset) {
591
+ return false;
592
+ }
593
+ entry.init(this);
594
+ if (options?.verifySignatures) {
595
+ if (!(await entry.verifySignatures())) {
596
+ throw new Error('Invalid signature entry with hash "' + entry.hash + '"');
597
+ }
598
+ }
599
+ const headsWithGid = await this.entryIndex
600
+ .getHeads(entry.meta.gid, { type: "shape", shape: ENTRY_JOIN_SHAPE })
601
+ .all();
602
+ if (headsWithGid) {
603
+ for (const v of headsWithGid) {
604
+ // TODO second argument should be a time compare instead? what about next nexts?
605
+ // and check the cut entry is newer than the current 'entry'
606
+ if (v.meta.type === EntryType.CUT &&
607
+ v.meta.next.includes(entry.hash) &&
608
+ Sorting.compare(entry, v, this._sortFn) < 0) {
609
+ return false; // already deleted
665
610
  }
666
611
  }
667
612
  }
668
- counter++;
669
- }
670
- await Promise.all(promises);
671
- return toDelete;
672
- }
673
- async prepareDelete(hash) {
674
- let entry = await this._entryIndex.getShallow(hash);
675
- if (!entry) {
676
- return { entry: undefined };
677
- }
678
- return {
679
- entry: entry.value,
680
- fn: async () => {
681
- await this._trim.deleteFromCache(hash);
682
- const removedEntry = (await this._entryIndex.delete(hash, entry.value));
683
- return removedEntry;
684
- },
685
- };
686
- }
687
- async delete(hash) {
688
- const deleteFn = await this.prepareDelete(hash);
689
- return deleteFn.entry && deleteFn.fn();
690
- }
691
- /**
692
- * Returns the log entries as a formatted string.
693
- * @returns {string}
694
- * @example
695
- * two
696
- * └─one
697
- * └─three
698
- */
699
- async toString(payloadMapper = (payload) => payload.getValue(this.encoding).toString()) {
700
- return (await Promise.all((await this.toArray())
701
- .slice()
702
- .reverse()
703
- .map(async (e, idx) => {
704
- const parents = Entry.findDirectChildren(e, await this.toArray());
705
- const len = parents.length;
706
- let padding = new Array(Math.max(len - 1, 0));
707
- padding = len > 1 ? padding.fill(" ") : padding;
708
- padding = len > 0 ? padding.concat(["└─"]) : padding;
709
- return (padding.join("") +
710
- (payloadMapper?.(e.payload) || e.payload));
711
- }))).join("\n");
712
- }
713
- async close() {
714
- // Don't return early here if closed = true, because "load" might create processes that needs to be closed
715
- 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
716
- this._closeController.abort();
717
- await this._indexer?.stop?.();
718
- this._indexer = undefined;
719
- this._loadedOnce = false;
720
- }
721
- async drop() {
722
- // Don't return early here if closed = true, because "load" might create processes that needs to be closed
723
- 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
724
- this._closeController.abort();
725
- await this.entryIndex?.clear();
726
- await this._indexer?.drop();
727
- await this._indexer?.stop?.();
728
- }
729
- async recover() {
730
- // merge existing
731
- const existing = await this.getHeads(true).all();
732
- const allHeads = new Map();
733
- for (const head of existing) {
734
- allHeads.set(head.hash, head);
613
+ if (entry.meta.type !== EntryType.CUT) {
614
+ for (const a of entry.meta.next) {
615
+ const prev = this._joining.get(a);
616
+ if (prev) {
617
+ await prev;
618
+ }
619
+ else if (!(await this.has(a)) || options.reset) {
620
+ const nested = options.references?.get(a) ||
621
+ (await Entry.fromMultihash(this._storage, a, {
622
+ remote: { timeout: options?.remote?.timeout },
623
+ }));
624
+ if (!nested) {
625
+ throw new Error("Missing entry in joinRecursively: " + a);
626
+ }
627
+ const p = this.joinRecursively(nested, options.isHead ? { ...options, isHead: false } : options);
628
+ this._joining.set(nested.hash, p);
629
+ p.finally(() => {
630
+ this._joining.delete(nested.hash);
631
+ });
632
+ await p;
633
+ }
634
+ }
635
+ }
636
+ if (this?._canAppend && !(await this?._canAppend(entry))) {
637
+ return false;
638
+ }
639
+ const clock = await entry.getClock();
640
+ this._hlc.update(clock.timestamp);
641
+ await this._entryIndex.put(entry, {
642
+ unique: false,
643
+ isHead: options.isHead,
644
+ toMultiHash: true,
645
+ });
646
+ const pendingDeletes = await this.processEntry(entry);
647
+ const trimmed = await this.trim(options?.trim);
648
+ if (trimmed) {
649
+ for (const entry of trimmed) {
650
+ pendingDeletes.push({ entry, fn: undefined });
651
+ }
652
+ }
653
+ const removed = pendingDeletes.map((x) => x.entry);
654
+ await options?.onChange?.({
655
+ added: [{ head: options.isHead, entry }],
656
+ removed: removed,
657
+ });
658
+ await this._onChange?.({
659
+ added: [{ head: options.isHead, entry }],
660
+ removed: removed,
661
+ });
662
+ await Promise.all(pendingDeletes.map((x) => x.fn?.()));
663
+ return true;
735
664
  }
736
- // fetch all possible entries
737
- for await (const [key, value] of this._storage.iterator()) {
738
- if (allHeads.has(key)) {
739
- continue;
665
+ async processEntry(entry) {
666
+ if (entry.meta.type === EntryType.CUT) {
667
+ return this.prepareDeleteRecursively(entry, true);
740
668
  }
741
- try {
742
- cidifyString(key);
669
+ return [];
670
+ }
671
+ async deleteRecursively(from, skipFirst = false) {
672
+ const toDelete = await this.prepareDeleteRecursively(from, skipFirst);
673
+ const promises = toDelete.map(async (x) => {
674
+ const removed = await x.fn();
675
+ if (removed) {
676
+ return removed;
677
+ }
678
+ return undefined;
679
+ });
680
+ const results = Promise.all(promises);
681
+ return (await results).filter((x) => x);
682
+ }
683
+ /// TODO simplify methods below
684
+ async prepareDeleteRecursively(from, skipFirst = false) {
685
+ const stack = Array.isArray(from) ? [...from] : [from];
686
+ const promises = [];
687
+ let counter = 0;
688
+ const toDelete = [];
689
+ while (stack.length > 0) {
690
+ const entry = stack.pop();
691
+ const skip = counter === 0 && skipFirst;
692
+ if (!skip) {
693
+ const deleteFn = await this.prepareDelete(entry.hash);
694
+ deleteFn.entry &&
695
+ toDelete.push({ entry: deleteFn.entry, fn: deleteFn.fn });
696
+ }
697
+ for (const next of entry.meta.next) {
698
+ const nextFromNext = this.entryIndex.getHasNext(next);
699
+ const entriesThatHasNext = await nextFromNext.all();
700
+ // if there are no entries which is not of "CUT" type, we can safely delete the next entry
701
+ // figureately speaking, these means where are cutting all branches to a stem, so we can delete the stem as well
702
+ let hasAlternativeNext = !!entriesThatHasNext.find((x) => x.meta.type !== EntryType.CUT && x.hash !== entry.hash);
703
+ if (!hasAlternativeNext) {
704
+ const ne = await this.get(next);
705
+ if (ne) {
706
+ stack.push(ne);
707
+ }
708
+ }
709
+ }
710
+ counter++;
743
711
  }
744
- catch (error) {
745
- continue;
712
+ await Promise.all(promises);
713
+ return toDelete;
714
+ }
715
+ async prepareDelete(hash) {
716
+ let entry = await this._entryIndex.getShallow(hash);
717
+ if (!entry) {
718
+ return { entry: undefined };
746
719
  }
747
- try {
748
- const der = deserialize(value, Entry);
749
- der.hash = key;
750
- der.init(this);
751
- allHeads.set(key, der);
720
+ return {
721
+ entry: entry.value,
722
+ fn: async () => {
723
+ await this._trim.deleteFromCache(hash);
724
+ const removedEntry = (await this._entryIndex.delete(hash, entry.value));
725
+ return removedEntry;
726
+ },
727
+ };
728
+ }
729
+ async delete(hash) {
730
+ const deleteFn = await this.prepareDelete(hash);
731
+ return deleteFn.entry && deleteFn.fn();
732
+ }
733
+ /**
734
+ * Returns the log entries as a formatted string.
735
+ * @returns {string}
736
+ * @example
737
+ * two
738
+ * └─one
739
+ * └─three
740
+ */
741
+ async toString(payloadMapper = (payload) => payload.getValue(this.encoding).toString()) {
742
+ return (await Promise.all((await this.toArray())
743
+ .slice()
744
+ .reverse()
745
+ .map(async (e, idx) => {
746
+ const parents = Entry.findDirectChildren(e, await this.toArray());
747
+ const len = parents.length;
748
+ let padding = new Array(Math.max(len - 1, 0));
749
+ padding = len > 1 ? padding.fill(" ") : padding;
750
+ padding = len > 0 ? padding.concat(["└─"]) : padding;
751
+ return (padding.join("") +
752
+ (payloadMapper?.(e.payload) || e.payload));
753
+ }))).join("\n");
754
+ }
755
+ async close() {
756
+ // Don't return early here if closed = true, because "load" might create processes that needs to be closed
757
+ 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
758
+ this._closeController.abort();
759
+ await this._indexer?.stop?.();
760
+ this._indexer = undefined;
761
+ this._loadedOnce = false;
762
+ }
763
+ async drop() {
764
+ // Don't return early here if closed = true, because "load" might create processes that needs to be closed
765
+ 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
766
+ this._closeController.abort();
767
+ await this.entryIndex?.clear();
768
+ await this._indexer?.drop();
769
+ await this._indexer?.stop?.();
770
+ }
771
+ async recover() {
772
+ // merge existing
773
+ const existing = await this.getHeads(true).all();
774
+ const allHeads = new Map();
775
+ for (const head of existing) {
776
+ allHeads.set(head.hash, head);
752
777
  }
753
- catch (error) {
754
- continue; // invalid entries
778
+ // fetch all possible entries
779
+ for await (const [key, value] of this._storage.iterator()) {
780
+ if (allHeads.has(key)) {
781
+ continue;
782
+ }
783
+ try {
784
+ cidifyString(key);
785
+ }
786
+ catch (error) {
787
+ continue;
788
+ }
789
+ try {
790
+ const der = deserialize(value, Entry);
791
+ der.hash = key;
792
+ der.init(this);
793
+ allHeads.set(key, der);
794
+ }
795
+ catch (error) {
796
+ continue; // invalid entries
797
+ }
755
798
  }
799
+ // assume they are valid, (let access control reject them if not)
800
+ await this.load({ reset: true, heads: [...allHeads.values()] });
756
801
  }
757
- // assume they are valid, (let access control reject them if not)
758
- await this.load({ reset: true, heads: [...allHeads.values()] });
759
- }
760
- async load(opts = {}) {
761
- if (this.closed) {
762
- throw new Error("Closed");
763
- }
764
- if (this._loadedOnce && !opts.reset) {
765
- return;
766
- }
767
- this._loadedOnce = true;
768
- const heads = opts.heads ??
769
- (await this.entryIndex
770
- .getHeads(undefined, {
771
- type: "full",
772
- signal: this._closeController.signal,
773
- ignoreMissing: opts.ignoreMissing,
774
- timeout: opts.timeout,
775
- })
776
- .all());
777
- if (heads) {
778
- // Load the log
779
- await this.join(heads instanceof Entry ? [heads] : heads, {
780
- timeout: opts?.fetchEntryTimeout,
781
- reset: opts?.reset,
782
- });
783
- if (opts.heads) {
784
- // remove all heads that are not in the provided heads
785
- const allHeads = this.getHeads(false);
786
- const allProvidedHeadsHashes = new Set(opts.heads.map((x) => x.hash));
787
- while (!allHeads.done()) {
788
- let next = await allHeads.next(100);
789
- for (const head of next) {
790
- if (!allProvidedHeadsHashes.has(head.hash)) {
791
- await this.remove(head);
802
+ async load(opts = {}) {
803
+ if (this.closed) {
804
+ throw new Error("Closed");
805
+ }
806
+ if (this._loadedOnce && !opts.reset) {
807
+ return;
808
+ }
809
+ this._loadedOnce = true;
810
+ const heads = opts.heads ??
811
+ (await this.entryIndex
812
+ .getHeads(undefined, {
813
+ type: "full",
814
+ signal: this._closeController.signal,
815
+ ignoreMissing: opts.ignoreMissing,
816
+ timeout: opts.timeout,
817
+ })
818
+ .all());
819
+ if (heads) {
820
+ // Load the log
821
+ await this.join(heads instanceof Entry ? [heads] : heads, {
822
+ timeout: opts?.fetchEntryTimeout,
823
+ reset: opts?.reset,
824
+ });
825
+ if (opts.heads) {
826
+ // remove all heads that are not in the provided heads
827
+ const allHeads = this.getHeads(false);
828
+ const allProvidedHeadsHashes = new Set(opts.heads.map((x) => x.hash));
829
+ while (!allHeads.done()) {
830
+ let next = await allHeads.next(100);
831
+ for (const head of next) {
832
+ if (!allProvidedHeadsHashes.has(head.hash)) {
833
+ await this.remove(head);
834
+ }
792
835
  }
793
836
  }
794
837
  }
795
838
  }
796
839
  }
797
- }
798
- static async fromEntry(store, identity, entryOrHash, options = { id: randomBytes(32) }) {
799
- const log = new Log_1(options.id && { id: options.id });
800
- await log.open(store, identity, options);
801
- await log.join(!Array.isArray(entryOrHash) ? [entryOrHash] : entryOrHash, {
802
- timeout: options.timeout,
803
- trim: options.trim,
804
- verifySignatures: true,
805
- });
806
- return log;
807
- }
808
- /**
809
- * Find heads from a collection of entries.
810
- *
811
- * Finds entries that are the heads of this collection,
812
- * ie. entries that are not referenced by other entries.
813
- *
814
- * @param {Array<Entry<T>>} entries - Entries to search heads from
815
- * @returns {Array<Entry<T>>}
816
- */
817
- static findHeads(entries) {
818
- const indexReducer = (res, entry) => {
819
- const addToResult = (e) => (res[e] = entry.hash);
820
- entry.meta.next.forEach(addToResult);
821
- return res;
822
- };
823
- const items = entries.reduce(indexReducer, {});
824
- const exists = (e) => items[e.hash] === undefined;
825
- return entries.filter(exists);
826
- }
827
- // Find entries that point to another entry that is not in the
828
- // input array
829
- static findTails(entries) {
830
- // Reverse index { next -> entry }
831
- const reverseIndex = {};
832
- // Null index containing entries that have no parents (nexts)
833
- const nullIndex = [];
834
- // Hashes for all entries for quick lookups
835
- const hashes = {};
836
- // Hashes of all next entries
837
- let nexts = [];
838
- const addToIndex = (e) => {
839
- if (e.meta.next.length === 0) {
840
- nullIndex.push(e);
841
- }
842
- const addToReverseIndex = (a) => {
843
- /* istanbul ignore else */
844
- if (!reverseIndex[a])
845
- reverseIndex[a] = [];
846
- reverseIndex[a].push(e);
840
+ static async fromEntry(store, identity, entryOrHash, options = { id: randomBytes(32) }) {
841
+ const log = new Log(options.id && { id: options.id });
842
+ await log.open(store, identity, options);
843
+ await log.join(!Array.isArray(entryOrHash) ? [entryOrHash] : entryOrHash, {
844
+ timeout: options.timeout,
845
+ trim: options.trim,
846
+ verifySignatures: true,
847
+ });
848
+ return log;
849
+ }
850
+ /**
851
+ * Find heads from a collection of entries.
852
+ *
853
+ * Finds entries that are the heads of this collection,
854
+ * ie. entries that are not referenced by other entries.
855
+ *
856
+ * @param {Array<Entry<T>>} entries - Entries to search heads from
857
+ * @returns {Array<Entry<T>>}
858
+ */
859
+ static findHeads(entries) {
860
+ const indexReducer = (res, entry) => {
861
+ const addToResult = (e) => (res[e] = entry.hash);
862
+ entry.meta.next.forEach(addToResult);
863
+ return res;
847
864
  };
848
- // Add all entries and their parents to the reverse index
849
- e.meta.next.forEach(addToReverseIndex);
850
- // Get all next references
851
- nexts = nexts.concat(e.meta.next);
852
- // Get the hashes of input entries
853
- hashes[e.hash] = true;
854
- };
855
- // Create our indices
856
- entries.forEach(addToIndex);
857
- const addUniques = (res, entries, _idx, _arr) => res.concat(findUniques(entries, "hash"));
858
- const exists = (e) => hashes[e] === undefined;
859
- const findFromReverseIndex = (e) => reverseIndex[e];
860
- // Drop hashes that are not in the input entries
861
- const tails = nexts // For every hash in nexts:
862
- .filter(exists) // Remove undefineds and nulls
863
- .map(findFromReverseIndex) // Get the Entry from the reverse index
864
- .reduce(addUniques, []) // Flatten the result and take only uniques
865
- .concat(nullIndex); // Combine with tails the have no next refs (ie. first-in-their-chain)
866
- return findUniques(tails, "hash").sort(Entry.compare);
867
- }
868
- // Find the hashes to entries that are not in a collection
869
- // but referenced by other entries
870
- static findTailHashes(entries) {
871
- const hashes = {};
872
- const addToIndex = (e) => (hashes[e.hash] = true);
873
- const reduceTailHashes = (res, entry, idx, arr) => {
874
- const addToResult = (e) => {
875
- /* istanbul ignore else */
876
- if (hashes[e] === undefined) {
877
- res.splice(0, 0, e);
865
+ const items = entries.reduce(indexReducer, {});
866
+ const exists = (e) => items[e.hash] === undefined;
867
+ return entries.filter(exists);
868
+ }
869
+ // Find entries that point to another entry that is not in the
870
+ // input array
871
+ static findTails(entries) {
872
+ // Reverse index { next -> entry }
873
+ const reverseIndex = {};
874
+ // Null index containing entries that have no parents (nexts)
875
+ const nullIndex = [];
876
+ // Hashes for all entries for quick lookups
877
+ const hashes = {};
878
+ // Hashes of all next entries
879
+ let nexts = [];
880
+ const addToIndex = (e) => {
881
+ if (e.meta.next.length === 0) {
882
+ nullIndex.push(e);
878
883
  }
884
+ const addToReverseIndex = (a) => {
885
+ /* istanbul ignore else */
886
+ if (!reverseIndex[a])
887
+ reverseIndex[a] = [];
888
+ reverseIndex[a].push(e);
889
+ };
890
+ // Add all entries and their parents to the reverse index
891
+ e.meta.next.forEach(addToReverseIndex);
892
+ // Get all next references
893
+ nexts = nexts.concat(e.meta.next);
894
+ // Get the hashes of input entries
895
+ hashes[e.hash] = true;
879
896
  };
880
- entry.meta.next.reverse().forEach(addToResult);
881
- return res;
882
- };
883
- entries.forEach(addToIndex);
884
- return entries.reduce(reduceTailHashes, []);
885
- }
886
- };
887
- __decorate([
888
- field({ type: fixedArray("u8", 32) }),
889
- __metadata("design:type", Uint8Array)
890
- ], Log.prototype, "_id", void 0);
891
- Log = Log_1 = __decorate([
892
- variant(0),
893
- __metadata("design:paramtypes", [Object])
894
- ], Log);
897
+ // Create our indices
898
+ entries.forEach(addToIndex);
899
+ const addUniques = (res, entries, _idx, _arr) => res.concat(findUniques(entries, "hash"));
900
+ const exists = (e) => hashes[e] === undefined;
901
+ const findFromReverseIndex = (e) => reverseIndex[e];
902
+ // Drop hashes that are not in the input entries
903
+ const tails = nexts // For every hash in nexts:
904
+ .filter(exists) // Remove undefineds and nulls
905
+ .map(findFromReverseIndex) // Get the Entry from the reverse index
906
+ .reduce(addUniques, []) // Flatten the result and take only uniques
907
+ .concat(nullIndex); // Combine with tails the have no next refs (ie. first-in-their-chain)
908
+ return findUniques(tails, "hash").sort(Entry.compare);
909
+ }
910
+ // Find the hashes to entries that are not in a collection
911
+ // but referenced by other entries
912
+ static findTailHashes(entries) {
913
+ const hashes = {};
914
+ const addToIndex = (e) => (hashes[e.hash] = true);
915
+ const reduceTailHashes = (res, entry, idx, arr) => {
916
+ const addToResult = (e) => {
917
+ /* istanbul ignore else */
918
+ if (hashes[e] === undefined) {
919
+ res.splice(0, 0, e);
920
+ }
921
+ };
922
+ entry.meta.next.reverse().forEach(addToResult);
923
+ return res;
924
+ };
925
+ entries.forEach(addToIndex);
926
+ return entries.reduce(reduceTailHashes, []);
927
+ }
928
+ };
929
+ return Log = _classThis;
930
+ })();
895
931
  export { Log };
896
932
  //# sourceMappingURL=log.js.map