@peerbit/log 1.0.14 → 2.0.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/lib/esm/clock.d.ts +1 -1
- package/lib/esm/clock.js +11 -13
- package/lib/esm/clock.js.map +1 -1
- package/lib/esm/encoding.js +1 -1
- package/lib/esm/entry-index.d.ts +5 -2
- package/lib/esm/entry-index.js +9 -3
- package/lib/esm/entry-index.js.map +1 -1
- package/lib/esm/entry.d.ts +40 -27
- package/lib/esm/entry.js +115 -115
- package/lib/esm/entry.js.map +1 -1
- package/lib/esm/heads-cache.js +1 -1
- package/lib/esm/heads.d.ts +13 -8
- package/lib/esm/heads.js +52 -44
- package/lib/esm/heads.js.map +1 -1
- package/lib/esm/log-sorting.d.ts +3 -3
- package/lib/esm/log-sorting.js +2 -2
- package/lib/esm/log-sorting.js.map +1 -1
- package/lib/esm/log.d.ts +10 -6
- package/lib/esm/log.js +40 -52
- package/lib/esm/log.js.map +1 -1
- package/lib/esm/trim.d.ts +2 -2
- package/lib/esm/trim.js +10 -9
- package/lib/esm/trim.js.map +1 -1
- package/lib/esm/values.d.ts +5 -11
- package/lib/esm/values.js +17 -17
- package/lib/esm/values.js.map +1 -1
- package/package.json +5 -5
- package/src/clock.ts +11 -13
- package/src/encoding.ts +1 -1
- package/src/entry-index.ts +13 -6
- package/src/entry.ts +139 -138
- package/src/heads-cache.ts +1 -1
- package/src/heads.ts +76 -57
- package/src/log-sorting.ts +20 -16
- package/src/log.ts +61 -59
- package/src/trim.ts +14 -12
- package/src/values.ts +24 -32
- package/lib/esm/is-defined.d.ts +0 -1
- package/lib/esm/is-defined.js +0 -2
- package/lib/esm/is-defined.js.map +0 -1
- package/src/is-defined.ts +0 -1
package/src/heads.ts
CHANGED
|
@@ -4,34 +4,47 @@ import { HeadsCache } from "./heads-cache.js";
|
|
|
4
4
|
import { Blocks } from "@peerbit/blocks-interface";
|
|
5
5
|
import { Keychain } from "@peerbit/crypto";
|
|
6
6
|
import { Encoding } from "./encoding.js";
|
|
7
|
+
import { Values } from "./values.js";
|
|
8
|
+
import { logger } from "./logger.js";
|
|
9
|
+
import { EntryIndex } from "./entry-index.js";
|
|
7
10
|
|
|
8
11
|
export type CacheUpdateOptions = {
|
|
9
12
|
cache?: { update?: false; reset?: false } | { update: true; reset?: boolean };
|
|
10
13
|
};
|
|
11
14
|
|
|
12
|
-
interface
|
|
15
|
+
interface Log<T> {
|
|
13
16
|
storage: Blocks;
|
|
14
17
|
keychain?: Keychain;
|
|
15
18
|
memory?: SimpleLevel;
|
|
16
19
|
encoding: Encoding<any>;
|
|
20
|
+
entryIndex: EntryIndex<T>;
|
|
21
|
+
values: Values<T>;
|
|
17
22
|
}
|
|
18
23
|
export class HeadsIndex<T> {
|
|
19
24
|
private _id: Uint8Array;
|
|
20
25
|
private _index: Set<string> = new Set();
|
|
21
|
-
private _gids: Map<string,
|
|
26
|
+
private _gids: Map<string, Map<string, Entry<T>>>; // gid -> hash -> entry
|
|
22
27
|
private _headsCache: HeadsCache<T> | undefined;
|
|
23
|
-
private _config:
|
|
28
|
+
private _config: Log<T>;
|
|
29
|
+
private _onGidRemoved?: (gid: string[]) => Promise<void> | void;
|
|
24
30
|
constructor(id: Uint8Array) {
|
|
25
31
|
this._gids = new Map();
|
|
26
32
|
this._id = id;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
async init(
|
|
30
|
-
|
|
35
|
+
async init(
|
|
36
|
+
log: Log<T>,
|
|
37
|
+
options: {
|
|
38
|
+
entries?: Entry<T>[];
|
|
39
|
+
onGidRemoved?: (gid: string[]) => Promise<void> | void;
|
|
40
|
+
} = {}
|
|
41
|
+
) {
|
|
42
|
+
this._config = log;
|
|
43
|
+
this._onGidRemoved = options.onGidRemoved;
|
|
31
44
|
await this.reset(options?.entries || []);
|
|
32
|
-
if (
|
|
45
|
+
if (log.memory) {
|
|
33
46
|
this._headsCache = new HeadsCache(this);
|
|
34
|
-
return this._headsCache.init(await
|
|
47
|
+
return this._headsCache.init(await log.memory.sublevel("heads"));
|
|
35
48
|
}
|
|
36
49
|
}
|
|
37
50
|
|
|
@@ -41,7 +54,7 @@ export class HeadsIndex<T> {
|
|
|
41
54
|
replicate?: boolean;
|
|
42
55
|
reload?: boolean;
|
|
43
56
|
} & CacheUpdateOptions
|
|
44
|
-
) {
|
|
57
|
+
): Promise<Entry<T>[] | undefined> {
|
|
45
58
|
if (!this._headsCache || (this._headsCache.loaded && !options?.reload)) {
|
|
46
59
|
return;
|
|
47
60
|
}
|
|
@@ -51,25 +64,19 @@ export class HeadsIndex<T> {
|
|
|
51
64
|
if (!heads) {
|
|
52
65
|
return;
|
|
53
66
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
await this.reset(entries);
|
|
68
|
-
return entries;
|
|
69
|
-
} catch (error) {
|
|
70
|
-
const q = 123;
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
67
|
+
const entries = await Promise.all(
|
|
68
|
+
heads.map(async (x) => {
|
|
69
|
+
const entry = await this._config.entryIndex.get(x, { load: true });
|
|
70
|
+
if (!entry) {
|
|
71
|
+
logger.error("Failed to load entry from head with hash: " + x);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
await entry.getMeta(); // TODO types,decrypt gid
|
|
75
|
+
return entry;
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
await this.reset(entries.filter((x) => !!x) as Entry<any>[]);
|
|
79
|
+
return entries as Entry<any>[];
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
get headsCache(): HeadsCache<T> | undefined {
|
|
@@ -92,7 +99,7 @@ export class HeadsIndex<T> {
|
|
|
92
99
|
return this._index;
|
|
93
100
|
}
|
|
94
101
|
|
|
95
|
-
get gids(): Map<string,
|
|
102
|
+
get gids(): Map<string, Map<string, Entry<T>>> {
|
|
96
103
|
return this._gids;
|
|
97
104
|
}
|
|
98
105
|
|
|
@@ -105,10 +112,16 @@ export class HeadsIndex<T> {
|
|
|
105
112
|
options: CacheUpdateOptions = { cache: { reset: true, update: true } }
|
|
106
113
|
) {
|
|
107
114
|
this._index.clear();
|
|
115
|
+
const gidKeys = [...this._gids.keys()];
|
|
116
|
+
|
|
108
117
|
this._gids = new Map();
|
|
109
|
-
if (entries) {
|
|
118
|
+
if (entries?.length > 0) {
|
|
110
119
|
await this.putAll(entries, options); // reset cache = true
|
|
111
120
|
}
|
|
121
|
+
|
|
122
|
+
if (gidKeys.length > 0) {
|
|
123
|
+
this._onGidRemoved?.(gidKeys);
|
|
124
|
+
}
|
|
112
125
|
}
|
|
113
126
|
|
|
114
127
|
has(cid: string) {
|
|
@@ -116,14 +129,14 @@ export class HeadsIndex<T> {
|
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
async put(entry: Entry<T>, options?: CacheUpdateOptions) {
|
|
119
|
-
this._putOne(entry);
|
|
132
|
+
await this._putOne(entry);
|
|
120
133
|
if (options?.cache?.update) {
|
|
121
134
|
await this._headsCache?.queue({ added: [entry] }, options.cache.reset);
|
|
122
135
|
}
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
async putAll(entries: Entry<T>[], options?: CacheUpdateOptions) {
|
|
126
|
-
this._putAll(entries);
|
|
139
|
+
await this._putAll(entries);
|
|
127
140
|
if (options?.cache?.update) {
|
|
128
141
|
await this._headsCache?.queue({ added: entries }, options.cache.reset);
|
|
129
142
|
}
|
|
@@ -145,7 +158,7 @@ export class HeadsIndex<T> {
|
|
|
145
158
|
await this._headsCache?.queue(change, reset);
|
|
146
159
|
}
|
|
147
160
|
|
|
148
|
-
private _putOne(entry: Entry<T>) {
|
|
161
|
+
private async _putOne(entry: Entry<T>) {
|
|
149
162
|
if (!entry.hash) {
|
|
150
163
|
throw new Error("Missing hash");
|
|
151
164
|
}
|
|
@@ -154,44 +167,53 @@ export class HeadsIndex<T> {
|
|
|
154
167
|
}
|
|
155
168
|
|
|
156
169
|
this._index.add(entry.hash);
|
|
157
|
-
|
|
158
|
-
|
|
170
|
+
const map = this._gids.get(entry.meta.gid);
|
|
171
|
+
if (!map) {
|
|
172
|
+
const newMap = new Map();
|
|
173
|
+
this._gids.set(entry.meta.gid, newMap);
|
|
174
|
+
newMap.set(entry.hash, entry);
|
|
159
175
|
} else {
|
|
160
|
-
|
|
176
|
+
map.set(entry.hash, entry);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (const next of entry.next) {
|
|
180
|
+
const indexedEntry = this._config.entryIndex.getShallow(next);
|
|
181
|
+
if (indexedEntry) {
|
|
182
|
+
await this.del(indexedEntry);
|
|
183
|
+
}
|
|
161
184
|
}
|
|
162
185
|
}
|
|
163
186
|
|
|
164
|
-
private _putAll(entries: Entry<T>[]) {
|
|
187
|
+
private async _putAll(entries: Entry<T>[]) {
|
|
165
188
|
for (const entry of entries) {
|
|
166
|
-
this._putOne(entry);
|
|
189
|
+
await this._putOne(entry);
|
|
167
190
|
}
|
|
168
191
|
}
|
|
169
192
|
|
|
170
193
|
async del(
|
|
171
|
-
entry: { hash: string; gid: string },
|
|
194
|
+
entry: { hash: string; meta: { gid: string } },
|
|
172
195
|
options?: CacheUpdateOptions
|
|
173
|
-
): Promise<{
|
|
174
|
-
removed: boolean;
|
|
175
|
-
lastWithGid: boolean;
|
|
176
|
-
}> {
|
|
196
|
+
): Promise<boolean> {
|
|
177
197
|
const wasHead = this._index.delete(entry.hash);
|
|
178
198
|
if (!wasHead) {
|
|
179
|
-
return
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
this._gids.delete(entry.gid);
|
|
188
|
-
} else {
|
|
189
|
-
this._gids.set(entry.gid, newValue);
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
let removedGids: Set<string> | undefined = undefined;
|
|
202
|
+
const map = this._gids.get(entry.meta.gid)!;
|
|
203
|
+
map.delete(entry.hash);
|
|
204
|
+
if (map.size <= 0) {
|
|
205
|
+
this._gids.delete(entry.meta.gid);
|
|
206
|
+
(removedGids || (removedGids = new Set<string>())).add(entry.meta.gid);
|
|
190
207
|
}
|
|
208
|
+
|
|
191
209
|
if (!entry.hash) {
|
|
192
210
|
throw new Error("Missing hash");
|
|
193
211
|
}
|
|
194
212
|
|
|
213
|
+
if (removedGids) {
|
|
214
|
+
await this._onGidRemoved?.([...removedGids]);
|
|
215
|
+
}
|
|
216
|
+
|
|
195
217
|
if (wasHead && options?.cache?.update) {
|
|
196
218
|
await this._headsCache?.queue(
|
|
197
219
|
{ removed: [entry.hash] },
|
|
@@ -199,10 +221,7 @@ export class HeadsIndex<T> {
|
|
|
199
221
|
);
|
|
200
222
|
}
|
|
201
223
|
|
|
202
|
-
return
|
|
203
|
-
removed: wasHead,
|
|
204
|
-
lastWithGid: lastWithGid,
|
|
205
|
-
};
|
|
224
|
+
return wasHead;
|
|
206
225
|
// this._headsCache = undefined; // TODO do smarter things here, only remove the element needed (?)
|
|
207
226
|
}
|
|
208
227
|
}
|
package/src/log-sorting.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { Entry } from "./entry.js";
|
|
1
|
+
import { Entry, ShallowEntry } from "./entry.js";
|
|
2
2
|
import { LamportClock as Clock } from "./clock.js";
|
|
3
3
|
import { compare } from "@peerbit/uint8arrays";
|
|
4
4
|
|
|
5
5
|
const First = (a: any, b: any) => a;
|
|
6
6
|
|
|
7
7
|
export type ISortFunction = <T>(
|
|
8
|
-
a:
|
|
9
|
-
b:
|
|
10
|
-
resolveConflict?: (a:
|
|
8
|
+
a: ShallowEntry,
|
|
9
|
+
b: ShallowEntry,
|
|
10
|
+
resolveConflict?: (a: ShallowEntry, b: ShallowEntry) => number
|
|
11
11
|
) => number;
|
|
12
12
|
/**
|
|
13
13
|
* Sort two entries as Last-Write-Wins (LWW).
|
|
@@ -19,13 +19,17 @@ export type ISortFunction = <T>(
|
|
|
19
19
|
* @param {Entry} b Second entry
|
|
20
20
|
* @returns {number} 1 if a is latest, -1 if b is latest
|
|
21
21
|
*/
|
|
22
|
-
export const LastWriteWins: ISortFunction = <T>(
|
|
22
|
+
export const LastWriteWins: ISortFunction = <T>(
|
|
23
|
+
a: ShallowEntry,
|
|
24
|
+
b: ShallowEntry
|
|
25
|
+
) => {
|
|
23
26
|
// Ultimate conflict resolution (take the first/left arg)
|
|
24
27
|
// Sort two entries by their clock id, if the same always take the first
|
|
25
|
-
const sortById = (a:
|
|
28
|
+
const sortById = (a: ShallowEntry, b: ShallowEntry) =>
|
|
29
|
+
SortByClockId(a, b, First);
|
|
26
30
|
// Sort two entries by their clock time, if concurrent,
|
|
27
31
|
// determine sorting using provided conflict resolution function
|
|
28
|
-
const sortByEntryClocks = (a:
|
|
32
|
+
const sortByEntryClocks = (a: ShallowEntry, b: ShallowEntry) =>
|
|
29
33
|
SortByClocks(a, b, sortById);
|
|
30
34
|
// Sort entries by clock time as the primary sort criteria
|
|
31
35
|
return sortByEntryClocks(a, b);
|
|
@@ -40,14 +44,14 @@ export const LastWriteWins: ISortFunction = <T>(a: Entry<T>, b: Entry<T>) => {
|
|
|
40
44
|
*/
|
|
41
45
|
export const SortByEntryHash: ISortFunction = (a, b) => {
|
|
42
46
|
// Ultimate conflict resolution (compare hashes)
|
|
43
|
-
const compareHash = (a:
|
|
47
|
+
const compareHash = (a: ShallowEntry, b: ShallowEntry) =>
|
|
44
48
|
a.hash < b.hash ? -1 : 1;
|
|
45
49
|
// Sort two entries by their clock id, if the same then compare hashes
|
|
46
|
-
const sortById = (a:
|
|
50
|
+
const sortById = (a: ShallowEntry, b: ShallowEntry) =>
|
|
47
51
|
SortByClockId(a, b, compareHash);
|
|
48
52
|
// Sort two entries by their clock time, if concurrent,
|
|
49
53
|
// determine sorting using provided conflict resolution function
|
|
50
|
-
const sortByEntryClocks = (a:
|
|
54
|
+
const sortByEntryClocks = (a: ShallowEntry, b: ShallowEntry) =>
|
|
51
55
|
SortByClocks(a, b, sortById);
|
|
52
56
|
// Sort entries by clock time as the primary sort criteria
|
|
53
57
|
return sortByEntryClocks(a, b);
|
|
@@ -61,12 +65,12 @@ export const SortByEntryHash: ISortFunction = (a, b) => {
|
|
|
61
65
|
* @returns {number} 1 if a is greater, -1 if b is greater
|
|
62
66
|
*/
|
|
63
67
|
export const SortByClocks: ISortFunction = <T>(
|
|
64
|
-
a:
|
|
65
|
-
b:
|
|
66
|
-
resolveConflict?: (a:
|
|
68
|
+
a: ShallowEntry,
|
|
69
|
+
b: ShallowEntry,
|
|
70
|
+
resolveConflict?: (a: ShallowEntry, b: ShallowEntry) => number
|
|
67
71
|
) => {
|
|
68
72
|
// Compare the clocks
|
|
69
|
-
const diff = Clock.compare(a.
|
|
73
|
+
const diff = Clock.compare(a.meta.clock, b.meta.clock);
|
|
70
74
|
// If the clocks are concurrent, use the provided
|
|
71
75
|
// conflict resolution function to determine which comes first
|
|
72
76
|
return diff === 0 ? (resolveConflict || First)(a, b) : diff;
|
|
@@ -82,7 +86,7 @@ export const SortByClocks: ISortFunction = <T>(
|
|
|
82
86
|
export const SortByClockId: ISortFunction = (a, b, resolveConflict) => {
|
|
83
87
|
// Sort by ID if clocks are concurrent,
|
|
84
88
|
// take the entry with a "greater" clock id
|
|
85
|
-
const clockCompare = compare(a.
|
|
89
|
+
const clockCompare = compare(a.meta.clock.id, b.meta.clock.id);
|
|
86
90
|
return clockCompare === 0 ? (resolveConflict || First)(a, b) : clockCompare;
|
|
87
91
|
};
|
|
88
92
|
|
|
@@ -93,7 +97,7 @@ export const SortByClockId: ISortFunction = (a, b, resolveConflict) => {
|
|
|
93
97
|
* @throws {Error} if func ever returns 0
|
|
94
98
|
*/
|
|
95
99
|
export const NoZeroes = (func: ISortFunction) => {
|
|
96
|
-
const comparator = <T>(a:
|
|
100
|
+
const comparator = <T>(a: ShallowEntry, b: ShallowEntry) => {
|
|
97
101
|
// Validate by calling the function
|
|
98
102
|
const result = func(a, b, (a, b) => -1);
|
|
99
103
|
if (result === 0) {
|
package/src/log.ts
CHANGED
|
@@ -12,7 +12,6 @@ import { SimpleLevel } from "@peerbit/lazy-level";
|
|
|
12
12
|
import { EntryIndex } from "./entry-index.js";
|
|
13
13
|
import * as LogError from "./log-errors.js";
|
|
14
14
|
import * as Sorting from "./log-sorting.js";
|
|
15
|
-
import { isDefined } from "./is-defined.js";
|
|
16
15
|
import { findUniques } from "./find-uniques.js";
|
|
17
16
|
import {
|
|
18
17
|
EncryptionTemplateMaybeEncrypted,
|
|
@@ -42,6 +41,7 @@ const { LastWriteWins, NoZeroes } = Sorting;
|
|
|
42
41
|
|
|
43
42
|
export type LogEvents<T> = {
|
|
44
43
|
onChange?: (change: Change<T>) => void;
|
|
44
|
+
onGidRemoved?: (gids: string[]) => Promise<void> | void;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
export type MemoryProperties = {
|
|
@@ -62,19 +62,23 @@ export type LogOptions<T> = LogProperties<T> & LogEvents<T> & MemoryProperties;
|
|
|
62
62
|
const ENTRY_CACHE_MAX = 1000; // TODO as param
|
|
63
63
|
|
|
64
64
|
export type AppendOptions<T> = {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
meta?: {
|
|
66
|
+
type?: EntryType;
|
|
67
|
+
gidSeed?: Uint8Array;
|
|
68
|
+
data?: Uint8Array;
|
|
69
|
+
timestamp?: Timestamp;
|
|
70
|
+
next?: Entry<any>[];
|
|
71
|
+
};
|
|
72
|
+
|
|
68
73
|
identity?: Identity;
|
|
69
74
|
signers?: ((
|
|
70
75
|
data: Uint8Array
|
|
71
76
|
) => Promise<SignatureWithKey> | SignatureWithKey)[];
|
|
72
|
-
|
|
77
|
+
|
|
73
78
|
trim?: TrimOptions;
|
|
74
|
-
timestamp?: Timestamp;
|
|
75
79
|
encryption?: {
|
|
76
80
|
keypair: X25519Keypair;
|
|
77
|
-
|
|
81
|
+
receiver: EncryptionTemplateMaybeEncrypted;
|
|
78
82
|
};
|
|
79
83
|
};
|
|
80
84
|
|
|
@@ -113,11 +117,11 @@ export class Log<T> {
|
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
async open(store: Blocks, identity: Identity, options: LogOptions<T> = {}) {
|
|
116
|
-
if (
|
|
120
|
+
if (store == null) {
|
|
117
121
|
throw LogError.BlockStoreNotDefinedError();
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
if (
|
|
124
|
+
if (identity == null) {
|
|
121
125
|
throw new Error("Identity is required");
|
|
122
126
|
}
|
|
123
127
|
|
|
@@ -125,10 +129,10 @@ export class Log<T> {
|
|
|
125
129
|
throw new Error("Already open");
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
const { encoding, trim, keychain, cache } = options;
|
|
132
|
+
const { encoding, trim, keychain, cache, onGidRemoved } = options;
|
|
129
133
|
let { sortFn } = options;
|
|
130
134
|
|
|
131
|
-
if (
|
|
135
|
+
if (sortFn == null) {
|
|
132
136
|
sortFn = LastWriteWins;
|
|
133
137
|
}
|
|
134
138
|
sortFn = sortFn as Sorting.ISortFunction;
|
|
@@ -158,7 +162,7 @@ export class Log<T> {
|
|
|
158
162
|
throw new Error("Id not set");
|
|
159
163
|
}
|
|
160
164
|
this._headsIndex = new HeadsIndex(id);
|
|
161
|
-
await this._headsIndex.init(this);
|
|
165
|
+
await this._headsIndex.init(this, { onGidRemoved });
|
|
162
166
|
this._entryCache = new Cache({ max: ENTRY_CACHE_MAX });
|
|
163
167
|
this._entryIndex = new EntryIndex({
|
|
164
168
|
store: this._storage,
|
|
@@ -170,15 +174,15 @@ export class Log<T> {
|
|
|
170
174
|
{
|
|
171
175
|
deleteNode: async (node: EntryNode) => {
|
|
172
176
|
// TODO check if we have before delete?
|
|
173
|
-
const entry = await this.get(node.value
|
|
177
|
+
const entry = await this.get(node.value);
|
|
174
178
|
//f (!!entry)
|
|
175
179
|
const a = this.values.length;
|
|
176
180
|
if (entry) {
|
|
177
181
|
this.values.deleteNode(node);
|
|
178
|
-
await this.entryIndex.
|
|
179
|
-
await this.
|
|
180
|
-
this.nextsIndex.delete(node.value
|
|
181
|
-
await this.storage.rm(node.value
|
|
182
|
+
await this.headsIndex.del(this.entryIndex.getShallow(node.value)!);
|
|
183
|
+
await this.entryIndex.delete(node.value);
|
|
184
|
+
this.nextsIndex.delete(node.value);
|
|
185
|
+
await this.storage.rm(node.value);
|
|
182
186
|
}
|
|
183
187
|
const b = this.values.length;
|
|
184
188
|
if (a === b) {
|
|
@@ -243,6 +247,9 @@ export class Log<T> {
|
|
|
243
247
|
get values(): Values<T> {
|
|
244
248
|
return this._values;
|
|
245
249
|
}
|
|
250
|
+
get canAppend() {
|
|
251
|
+
return this._canAppend;
|
|
252
|
+
}
|
|
246
253
|
|
|
247
254
|
/**
|
|
248
255
|
* Checks if a entry is part of the log
|
|
@@ -506,8 +513,8 @@ export class Log<T> {
|
|
|
506
513
|
options: AppendOptions<T> = {}
|
|
507
514
|
): Promise<{ entry: Entry<T>; removed: Entry<T>[] }> {
|
|
508
515
|
// Update the clock (find the latest clock)
|
|
509
|
-
if (options.
|
|
510
|
-
for (const n of options.
|
|
516
|
+
if (options.meta?.next) {
|
|
517
|
+
for (const n of options.meta.next) {
|
|
511
518
|
if (!n.hash)
|
|
512
519
|
throw new Error(
|
|
513
520
|
"Expecting nexts to already be saved. missing hash for one or more entries"
|
|
@@ -517,13 +524,12 @@ export class Log<T> {
|
|
|
517
524
|
|
|
518
525
|
await this.load({ reload: false });
|
|
519
526
|
|
|
520
|
-
const
|
|
521
|
-
const nexts: Entry<any>[] = options.nexts || (await this.getHeads());
|
|
527
|
+
const nexts: Entry<any>[] = options.meta?.next || (await this.getHeads());
|
|
522
528
|
|
|
523
529
|
// Calculate max time for log/graph
|
|
524
530
|
const clock = new Clock({
|
|
525
531
|
id: this._identity.publicKey.bytes,
|
|
526
|
-
timestamp: options
|
|
532
|
+
timestamp: options?.meta?.timestamp || this._hlc.now(),
|
|
527
533
|
});
|
|
528
534
|
|
|
529
535
|
const entry = await Entry.create<T>({
|
|
@@ -531,23 +537,28 @@ export class Log<T> {
|
|
|
531
537
|
identity: options.identity || this._identity,
|
|
532
538
|
signers: options.signers,
|
|
533
539
|
data,
|
|
534
|
-
|
|
535
|
-
|
|
540
|
+
meta: {
|
|
541
|
+
clock,
|
|
542
|
+
type: options.meta?.type,
|
|
543
|
+
gidSeed: options.meta?.gidSeed,
|
|
544
|
+
data: options.meta?.data,
|
|
545
|
+
next: nexts,
|
|
546
|
+
},
|
|
547
|
+
|
|
536
548
|
encoding: this._encoding,
|
|
537
|
-
|
|
538
|
-
gidSeed: options.gidSeed,
|
|
549
|
+
|
|
539
550
|
encryption: options.encryption
|
|
540
551
|
? {
|
|
541
552
|
keypair: options.encryption.keypair,
|
|
542
|
-
|
|
543
|
-
...options.encryption.
|
|
553
|
+
receiver: {
|
|
554
|
+
...options.encryption.receiver,
|
|
544
555
|
},
|
|
545
556
|
}
|
|
546
557
|
: undefined,
|
|
547
558
|
canAppend: this._canAppend,
|
|
548
559
|
});
|
|
549
560
|
|
|
550
|
-
if (!
|
|
561
|
+
if (!entry.hash) {
|
|
551
562
|
throw new Error("Unexpected");
|
|
552
563
|
}
|
|
553
564
|
|
|
@@ -562,35 +573,12 @@ export class Log<T> {
|
|
|
562
573
|
}
|
|
563
574
|
}
|
|
564
575
|
|
|
565
|
-
const removedGids: Set<string> = new Set();
|
|
566
|
-
if (hasNext) {
|
|
567
|
-
for (const next of nexts) {
|
|
568
|
-
const deletion = await this._headsIndex.del(next);
|
|
569
|
-
if (deletion.lastWithGid && next.gid !== entry.gid) {
|
|
570
|
-
removedGids.add(next.gid);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
} else {
|
|
574
|
-
// next is all heads, which means we should just overwrite
|
|
575
|
-
for (const key of this.headsIndex.gids.keys()) {
|
|
576
|
-
if (key !== entry.gid) {
|
|
577
|
-
removedGids.add(key);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
await this.headsIndex.reset([entry], { cache: { update: false } });
|
|
581
|
-
}
|
|
582
|
-
|
|
583
576
|
await this._entryIndex.set(entry, false); // save === false, because its already saved when Entry.create
|
|
584
577
|
await this._headsIndex.put(entry, { cache: { update: false } }); // we will update the cache a few lines later *
|
|
585
578
|
await this._values.put(entry);
|
|
586
579
|
|
|
587
580
|
const removed = await this.processEntry(entry);
|
|
588
581
|
|
|
589
|
-
// if next contails all gids
|
|
590
|
-
if (options.onGidsShadowed && removedGids.size > 0) {
|
|
591
|
-
options.onGidsShadowed([...removedGids]);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
582
|
entry.init({ encoding: this._encoding, keychain: this._keychain });
|
|
595
583
|
// console.log('put entry', entry.hash, (await this._entryIndex._index.size));
|
|
596
584
|
|
|
@@ -718,7 +706,7 @@ export class Log<T> {
|
|
|
718
706
|
return;
|
|
719
707
|
}
|
|
720
708
|
|
|
721
|
-
yield next.value
|
|
709
|
+
yield next.value;
|
|
722
710
|
counter++;
|
|
723
711
|
|
|
724
712
|
next = nextFn(next);
|
|
@@ -782,6 +770,13 @@ export class Log<T> {
|
|
|
782
770
|
timeout?: number;
|
|
783
771
|
} & CacheUpdateOptions
|
|
784
772
|
): Promise<void> {
|
|
773
|
+
const definedOptions = {
|
|
774
|
+
...options,
|
|
775
|
+
cache: options?.cache ?? {
|
|
776
|
+
update: true,
|
|
777
|
+
},
|
|
778
|
+
};
|
|
779
|
+
|
|
785
780
|
await this.load({ reload: false });
|
|
786
781
|
if (entriesOrLog.length === 0) {
|
|
787
782
|
return;
|
|
@@ -832,7 +827,6 @@ export class Log<T> {
|
|
|
832
827
|
}
|
|
833
828
|
}
|
|
834
829
|
|
|
835
|
-
// Resolve missing entries
|
|
836
830
|
const removedHeads: Entry<T>[] = [];
|
|
837
831
|
for (const hash of stack) {
|
|
838
832
|
if (visited.has(hash) || this.has(hash)) {
|
|
@@ -851,7 +845,7 @@ export class Log<T> {
|
|
|
851
845
|
|
|
852
846
|
let nexts: string[];
|
|
853
847
|
if (
|
|
854
|
-
entry.
|
|
848
|
+
entry.meta.type !== EntryType.CUT &&
|
|
855
849
|
(nexts = await entry.getNext())
|
|
856
850
|
) {
|
|
857
851
|
let isRoot = true;
|
|
@@ -888,7 +882,12 @@ export class Log<T> {
|
|
|
888
882
|
while (entriesBottomUp.length > 0) {
|
|
889
883
|
const e = entriesBottomUp.shift()!;
|
|
890
884
|
await this._joining.get(e.hash);
|
|
891
|
-
const p = this.joinEntry(
|
|
885
|
+
const p = this.joinEntry(
|
|
886
|
+
e,
|
|
887
|
+
nextRefs,
|
|
888
|
+
entriesBottomUp,
|
|
889
|
+
definedOptions
|
|
890
|
+
).then(
|
|
892
891
|
() => this._joining.delete(e.hash) // TODO, if head we run into problems with concurrency here!, we add heads at line 929 but resolve here
|
|
893
892
|
);
|
|
894
893
|
this._joining.set(e.hash, p);
|
|
@@ -910,7 +909,7 @@ export class Log<T> {
|
|
|
910
909
|
return;
|
|
911
910
|
}
|
|
912
911
|
|
|
913
|
-
if (!
|
|
912
|
+
if (!e.hash) {
|
|
914
913
|
throw new Error("Unexpected");
|
|
915
914
|
}
|
|
916
915
|
|
|
@@ -929,7 +928,7 @@ export class Log<T> {
|
|
|
929
928
|
await this._entryIndex.set(e);
|
|
930
929
|
await this._values.put(e);
|
|
931
930
|
|
|
932
|
-
if (e.
|
|
931
|
+
if (e.meta.type !== EntryType.CUT) {
|
|
933
932
|
for (const a of e.next) {
|
|
934
933
|
if (!this.has(a)) {
|
|
935
934
|
await this.join([a]);
|
|
@@ -973,7 +972,7 @@ export class Log<T> {
|
|
|
973
972
|
}
|
|
974
973
|
|
|
975
974
|
private async processEntry(entry: Entry<T>) {
|
|
976
|
-
if (entry.
|
|
975
|
+
if (entry.meta.type === EntryType.CUT) {
|
|
977
976
|
return this.deleteRecursively(entry, true);
|
|
978
977
|
}
|
|
979
978
|
return [];
|
|
@@ -985,6 +984,8 @@ export class Log<T> {
|
|
|
985
984
|
const promises: Promise<void>[] = [];
|
|
986
985
|
let counter = 0;
|
|
987
986
|
const deleted: Entry<T>[] = [];
|
|
987
|
+
const deletedGids = new Set();
|
|
988
|
+
|
|
988
989
|
while (stack.length > 0) {
|
|
989
990
|
const entry = stack.pop()!;
|
|
990
991
|
if ((counter > 0 || !skipFirst) && this.has(entry.hash)) {
|
|
@@ -993,6 +994,7 @@ export class Log<T> {
|
|
|
993
994
|
await this._values.delete(entry);
|
|
994
995
|
await this._entryIndex.delete(entry.hash);
|
|
995
996
|
await this._headsIndex.del(entry);
|
|
997
|
+
|
|
996
998
|
this._nextsIndex.delete(entry.hash);
|
|
997
999
|
deleted.push(entry);
|
|
998
1000
|
promises.push(entry.delete(this._storage));
|