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