autobee 2.0.0-rc.2 → 2.0.0-rc.21
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/README.md +48 -1
- package/encoding/compat.js +1 -1
- package/encoding/legacy.js +3 -3
- package/encoding/spec/autobee/index.js +104 -22
- package/encoding/spec/autobee/schema.json +63 -1
- package/index.js +392 -197
- package/lib/apply-calls.js +5 -0
- package/lib/apply-view.js +25 -0
- package/lib/boot.js +6 -1
- package/lib/encoding.js +12 -0
- package/lib/{reboot.js → fast-forward.js} +213 -19
- package/lib/system.js +38 -62
- package/lib/topo.js +121 -18
- package/lib/trusted.js +44 -0
- package/lib/updates.js +1 -38
- package/lib/writers.js +90 -15
- package/package.json +2 -2
package/lib/apply-calls.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const ID = require('hypercore-id-encoding')
|
|
2
|
+
const b4a = require('b4a')
|
|
2
3
|
|
|
3
4
|
class ApplyCalls {
|
|
4
5
|
constructor(auto) {
|
|
@@ -39,6 +40,10 @@ class ApplyCalls {
|
|
|
39
40
|
|
|
40
41
|
ackWriter(key) {
|
|
41
42
|
if (typeof key === 'string') key = ID.decode(key)
|
|
43
|
+
// acking an optimistic node means we owe it a linking node
|
|
44
|
+
if (this.applying && this.applying[0].optimistic && !b4a.equals(key, this.auto.local.key)) {
|
|
45
|
+
this.auto._ackRequired = true
|
|
46
|
+
}
|
|
42
47
|
return this.auto.system.ackWriter(key)
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module.exports = class ApplyView {
|
|
2
|
+
constructor(bee, auto) {
|
|
3
|
+
this.bee = bee
|
|
4
|
+
this.auto = auto
|
|
5
|
+
this.view = ApplyView.open(bee, auto)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static open(bee, auto) {
|
|
9
|
+
return auto._handlers.open ? auto._handlers.open(bee, auto) : bee
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static close(view, auto) {
|
|
13
|
+
if (!auto._handlers.close) return Promise.resolve()
|
|
14
|
+
return auto._handlers.close(view)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
apply(userBatch) {
|
|
18
|
+
return this.auto._handlers.apply(userBatch, this.view, this.auto._host)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async close() {
|
|
22
|
+
await ApplyView.close(this.view, this.auto)
|
|
23
|
+
await this.bee.close()
|
|
24
|
+
}
|
|
25
|
+
}
|
package/lib/boot.js
CHANGED
|
@@ -201,6 +201,8 @@ async function checkAutobaseMigration(store, local, bootstrap, legacyViews) {
|
|
|
201
201
|
mappedViews.set(name, v)
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
await core.close()
|
|
205
|
+
|
|
204
206
|
return {
|
|
205
207
|
encryptionKey,
|
|
206
208
|
system,
|
|
@@ -242,6 +244,7 @@ async function getCatchupHeads(core) {
|
|
|
242
244
|
if (entry.key[0] !== 0x01) continue
|
|
243
245
|
|
|
244
246
|
const { key, length } = encoding.decodeSystemWriter(entry.key, entry.value)
|
|
247
|
+
if (!length) continue
|
|
245
248
|
nodes.push({ key, length })
|
|
246
249
|
}
|
|
247
250
|
|
|
@@ -254,7 +257,9 @@ async function getWriterBatch(store, head, key, encryptionKey, nodes = []) {
|
|
|
254
257
|
const core = store.get(head.key)
|
|
255
258
|
await core.ready()
|
|
256
259
|
|
|
257
|
-
|
|
260
|
+
if (encryptionKey) {
|
|
261
|
+
await core.setEncryption(AutobeeEncryption.getWriterEncryption(key, encryptionKey))
|
|
262
|
+
}
|
|
258
263
|
|
|
259
264
|
let seq = head.length - 1
|
|
260
265
|
const block = await core.get(seq--, { wait: false })
|
package/lib/encoding.js
CHANGED
|
@@ -12,6 +12,7 @@ const SystemInfo = getEncoding('@autobee/system-info')
|
|
|
12
12
|
const SystemWriter = getEncoding('@autobee/system-writer')
|
|
13
13
|
const ManifestData = getEncoding('@autobee/manifest-data')
|
|
14
14
|
const AutobaseBootRecord = getEncoding('@autobase-compat/boot-record')
|
|
15
|
+
const MigratedHead = getEncoding('@autobee/migrated-head')
|
|
15
16
|
|
|
16
17
|
exports.OPLOG_VERSION = OPLOG_VERSION
|
|
17
18
|
|
|
@@ -21,6 +22,9 @@ exports.ManifestData = ManifestData
|
|
|
21
22
|
exports.encodeBootRecord = encodeBootRecord
|
|
22
23
|
exports.decodeBootRecord = decodeBootRecord
|
|
23
24
|
|
|
25
|
+
exports.encodeMigratedHead = encodeMigratedHead
|
|
26
|
+
exports.decodeMigratedHead = decodeMigratedHead
|
|
27
|
+
|
|
24
28
|
exports.encodePreviousDrain = encodePreviousDrain
|
|
25
29
|
exports.decodePreviousDrain = decodePreviousDrain
|
|
26
30
|
|
|
@@ -39,6 +43,14 @@ function decodeBootRecord(value) {
|
|
|
39
43
|
return c.decode(Link, value)
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
function encodeMigratedHead(m) {
|
|
47
|
+
return c.encode(MigratedHead, m)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function decodeMigratedHead(value) {
|
|
51
|
+
return c.decode(MigratedHead, value)
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
function encodePreviousDrain(d) {
|
|
43
55
|
return c.encode(c.uint, d)
|
|
44
56
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const safetyCatch = require('safety-catch')
|
|
2
2
|
const b4a = require('b4a')
|
|
3
|
+
const Hypercore = require('hypercore')
|
|
3
4
|
const c = require('compact-encoding')
|
|
4
5
|
const crypto = require('hypercore-crypto')
|
|
5
6
|
|
|
@@ -18,13 +19,24 @@ const [NS_SIGNER_NAMESPACE] = crypto.namespace('autobase', 1)
|
|
|
18
19
|
|
|
19
20
|
const EMPTY = b4a.alloc(0)
|
|
20
21
|
const DEFAULT_OP_TIMEOUT = 5_000
|
|
22
|
+
const MIN_FF_GAP = 32
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
// views are only stamped on the last node of a flush, so a head may sit above
|
|
25
|
+
// the most recent one - how far back we look for it
|
|
26
|
+
const MAX_FLUSH_SEARCH = 64
|
|
27
|
+
|
|
28
|
+
module.exports = class FastForward {
|
|
23
29
|
constructor(auto, head, tip, { timeout = DEFAULT_OP_TIMEOUT } = {}) {
|
|
24
30
|
this.auto = auto
|
|
31
|
+
|
|
32
|
+
this.activeRequests = []
|
|
33
|
+
|
|
34
|
+
// 'reboot' is a storage namespace: renaming it would derive a different
|
|
35
|
+
// scratch core on disk, so it stays as is
|
|
25
36
|
this.system = new System(auto.store.namespace('reboot'), null, {
|
|
26
37
|
getEncryptionProvider: this.auto.getSystemEncryption,
|
|
27
|
-
encrypted: this.encrypted
|
|
38
|
+
encrypted: this.encrypted,
|
|
39
|
+
activeRequests: this.activeRequests
|
|
28
40
|
})
|
|
29
41
|
|
|
30
42
|
// length -1 marks an uninitialised head: we know the key but not the length
|
|
@@ -37,6 +49,164 @@ module.exports = class Reboot {
|
|
|
37
49
|
this.cores = []
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
static DEFAULT_TIMEOUT = DEFAULT_OP_TIMEOUT
|
|
53
|
+
|
|
54
|
+
static async fromHead(
|
|
55
|
+
auto,
|
|
56
|
+
head,
|
|
57
|
+
trusted,
|
|
58
|
+
{ force = false, timeout = 0, condition = null, reference = null } = {}
|
|
59
|
+
) {
|
|
60
|
+
const conservative = !force && auto._conservativeFF
|
|
61
|
+
const timeoutOpts = timeout ? { timeout } : null
|
|
62
|
+
|
|
63
|
+
const oplog = await FastForward.flushHead(auto, head, {
|
|
64
|
+
conservative,
|
|
65
|
+
...timeoutOpts
|
|
66
|
+
})
|
|
67
|
+
if (!oplog) return null
|
|
68
|
+
|
|
69
|
+
const verified = trusted ? await FastForward.flushHead(auto, trusted, timeoutOpts) : oplog
|
|
70
|
+
if (!verified) return null
|
|
71
|
+
|
|
72
|
+
// legacy nodes from non-indexers have no system info to fast-forward from
|
|
73
|
+
if (!oplog.op.views || !verified.op.views) return null
|
|
74
|
+
|
|
75
|
+
if (!force && verified.op.views.flushes - auto.system.flushes < MIN_FF_GAP) {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (condition !== null && !(await FastForward.accepts(auto, verified, condition, reference))) {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const tip = {
|
|
84
|
+
system: batchToHead(oplog.op.views.system),
|
|
85
|
+
verified: {
|
|
86
|
+
op: { key: verified.key, length: verified.length },
|
|
87
|
+
flushes: verified.op.views.flushes
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return new FastForward(auto, batchToHead(verified.op.views.system), tip)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static async fromHeads(
|
|
95
|
+
auto,
|
|
96
|
+
heads,
|
|
97
|
+
{ force = false, timeout = 0, condition = null, reference = null } = {}
|
|
98
|
+
) {
|
|
99
|
+
if (reference === null) reference = auto._workingView.view
|
|
100
|
+
|
|
101
|
+
const promises = []
|
|
102
|
+
|
|
103
|
+
for (const head of heads) {
|
|
104
|
+
if (head.length === 0) continue
|
|
105
|
+
promises.push(FastForward.flushHead(auto, head, timeout ? { timeout } : null))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const ops = await Promise.all(promises)
|
|
109
|
+
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
110
|
+
|
|
111
|
+
const trust = await Promise.all(
|
|
112
|
+
ops.map((res) => {
|
|
113
|
+
return res === null ? false : auto.trusted.isTrusted(res.key, reference)
|
|
114
|
+
})
|
|
115
|
+
)
|
|
116
|
+
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
117
|
+
|
|
118
|
+
const candidates = []
|
|
119
|
+
|
|
120
|
+
let bestTrusted = null
|
|
121
|
+
let bestTrustedFlushes = -1
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < ops.length; i++) {
|
|
124
|
+
const res = ops[i]
|
|
125
|
+
if (res === null || !res.op.views) continue
|
|
126
|
+
if (!force && res.op.views.flushes - auto.system.flushes < MIN_FF_GAP) continue
|
|
127
|
+
|
|
128
|
+
if (!trust[i]) {
|
|
129
|
+
candidates.push(res)
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (res.op.views.flushes > bestTrustedFlushes) {
|
|
134
|
+
bestTrustedFlushes = res.op.views.flushes
|
|
135
|
+
bestTrusted = res
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (bestTrusted !== null) {
|
|
140
|
+
return FastForward.fromHead(auto, bestTrusted, null, {
|
|
141
|
+
force,
|
|
142
|
+
timeout,
|
|
143
|
+
condition,
|
|
144
|
+
reference
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
candidates.sort((a, b) => b.op.views.flushes - a.op.views.flushes)
|
|
149
|
+
|
|
150
|
+
for (const res of candidates) {
|
|
151
|
+
const trusted = await FastForward.mostRecentTrusted(auto, res, reference)
|
|
152
|
+
if (trusted === null) continue
|
|
153
|
+
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
154
|
+
|
|
155
|
+
const ff = await FastForward.fromHead(auto, res, trusted, {
|
|
156
|
+
force,
|
|
157
|
+
timeout,
|
|
158
|
+
condition,
|
|
159
|
+
reference
|
|
160
|
+
})
|
|
161
|
+
if (ff !== null) return ff
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static async flushHead(auto, head, opts) {
|
|
168
|
+
const core = auto.openCore(head.key)
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
let length = head.length
|
|
172
|
+
|
|
173
|
+
for (let i = 0; i < MAX_FLUSH_SEARCH; i++) {
|
|
174
|
+
const oplog = await auto.readOplog(core, length, opts)
|
|
175
|
+
if (oplog === null) return null
|
|
176
|
+
if (oplog.op.views) return oplog
|
|
177
|
+
|
|
178
|
+
length = oplog.length - 1
|
|
179
|
+
if (length <= 0) return null
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return null
|
|
183
|
+
} finally {
|
|
184
|
+
await core.close()
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static async accepts(auto, oplog, condition, reference) {
|
|
189
|
+
const opened = auto.openViewAt(oplog)
|
|
190
|
+
if (opened === null) return false
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
return !!(await condition(opened.view, reference))
|
|
194
|
+
} finally {
|
|
195
|
+
await opened.close()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static async mostRecentTrusted(auto, head, reference) {
|
|
200
|
+
const opened = auto.openViewAt(head)
|
|
201
|
+
if (opened === null) return null
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
return (await auto.trusted.mostRecentTrusted(opened.view, reference)) || null
|
|
205
|
+
} finally {
|
|
206
|
+
await opened.close()
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
40
210
|
async run() {
|
|
41
211
|
try {
|
|
42
212
|
if (!this.running) this.running = this._run()
|
|
@@ -64,8 +234,15 @@ module.exports = class Reboot {
|
|
|
64
234
|
|
|
65
235
|
// a legacy system needs migrating rather than a plain fast-forward
|
|
66
236
|
if (this.system.version <= LEGACY_AUTOBASE_VERSION) {
|
|
67
|
-
|
|
68
|
-
if (
|
|
237
|
+
const migrated = this.auto._migratedHead && this.auto._migratedHead.system
|
|
238
|
+
if (
|
|
239
|
+
migrated &&
|
|
240
|
+
b4a.equals(migrated.key, this.head.key) &&
|
|
241
|
+
migrated.length >= this.head.length
|
|
242
|
+
) {
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
|
|
69
246
|
return this._migrate()
|
|
70
247
|
}
|
|
71
248
|
|
|
@@ -77,7 +254,12 @@ module.exports = class Reboot {
|
|
|
77
254
|
const view = this.auto.store.get({ key: this.system.view.key, active: true })
|
|
78
255
|
this.cores.push(view)
|
|
79
256
|
|
|
80
|
-
promises.push(
|
|
257
|
+
promises.push(
|
|
258
|
+
view.get(this.system.view.length - 1, {
|
|
259
|
+
timeout: this.timeout,
|
|
260
|
+
activeRequests: this.activeRequests
|
|
261
|
+
})
|
|
262
|
+
)
|
|
81
263
|
|
|
82
264
|
for (const head of this.system.heads) {
|
|
83
265
|
promises.push(this.system.get(head.key, { timeout: this.timeout }))
|
|
@@ -86,23 +268,12 @@ module.exports = class Reboot {
|
|
|
86
268
|
await Promise.all(promises)
|
|
87
269
|
if (this.destroyed) return null
|
|
88
270
|
|
|
89
|
-
const shared = await this._commonAncestor()
|
|
90
271
|
if (this.destroyed) return null
|
|
91
272
|
|
|
92
273
|
return {
|
|
93
274
|
head: this.head,
|
|
94
275
|
tip: this.tip,
|
|
95
|
-
migrate: null
|
|
96
|
-
shared
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async _commonAncestor() {
|
|
101
|
-
try {
|
|
102
|
-
return await this.auto.system.commonAncestor(this.head)
|
|
103
|
-
} catch (err) {
|
|
104
|
-
safetyCatch(err)
|
|
105
|
-
return null
|
|
276
|
+
migrate: null
|
|
106
277
|
}
|
|
107
278
|
}
|
|
108
279
|
|
|
@@ -118,7 +289,12 @@ module.exports = class Reboot {
|
|
|
118
289
|
await core.setEncryption(this.auto.getSystemEncryption())
|
|
119
290
|
|
|
120
291
|
// Decode hyperbee block using hyperbee2 compat
|
|
121
|
-
const node = decodeBlock(
|
|
292
|
+
const node = decodeBlock(
|
|
293
|
+
await core.get(this.head.length - 1, {
|
|
294
|
+
timeout: this.timeout,
|
|
295
|
+
activeRequests: this.activeRequests
|
|
296
|
+
})
|
|
297
|
+
)
|
|
122
298
|
assert(node.keys.length > 0, 'bad system block')
|
|
123
299
|
|
|
124
300
|
// Decode system info from hyperbee block using autobee compat
|
|
@@ -163,7 +339,12 @@ module.exports = class Reboot {
|
|
|
163
339
|
await core.ready()
|
|
164
340
|
|
|
165
341
|
try {
|
|
166
|
-
if (!core.manifest)
|
|
342
|
+
if (!core.manifest) {
|
|
343
|
+
await core.get(length - 1, {
|
|
344
|
+
timeout: this.timeout,
|
|
345
|
+
activeRequests: this.activeRequests
|
|
346
|
+
})
|
|
347
|
+
}
|
|
167
348
|
} catch {
|
|
168
349
|
return null
|
|
169
350
|
}
|
|
@@ -191,13 +372,26 @@ module.exports = class Reboot {
|
|
|
191
372
|
}
|
|
192
373
|
}
|
|
193
374
|
|
|
375
|
+
// cancels every read in flight and fails any that come after
|
|
376
|
+
clearRequests(err = null) {
|
|
377
|
+
Hypercore.destroyRequests(this.activeRequests, err)
|
|
378
|
+
}
|
|
379
|
+
|
|
194
380
|
async close() {
|
|
195
381
|
this.destroyed = true
|
|
382
|
+
this.clearRequests()
|
|
196
383
|
if (this.system) await this.system.close()
|
|
197
384
|
for (const core of this.cores) await core.close()
|
|
198
385
|
}
|
|
199
386
|
}
|
|
200
387
|
|
|
388
|
+
function batchToHead(b) {
|
|
389
|
+
return {
|
|
390
|
+
key: b.key,
|
|
391
|
+
length: b.start + b.length
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
201
395
|
function decodeLegacySystemInfo(buffer) {
|
|
202
396
|
const state = { start: 0, end: buffer.length, buffer }
|
|
203
397
|
const version = c.uint.decode(state)
|
package/lib/system.js
CHANGED
|
@@ -2,7 +2,6 @@ const Hyperbee = require('hyperbee2')
|
|
|
2
2
|
const b4a = require('b4a')
|
|
3
3
|
|
|
4
4
|
const { AUTOBEE_VERSION } = require('./constants')
|
|
5
|
-
const asserts = require('./asserts.js')
|
|
6
5
|
const encoding = require('./encoding.js')
|
|
7
6
|
const topo = require('./topo.js')
|
|
8
7
|
|
|
@@ -13,6 +12,27 @@ const INDEXER_LT = b4a.from([3])
|
|
|
13
12
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
14
13
|
const EMPTY = b4a.from([])
|
|
15
14
|
|
|
15
|
+
class SystemSnapshot {
|
|
16
|
+
constructor(bee) {
|
|
17
|
+
this.bee = bee
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async get(key, { timeout } = {}) {
|
|
21
|
+
const node = await this.bee.get(encoding.encodeSystemWriterKey(key), { timeout })
|
|
22
|
+
return node !== null ? encoding.decodeSystemWriter(node.key, node.value) : null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async has(link) {
|
|
26
|
+
const node = await this.get(link.key)
|
|
27
|
+
if (!node || node.length < link.length) return false
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
close() {
|
|
32
|
+
return this.bee.close()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
module.exports = class Systembee {
|
|
17
37
|
constructor(store, name, opts = {}) {
|
|
18
38
|
this.name = name // for debuggin
|
|
@@ -26,7 +46,6 @@ module.exports = class Systembee {
|
|
|
26
46
|
this.indexers = null // legacy
|
|
27
47
|
this.updates = new Map()
|
|
28
48
|
this.writers = new Map()
|
|
29
|
-
this.shared = null
|
|
30
49
|
this.encrypted = opts.encrypted === true
|
|
31
50
|
}
|
|
32
51
|
|
|
@@ -95,49 +114,6 @@ module.exports = class Systembee {
|
|
|
95
114
|
}
|
|
96
115
|
}
|
|
97
116
|
|
|
98
|
-
async commonAncestor(head) {
|
|
99
|
-
const info = await this.getInfo()
|
|
100
|
-
|
|
101
|
-
const snap = this.bee.checkout(head)
|
|
102
|
-
|
|
103
|
-
const hist1 = this.bee.createChangesStream()[Symbol.asyncIterator]()
|
|
104
|
-
const hist2 = snap.createChangesStream()[Symbol.asyncIterator]()
|
|
105
|
-
|
|
106
|
-
let a = await hist1.next()
|
|
107
|
-
let b = await hist2.next()
|
|
108
|
-
|
|
109
|
-
if (!a.value || !b.value) {
|
|
110
|
-
return { flushes: 0, system: EMPTY_HEAD, view: EMPTY_HEAD }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
let l = getInfo(a.value)
|
|
114
|
-
let r = getInfo(b.value)
|
|
115
|
-
|
|
116
|
-
while (!a.done || !b.done) {
|
|
117
|
-
if (l.flushes === r.flushes && sameHead(l.view, r.view)) {
|
|
118
|
-
return {
|
|
119
|
-
flushes: l.flushes,
|
|
120
|
-
system: a.value.head,
|
|
121
|
-
view: l.view || EMPTY_HEAD
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (l.flushes >= r.flushes) {
|
|
126
|
-
if (a.done) break
|
|
127
|
-
a = await hist1.next()
|
|
128
|
-
if (a.value) l = getInfo(a.value)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (r.flushes >= l.flushes) {
|
|
132
|
-
if (b.done) break
|
|
133
|
-
b = await hist2.next()
|
|
134
|
-
if (b.value) r = getInfo(b.value)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return { flushes: 0, system: EMPTY_HEAD, view: EMPTY_HEAD }
|
|
139
|
-
}
|
|
140
|
-
|
|
141
117
|
async reset({ timeout } = {}) {
|
|
142
118
|
const info = await this.getInfo({ timeout })
|
|
143
119
|
|
|
@@ -147,7 +123,6 @@ module.exports = class Systembee {
|
|
|
147
123
|
this.timestamp = info ? info.timestamp : 0
|
|
148
124
|
this.flushes = info ? info.flushes : 0
|
|
149
125
|
this.indexers = info ? info.indexers : null
|
|
150
|
-
this.shared = null
|
|
151
126
|
this.updates.clear()
|
|
152
127
|
this.writers.clear()
|
|
153
128
|
|
|
@@ -169,16 +144,8 @@ module.exports = class Systembee {
|
|
|
169
144
|
async undo(head) {
|
|
170
145
|
this.bee.move(head)
|
|
171
146
|
|
|
172
|
-
const shared = this.shared
|
|
173
147
|
await this.reset()
|
|
174
148
|
|
|
175
|
-
// deepest rewind since last mark - everything at or below it is untouched
|
|
176
|
-
if (shared === null || this.flushes < shared.flushes) {
|
|
177
|
-
this.shared = { flushes: this.flushes, view: this.view || EMPTY_HEAD, system: head }
|
|
178
|
-
} else if (shared) {
|
|
179
|
-
this.shared = shared
|
|
180
|
-
}
|
|
181
|
-
|
|
182
149
|
return this.view || EMPTY_HEAD
|
|
183
150
|
}
|
|
184
151
|
|
|
@@ -230,6 +197,14 @@ module.exports = class Systembee {
|
|
|
230
197
|
return info ? !info.isRemoved : !!optimistic
|
|
231
198
|
}
|
|
232
199
|
|
|
200
|
+
snapshot(head = null) {
|
|
201
|
+
const bee =
|
|
202
|
+
head === null
|
|
203
|
+
? this.bee.snapshot()
|
|
204
|
+
: this.bee.checkout({ length: head.length, key: head.key })
|
|
205
|
+
return new SystemSnapshot(bee)
|
|
206
|
+
}
|
|
207
|
+
|
|
233
208
|
async get(key, { unflushed = false, timeout } = {}) {
|
|
234
209
|
const node = await this.bee.get(encoding.encodeSystemWriterKey(key), { timeout })
|
|
235
210
|
const info = node !== null ? encoding.decodeSystemWriter(node.key, node.value) : null
|
|
@@ -436,6 +411,9 @@ module.exports = class Systembee {
|
|
|
436
411
|
|
|
437
412
|
w.tryPut(INFO_KEY, encoding.encodeSystemInfo(info))
|
|
438
413
|
|
|
414
|
+
this.version = info.version
|
|
415
|
+
this.view = info.view
|
|
416
|
+
|
|
439
417
|
await w.flush()
|
|
440
418
|
|
|
441
419
|
// Can enable if needed during debuggin
|
|
@@ -462,16 +440,14 @@ function getInfo(data) {
|
|
|
462
440
|
const prefix = k.key[0]
|
|
463
441
|
|
|
464
442
|
if (prefix === 0) {
|
|
465
|
-
|
|
443
|
+
try {
|
|
444
|
+
return encoding.decodeSystemInfo(k.value)
|
|
445
|
+
} catch {
|
|
446
|
+
return null
|
|
447
|
+
}
|
|
466
448
|
}
|
|
467
449
|
}
|
|
468
450
|
}
|
|
469
451
|
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function sameHead(a, b) {
|
|
474
|
-
if (!a || !b || a.length !== b.length) return false
|
|
475
|
-
if (!a.key || !b.key) return a.key === b.key
|
|
476
|
-
return b4a.equals(a.key, b.key)
|
|
452
|
+
return null
|
|
477
453
|
}
|