@peerbit/log 4.1.11 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmark/payload.js +98 -49
- package/dist/benchmark/payload.js.map +1 -1
- package/dist/src/clock.d.ts +1 -1
- package/dist/src/clock.d.ts.map +1 -1
- package/dist/src/clock.js +165 -115
- package/dist/src/clock.js.map +1 -1
- package/dist/src/entry-index.d.ts.map +1 -1
- package/dist/src/entry-index.js +4 -3
- package/dist/src/entry-index.js.map +1 -1
- package/dist/src/entry-shallow.js +119 -69
- package/dist/src/entry-shallow.js.map +1 -1
- package/dist/src/entry-v0.d.ts.map +1 -1
- package/dist/src/entry-v0.js +451 -381
- package/dist/src/entry-v0.js.map +1 -1
- package/dist/src/entry.d.ts +1 -1
- package/dist/src/heads-cache.d.ts +2 -3
- package/dist/src/heads-cache.d.ts.map +1 -1
- package/dist/src/heads-cache.js +128 -64
- package/dist/src/heads-cache.js.map +1 -1
- package/dist/src/log-sorting.d.ts +1 -1
- package/dist/src/log-sorting.d.ts.map +1 -1
- package/dist/src/log.js +838 -802
- package/dist/src/log.js.map +1 -1
- package/dist/src/logger.d.ts +1 -3
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +2 -3
- package/dist/src/logger.js.map +1 -1
- package/dist/src/payload.js +80 -43
- package/dist/src/payload.js.map +1 -1
- package/dist/src/snapshot.d.ts +1 -1
- package/dist/src/snapshot.d.ts.map +1 -1
- package/dist/src/snapshot.js +87 -45
- package/dist/src/snapshot.js.map +1 -1
- package/package.json +17 -17
- package/src/clock.ts +1 -5
- package/src/entry-index.ts +5 -3
- package/src/entry-v0.ts +6 -6
- package/src/heads-cache.ts +2 -4
- package/src/log-sorting.ts +1 -1
- package/src/logger.ts +2 -3
- package/src/snapshot.ts +8 -6
package/dist/src/log.js
CHANGED
|
@@ -1,13 +1,37 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
8
|
-
|
|
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 =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
294
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
|
423
|
+
await this.join([entry]);
|
|
380
424
|
}
|
|
381
|
-
await this.join([entry]);
|
|
382
425
|
}
|
|
383
426
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
removed
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
572
|
-
|
|
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
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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(
|
|
571
|
+
this._joining.delete(entry.hash);
|
|
589
572
|
});
|
|
590
573
|
await p;
|
|
591
574
|
}
|
|
592
575
|
}
|
|
593
576
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
.
|
|
702
|
-
.
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
continue;
|
|
665
|
+
async processEntry(entry) {
|
|
666
|
+
if (entry.meta.type === EntryType.CUT) {
|
|
667
|
+
return this.prepareDeleteRecursively(entry, true);
|
|
740
668
|
}
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
|
|
745
|
-
|
|
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
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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
|
-
|
|
754
|
-
|
|
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
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
.
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
849
|
-
e.
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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
|