autobee 1.0.0 → 1.0.2
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 +242 -2
- package/index.js +73 -15
- package/lib/apply-calls.js +7 -4
- package/lib/encoding.js +1 -1
- package/lib/system.js +23 -10
- package/lib/topo.js +114 -39
- package/lib/writers.js +56 -10
- package/package.json +6 -2
- package/spec/hyperschema/index.js +0 -644
- package/spec/hyperschema/schema.json +0 -456
package/lib/topo.js
CHANGED
|
@@ -5,11 +5,43 @@ const asserts = require('./asserts.js')
|
|
|
5
5
|
|
|
6
6
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
7
7
|
|
|
8
|
+
exports.replay = replay
|
|
8
9
|
exports.sort = sort
|
|
9
10
|
|
|
10
11
|
exports.isLinking = isLinking
|
|
11
12
|
exports.isLinkingAll = isLinkingAll
|
|
12
13
|
|
|
14
|
+
class Clock {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.map = new Map()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
add(link) {
|
|
20
|
+
const hex = b4a.toString(link.key, 'hex')
|
|
21
|
+
const p = this.map.get(hex)
|
|
22
|
+
if (p < link.length) this.map.set(hex, link.length)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
has(link) {
|
|
26
|
+
const hex = b4a.toString(link.key, 'hex')
|
|
27
|
+
const p = this.map.get(hex)
|
|
28
|
+
return p >= link.length
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function replay(auto) {
|
|
33
|
+
const ch = auto.system.bee.createChangesStream()
|
|
34
|
+
const replay = []
|
|
35
|
+
|
|
36
|
+
for await (const data of ch) {
|
|
37
|
+
const { ref, info } = getOplog(data)
|
|
38
|
+
replay.push(ref)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const inflated = await inflate(auto, replay.reverse())
|
|
42
|
+
return inflated.flatMap((b) => b.batch)
|
|
43
|
+
}
|
|
44
|
+
|
|
13
45
|
async function sort(auto, batch) {
|
|
14
46
|
const node = batch[0]
|
|
15
47
|
const ch = auto.system.bee.createChangesStream()
|
|
@@ -17,52 +49,98 @@ async function sort(auto, batch) {
|
|
|
17
49
|
|
|
18
50
|
for await (const data of ch) {
|
|
19
51
|
const { ref, info } = getOplog(data)
|
|
20
|
-
const link = {
|
|
52
|
+
const link = {
|
|
53
|
+
key: ref.oplog.key,
|
|
54
|
+
length: ref.oplog.length,
|
|
55
|
+
weight: ref.oplog.weight,
|
|
56
|
+
timestamp: info.timestamp
|
|
57
|
+
}
|
|
21
58
|
|
|
22
|
-
if (
|
|
59
|
+
if (!isOrderedBefore(node, link)) break
|
|
23
60
|
reorder.push(ref)
|
|
24
61
|
}
|
|
25
62
|
|
|
26
63
|
const inflated = await inflate(auto, reorder.reverse())
|
|
27
64
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const c = cmp(op, node)
|
|
65
|
+
const { undo, shared } = addSorted(inflated, batch)
|
|
66
|
+
|
|
67
|
+
const tip = inflated.slice(shared).map((b) => b.batch)
|
|
32
68
|
|
|
33
|
-
|
|
69
|
+
return {
|
|
70
|
+
undo,
|
|
71
|
+
view: null,
|
|
72
|
+
tip
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isOrderedBefore(node, oplog) {
|
|
77
|
+
const { version, weight, timestamp } = oplog
|
|
78
|
+
if (version === 0) return false
|
|
79
|
+
if (node.weight < weight) return false
|
|
80
|
+
if (isLinking(node, oplog)) return false
|
|
81
|
+
return node.timestamp <= timestamp
|
|
82
|
+
}
|
|
34
83
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
84
|
+
function addSorted(list, batch) {
|
|
85
|
+
const node = batch[0]
|
|
86
|
+
const clock = new Clock()
|
|
87
|
+
for (const link of node.links) {
|
|
88
|
+
clock.add(link)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const tip = []
|
|
92
|
+
const linked = []
|
|
93
|
+
|
|
94
|
+
let undo = null
|
|
95
|
+
let shared = list.length
|
|
96
|
+
|
|
97
|
+
while (list.length) {
|
|
98
|
+
// reached a stable sorting point
|
|
99
|
+
if (cmp(node, list[list.length - 1].batch[0]) > 0) {
|
|
100
|
+
break
|
|
39
101
|
}
|
|
40
102
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
103
|
+
const b = list.pop()
|
|
104
|
+
const target = b.batch[0]
|
|
105
|
+
|
|
106
|
+
if (list.length < shared) {
|
|
107
|
+
undo = b.undo
|
|
108
|
+
shared = list.length
|
|
45
109
|
}
|
|
46
|
-
}
|
|
47
110
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
111
|
+
// move past this node
|
|
112
|
+
if (!clock.has(target)) {
|
|
113
|
+
tip.push(b)
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// remove node
|
|
118
|
+
linked.push(b)
|
|
119
|
+
|
|
120
|
+
// update the clock to catch linked nodes
|
|
121
|
+
for (const link of target.links) {
|
|
122
|
+
clock.add(link)
|
|
123
|
+
}
|
|
52
124
|
}
|
|
125
|
+
|
|
126
|
+
while (linked.length) addSorted(list, linked.pop())
|
|
127
|
+
list.push({ undo: null, batch })
|
|
128
|
+
while (tip.length) list.push(tip.pop())
|
|
129
|
+
|
|
130
|
+
return { undo, shared }
|
|
53
131
|
}
|
|
54
132
|
|
|
55
133
|
function cmp(a, b) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
if (c === 0) {
|
|
59
|
-
return a.length - b.length
|
|
60
|
-
}
|
|
134
|
+
const w = a.weight - b.weight
|
|
135
|
+
if (w) return w > 0 ? -1 : 1
|
|
61
136
|
|
|
62
137
|
const t = a.timestamp - b.timestamp
|
|
138
|
+
if (t) return t < 0 ? -1 : 1
|
|
63
139
|
|
|
64
|
-
|
|
65
|
-
|
|
140
|
+
const c = b4a.compare(a.key, b.key)
|
|
141
|
+
if (c) return c
|
|
142
|
+
|
|
143
|
+
return a.length < b.length ? -1 : 1
|
|
66
144
|
}
|
|
67
145
|
|
|
68
146
|
async function inflate(auto, reorder) {
|
|
@@ -74,7 +152,7 @@ async function inflate(auto, reorder) {
|
|
|
74
152
|
|
|
75
153
|
const local = auto.writers.localWriter
|
|
76
154
|
if (local && local.id === id) {
|
|
77
|
-
promises.push(getLocalBatch(ref.undo, local, ref.oplog.length))
|
|
155
|
+
promises.push(getLocalBatch(ref.undo, local, ref.oplog.length, ref.oplog.weight))
|
|
78
156
|
continue
|
|
79
157
|
}
|
|
80
158
|
|
|
@@ -84,7 +162,7 @@ async function inflate(auto, reorder) {
|
|
|
84
162
|
sessions.set(id, core)
|
|
85
163
|
}
|
|
86
164
|
|
|
87
|
-
promises.push(getOplogBatch(ref.undo, core, ref.oplog.length))
|
|
165
|
+
promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, ref.oplog.weight))
|
|
88
166
|
}
|
|
89
167
|
|
|
90
168
|
try {
|
|
@@ -94,11 +172,11 @@ async function inflate(auto, reorder) {
|
|
|
94
172
|
}
|
|
95
173
|
}
|
|
96
174
|
|
|
97
|
-
async function getOplogBatch(undo, core, length) {
|
|
175
|
+
async function getOplogBatch(undo, core, length, weight) {
|
|
98
176
|
const seq = length - 1
|
|
99
177
|
const block = await core.get(seq)
|
|
100
178
|
const oplog = encoding.decodeOplog(block)
|
|
101
|
-
const head = createNode(core, length, oplog)
|
|
179
|
+
const head = createNode(core, length, weight, oplog)
|
|
102
180
|
|
|
103
181
|
const batch = []
|
|
104
182
|
const result = { undo, batch }
|
|
@@ -115,7 +193,7 @@ async function getOplogBatch(undo, core, length) {
|
|
|
115
193
|
const blocks = await Promise.all(remaining)
|
|
116
194
|
for (let i = 0; i < blocks.length; i++) {
|
|
117
195
|
const oplog = encoding.decodeOplog(blocks[i])
|
|
118
|
-
batch.push(createNode(core, i + start + 1, oplog))
|
|
196
|
+
batch.push(createNode(core, i + start + 1, weight, oplog))
|
|
119
197
|
}
|
|
120
198
|
}
|
|
121
199
|
|
|
@@ -123,7 +201,7 @@ async function getOplogBatch(undo, core, length) {
|
|
|
123
201
|
return result
|
|
124
202
|
}
|
|
125
203
|
|
|
126
|
-
|
|
204
|
+
function getLocalBatch(undo, writer, length, weight) {
|
|
127
205
|
if (length > writer.core.length) {
|
|
128
206
|
if (!writer.pending) throw new Error('No local nodes')
|
|
129
207
|
|
|
@@ -142,16 +220,13 @@ async function getLocalBatch(undo, writer, length) {
|
|
|
142
220
|
return { undo, batch }
|
|
143
221
|
}
|
|
144
222
|
|
|
145
|
-
return getOplogBatch(undo, writer.core, length)
|
|
223
|
+
return getOplogBatch(undo, writer.core, length, weight)
|
|
146
224
|
}
|
|
147
225
|
|
|
148
226
|
function getOplog(data) {
|
|
149
227
|
const result = { ref: null, info: null }
|
|
150
|
-
for (
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
for (let j = 0; j < keys.length; j++) {
|
|
154
|
-
const k = keys[j]
|
|
228
|
+
for (const { keys } of data.batch) {
|
|
229
|
+
for (const k of keys) {
|
|
155
230
|
const prefix = k.key[0]
|
|
156
231
|
|
|
157
232
|
if (prefix === 0) {
|
package/lib/writers.js
CHANGED
|
@@ -3,6 +3,8 @@ const encoding = require('./encoding.js')
|
|
|
3
3
|
const Triggers = require('./triggers.js')
|
|
4
4
|
const { WriterEncryption } = require('autobee-encryption')
|
|
5
5
|
|
|
6
|
+
const WRITER_PREFETCH = 10
|
|
7
|
+
|
|
6
8
|
const BATCH_WRITER_SANITY = 1024
|
|
7
9
|
const BATCH_OPTIMISTIC_SANITY = 32
|
|
8
10
|
|
|
@@ -80,7 +82,7 @@ class ActiveWriters {
|
|
|
80
82
|
const info = await this.auto.system.get(this.auto.local.key)
|
|
81
83
|
const writable = this.writable
|
|
82
84
|
|
|
83
|
-
w.
|
|
85
|
+
w.weight = info ? info.weight : 0
|
|
84
86
|
this.writable = info ? !info.isRemoved : bootstrapping
|
|
85
87
|
|
|
86
88
|
if (writable === this.writable) return
|
|
@@ -158,23 +160,30 @@ class Writer {
|
|
|
158
160
|
this.core = core
|
|
159
161
|
this.id = id
|
|
160
162
|
this.index = 0
|
|
163
|
+
this.weight = 0
|
|
161
164
|
this.pending = null
|
|
162
165
|
this.waiting = null
|
|
166
|
+
this.download = null
|
|
163
167
|
|
|
164
168
|
this.isPending = false
|
|
165
169
|
this.isClosed = false
|
|
166
170
|
this.isAttached = false
|
|
167
|
-
this.isIndexer = false
|
|
168
171
|
this.isAdded = false
|
|
169
172
|
this.isRemoved = false
|
|
170
173
|
this.isCoupled = false
|
|
171
174
|
|
|
172
|
-
this.
|
|
173
|
-
|
|
175
|
+
this._onchangeBound = this._onchange.bind(this)
|
|
176
|
+
|
|
177
|
+
if (!local) this.core.on('append', this._onchangeBound)
|
|
178
|
+
if (!local) this.core.on('download', this._onchangeBound)
|
|
174
179
|
|
|
175
180
|
this.couple()
|
|
176
181
|
}
|
|
177
182
|
|
|
183
|
+
get isIndexer() {
|
|
184
|
+
return this.weight === 2
|
|
185
|
+
}
|
|
186
|
+
|
|
178
187
|
couple() {
|
|
179
188
|
if (this.isCoupled) return
|
|
180
189
|
// if we don't have coupler yet we can't add
|
|
@@ -216,7 +225,7 @@ class Writer {
|
|
|
216
225
|
}
|
|
217
226
|
}
|
|
218
227
|
|
|
219
|
-
|
|
228
|
+
_onchange() {
|
|
220
229
|
if (this.waiting === null) this.bump()
|
|
221
230
|
}
|
|
222
231
|
|
|
@@ -236,6 +245,8 @@ class Writer {
|
|
|
236
245
|
detach() {
|
|
237
246
|
if (!this.isAttached) return
|
|
238
247
|
this.isAttached = false
|
|
248
|
+
this.download.destroy()
|
|
249
|
+
this.download = null
|
|
239
250
|
this.writers.active.delete(this.id)
|
|
240
251
|
this.removePending()
|
|
241
252
|
}
|
|
@@ -258,6 +269,17 @@ class Writer {
|
|
|
258
269
|
return this.close()
|
|
259
270
|
}
|
|
260
271
|
|
|
272
|
+
async views() {
|
|
273
|
+
const latest = await this.getLatest()
|
|
274
|
+
if (!latest || !latest.views) return []
|
|
275
|
+
|
|
276
|
+
// signedLength for autobase compat
|
|
277
|
+
return [
|
|
278
|
+
{ ...latest.views.system, signedLength: latest.views.system.length },
|
|
279
|
+
{ ...latest.views.view, signedLength: latest.views.view.length }
|
|
280
|
+
]
|
|
281
|
+
}
|
|
282
|
+
|
|
261
283
|
async getLatest() {
|
|
262
284
|
if (this.core.length === 0) return null
|
|
263
285
|
const block = await this.core.get(this.core.length - 1)
|
|
@@ -273,7 +295,7 @@ class Writer {
|
|
|
273
295
|
} else {
|
|
274
296
|
this.isAdded = true
|
|
275
297
|
this.isRemoved = info.isRemoved
|
|
276
|
-
this.
|
|
298
|
+
this.weight = info.weight
|
|
277
299
|
}
|
|
278
300
|
|
|
279
301
|
return info ? info.length : 0
|
|
@@ -289,10 +311,21 @@ class Writer {
|
|
|
289
311
|
let optimistic = false
|
|
290
312
|
let timestamp = 0
|
|
291
313
|
|
|
314
|
+
if (this.download) this.download.destroy()
|
|
315
|
+
this.download = this.core.download({ start: length, end: length + WRITER_PREFETCH })
|
|
316
|
+
|
|
292
317
|
while (length < this.core.length) {
|
|
318
|
+
// @todo aim to prefetch when writer added, with read-ahead
|
|
319
|
+
// download the block if it's not available locally
|
|
320
|
+
// writer will be added back as pending automatically
|
|
321
|
+
if (!(await this.core.has(length))) {
|
|
322
|
+
this.removePending()
|
|
323
|
+
return null
|
|
324
|
+
}
|
|
325
|
+
|
|
293
326
|
const block = await this.core.get(length++)
|
|
294
327
|
const oplog = encoding.decodeOplog(block)
|
|
295
|
-
const node = createNode(this.core, length, oplog)
|
|
328
|
+
const node = createNode(this.core, length, this.weight, oplog)
|
|
296
329
|
const b = oplog.batch
|
|
297
330
|
|
|
298
331
|
// auto correct some batch invariants
|
|
@@ -336,6 +369,9 @@ class Writer {
|
|
|
336
369
|
if (this.core.writable && this.pending !== null) {
|
|
337
370
|
for (let i = 0; i < this.pending.length; i++) {
|
|
338
371
|
const node = this.pending[i]
|
|
372
|
+
|
|
373
|
+
node.batch = { start: i, end: this.pending.length - 1 - i }
|
|
374
|
+
|
|
339
375
|
if (node.length <= length) continue
|
|
340
376
|
|
|
341
377
|
if (!this.writers.writable && !node.optimistic) {
|
|
@@ -409,7 +445,12 @@ class Writer {
|
|
|
409
445
|
value
|
|
410
446
|
}
|
|
411
447
|
|
|
412
|
-
const node = createNode(
|
|
448
|
+
const node = createNode(
|
|
449
|
+
this.core,
|
|
450
|
+
this.core.length + this.pending.length + 1,
|
|
451
|
+
this.weight,
|
|
452
|
+
oplog
|
|
453
|
+
)
|
|
413
454
|
|
|
414
455
|
this.pending.push(node)
|
|
415
456
|
this.addPending()
|
|
@@ -427,7 +468,11 @@ class Writer {
|
|
|
427
468
|
const s = this.system.bee.head()
|
|
428
469
|
const flushes = this.system.flushes
|
|
429
470
|
this.pending[this.pending.length - 1].views = { system: s, flushes, view }
|
|
430
|
-
for (
|
|
471
|
+
for (let i = 0; i < this.pending.length; i++) {
|
|
472
|
+
const node = this.pending[i]
|
|
473
|
+
node.batch = { start: i, end: this.pending.length - 1 - i }
|
|
474
|
+
buffers.push(encoding.encodeOplog(node))
|
|
475
|
+
}
|
|
431
476
|
this.pending = null
|
|
432
477
|
return this.core.append(buffers)
|
|
433
478
|
}
|
|
@@ -437,11 +482,12 @@ exports.ActiveWriters = ActiveWriters
|
|
|
437
482
|
exports.Writer = Writer
|
|
438
483
|
exports.createNode = createNode
|
|
439
484
|
|
|
440
|
-
function createNode(core, length, oplog) {
|
|
485
|
+
function createNode(core, length, weight, oplog) {
|
|
441
486
|
return {
|
|
442
487
|
core,
|
|
443
488
|
key: core.key,
|
|
444
489
|
length,
|
|
490
|
+
weight,
|
|
445
491
|
version: oplog.version,
|
|
446
492
|
timestamp: oplog.timestamp,
|
|
447
493
|
links: oplog.links,
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autobee",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Bee based autobase",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Holepunch Inc.",
|
|
7
7
|
"main": "index.js",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/holepunchto/autobee.git"
|
|
11
|
+
},
|
|
8
12
|
"files": [
|
|
9
13
|
"index.js",
|
|
10
14
|
"lib/*.js",
|
|
@@ -13,7 +17,7 @@
|
|
|
13
17
|
"scripts": {
|
|
14
18
|
"format": "prettier . --write",
|
|
15
19
|
"lint": "lunte && prettier . --check",
|
|
16
|
-
"test": "
|
|
20
|
+
"test": "node test/all.js",
|
|
17
21
|
"test:bare": "bare test/all.js",
|
|
18
22
|
"test:encrypted": "bare test/all.js --encrypt-all",
|
|
19
23
|
"test:generate": "brittle -r test/all.js test/*.js"
|