hyperbee2 0.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/README.md +19 -0
- package/index.js +240 -0
- package/lib/cache.js +65 -0
- package/lib/changes-stream.js +78 -0
- package/lib/compat.js +164 -0
- package/lib/context.js +128 -0
- package/lib/encoding.js +14 -0
- package/lib/key-value-stream.js +134 -0
- package/lib/tree.js +130 -0
- package/lib/write.js +409 -0
- package/package.json +41 -0
- package/spec/hyperschema/index.js +248 -0
- package/spec/hyperschema/schema.json +162 -0
package/README.md
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
const b4a = require('b4a')
|
|
2
|
+
const Hypercore = require('hypercore')
|
|
3
|
+
const KeyValueStream = require('./lib/key-value-stream.js')
|
|
4
|
+
const ChangesStream = require('./lib/changes-stream.js')
|
|
5
|
+
const NodeCache = require('./lib/cache.js')
|
|
6
|
+
const WriteBatch = require('./lib/write.js')
|
|
7
|
+
const CoreContext = require('./lib/context.js')
|
|
8
|
+
const { DataPointer, TreeNode, TreeNodePointer, EMPTY } = require('./lib/tree.js')
|
|
9
|
+
|
|
10
|
+
class Hyperbee {
|
|
11
|
+
constructor(store, options = {}) {
|
|
12
|
+
const {
|
|
13
|
+
key = null,
|
|
14
|
+
core = key ? store.get(key) : store.get({ key, name: 'bee' }),
|
|
15
|
+
context = new CoreContext(store, core),
|
|
16
|
+
maxCacheSize = 4096,
|
|
17
|
+
cache = new NodeCache(maxCacheSize),
|
|
18
|
+
root = null,
|
|
19
|
+
activeRequests = [],
|
|
20
|
+
view = false,
|
|
21
|
+
writable = true,
|
|
22
|
+
unbatch = 0
|
|
23
|
+
} = options
|
|
24
|
+
|
|
25
|
+
this.store = store
|
|
26
|
+
this.root = root
|
|
27
|
+
this.cache = cache
|
|
28
|
+
this.context = context
|
|
29
|
+
this.activeRequests = activeRequests
|
|
30
|
+
this.view = view
|
|
31
|
+
this.writable = writable
|
|
32
|
+
this.unbatch = unbatch
|
|
33
|
+
|
|
34
|
+
this.ready().catch(noop)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
head() {
|
|
38
|
+
if (!this.root) return null
|
|
39
|
+
if (this.root === EMPTY) return { length: 0, key: this.context.core.key }
|
|
40
|
+
return { length: this.root.seq + 1, key: this.root.context.core.key }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get core() {
|
|
44
|
+
return this.context.core
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get opening() {
|
|
48
|
+
return this.store.opening
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get opened() {
|
|
52
|
+
return this.store.opened
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get closing() {
|
|
56
|
+
return this.store.closing
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get closed() {
|
|
60
|
+
return this.store.closed
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
replicate(...opts) {
|
|
64
|
+
return this.store.replicate(...opts)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_makeView(root, writable, unbatch) {
|
|
68
|
+
return new Hyperbee(this.store, {
|
|
69
|
+
context: this.context,
|
|
70
|
+
cache: this.cache,
|
|
71
|
+
root,
|
|
72
|
+
view: true,
|
|
73
|
+
writable,
|
|
74
|
+
unbatch
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
checkout({ length = this.core.length, key = null, writable = false } = {}) {
|
|
79
|
+
const context = key ? this.context.getContextByKey(key) : this.context
|
|
80
|
+
const root = length === 0 ? EMPTY : new TreeNodePointer(context, 0, length - 1, 0, false, null)
|
|
81
|
+
return this._makeView(root, writable, 0)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
snapshot() {
|
|
85
|
+
return this._makeView(this.root, false, 0)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
undo(n) {
|
|
89
|
+
return this._makeView(this.root, true, n)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
write(opts) {
|
|
93
|
+
if (!this.writable) throw new Error('Not writable')
|
|
94
|
+
return new WriteBatch(this, opts)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async ready() {
|
|
98
|
+
if (!this.core.opened) await this.core.ready()
|
|
99
|
+
if (this.root) return
|
|
100
|
+
|
|
101
|
+
this.root =
|
|
102
|
+
this.context.core.length === 0
|
|
103
|
+
? EMPTY
|
|
104
|
+
: new TreeNodePointer(this.context, 0, this.core.length - 1, 0, false, null)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async close() {
|
|
108
|
+
if (this.activeRequests.length) Hypercore.clearRequests(this.activeRequests)
|
|
109
|
+
if (!this.view) await this.store.close()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
createReadStream(range) {
|
|
113
|
+
return new KeyValueStream(this, range)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
createChangesStream(options) {
|
|
117
|
+
return new ChangesStream(this, options)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async peek(range = {}) {
|
|
121
|
+
const rs = new KeyValueStream(this, { ...range, limit: 1 })
|
|
122
|
+
let entry = null
|
|
123
|
+
for await (const data of rs) entry = data
|
|
124
|
+
return entry
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
bump(ptr) {
|
|
128
|
+
if (ptr.changed) return ptr.value
|
|
129
|
+
this.cache.bump(ptr)
|
|
130
|
+
this.cache.gc()
|
|
131
|
+
return ptr.value
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async inflate(ptr, activeRequests = this.activeRequests) {
|
|
135
|
+
if (ptr.value) {
|
|
136
|
+
this.bump(ptr)
|
|
137
|
+
return ptr.value
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const block = await ptr.context.getBlock(ptr.seq, ptr.core, activeRequests)
|
|
141
|
+
const context = await ptr.context.getContext(ptr.core, activeRequests)
|
|
142
|
+
const tree = block.tree[ptr.offset]
|
|
143
|
+
|
|
144
|
+
const keys = new Array(tree.keys.length)
|
|
145
|
+
const children = new Array(tree.children.length)
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < keys.length; i++) {
|
|
148
|
+
const k = tree.keys[i]
|
|
149
|
+
const blk =
|
|
150
|
+
k.seq === ptr.seq && k.core === ptr.core
|
|
151
|
+
? block
|
|
152
|
+
: await context.getBlock(k.seq, k.core, activeRequests)
|
|
153
|
+
const d = blk.data[k.offset]
|
|
154
|
+
keys[i] = new DataPointer(context, k.core, k.seq, k.offset, false, d.key, d.value)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < children.length; i++) {
|
|
158
|
+
const c = tree.children[i]
|
|
159
|
+
children[i] = new TreeNodePointer(context, c.core, c.seq, c.offset, false, null)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
ptr.value = new TreeNode(keys, children)
|
|
163
|
+
this.bump(ptr)
|
|
164
|
+
|
|
165
|
+
return ptr.value
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async bootstrap(activeRequests = this.activeRequests) {
|
|
169
|
+
if (!this.root) await this.ready()
|
|
170
|
+
if (this.unbatch) await this._rollback(activeRequests)
|
|
171
|
+
return this.root === EMPTY ? null : this.root
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async _rollback(activeRequests) {
|
|
175
|
+
const expected = this.unbatch
|
|
176
|
+
|
|
177
|
+
let n = expected
|
|
178
|
+
let length = this.root === EMPTY ? 0 : this.root.seq + 1
|
|
179
|
+
let context = this.context
|
|
180
|
+
|
|
181
|
+
while (n > 0 && length > 0 && expected === this.unbatch) {
|
|
182
|
+
const seq = length - 1
|
|
183
|
+
const blk = await context.getBlock(seq, 0, activeRequests)
|
|
184
|
+
|
|
185
|
+
if (!blk.previous) {
|
|
186
|
+
length = 0
|
|
187
|
+
break
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
context = await context.getContext(blk.previous.core, activeRequests)
|
|
191
|
+
length = blk.previous.seq + 1
|
|
192
|
+
n--
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (expected === this.unbatch) {
|
|
196
|
+
this.context = context
|
|
197
|
+
this.root = length === 0 ? EMPTY : new TreeNodePointer(context, 0, length - 1, 0, false, null)
|
|
198
|
+
this.unbatch = 0
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
update(root = null) {
|
|
203
|
+
this.root = root
|
|
204
|
+
this.unbatch = 0
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async get(key, { activeRequests = this.activeRequests } = {}) {
|
|
208
|
+
let ptr = await this.bootstrap(activeRequests)
|
|
209
|
+
if (!ptr) return null
|
|
210
|
+
|
|
211
|
+
while (true) {
|
|
212
|
+
const v = ptr.value ? this.bump(ptr) : await this.inflate(ptr, activeRequests)
|
|
213
|
+
|
|
214
|
+
let s = 0
|
|
215
|
+
let e = v.keys.length
|
|
216
|
+
let c = 0
|
|
217
|
+
|
|
218
|
+
while (s < e) {
|
|
219
|
+
const mid = (s + e) >> 1
|
|
220
|
+
const m = v.keys[mid]
|
|
221
|
+
|
|
222
|
+
c = b4a.compare(key, m.key)
|
|
223
|
+
|
|
224
|
+
if (c === 0) return m
|
|
225
|
+
|
|
226
|
+
if (c < 0) e = mid
|
|
227
|
+
else s = mid + 1
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!v.children.length) return null
|
|
231
|
+
|
|
232
|
+
const i = c < 0 ? e : s
|
|
233
|
+
ptr = v.children[i]
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = Hyperbee
|
|
239
|
+
|
|
240
|
+
function noop() {}
|
package/lib/cache.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module.exports = class NodeCache {
|
|
2
|
+
constructor(maxSize) {
|
|
3
|
+
this.size = 0
|
|
4
|
+
this.maxSize = maxSize
|
|
5
|
+
this.latest = null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
oldest() {
|
|
9
|
+
return this.latest ? this.latest.next : null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
gc() {
|
|
13
|
+
while (this.size > this.maxSize) {
|
|
14
|
+
const old = this.oldest()
|
|
15
|
+
if (old.changed) break
|
|
16
|
+
this.remove(old)
|
|
17
|
+
old.value = null
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
bump(node) {
|
|
22
|
+
if (node === this.latest) return
|
|
23
|
+
|
|
24
|
+
if (node.prev) this.remove(node)
|
|
25
|
+
this.size++
|
|
26
|
+
|
|
27
|
+
if (!this.latest) {
|
|
28
|
+
node.prev = node.next = node
|
|
29
|
+
this.latest = node
|
|
30
|
+
} else {
|
|
31
|
+
node.prev = this.latest
|
|
32
|
+
node.next = this.latest.next
|
|
33
|
+
this.latest.next.prev = node
|
|
34
|
+
this.latest.next = node
|
|
35
|
+
this.latest = node
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
remove(node) {
|
|
40
|
+
if (node.prev) {
|
|
41
|
+
this.size--
|
|
42
|
+
|
|
43
|
+
if (node === this.latest) {
|
|
44
|
+
this.latest = node.next === node ? null : node.next
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
node.prev.next = node.next
|
|
48
|
+
node.next.prev = node.prev
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
node.prev = node.next = null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
*[Symbol.iterator]() {
|
|
55
|
+
const node = this.latest
|
|
56
|
+
if (node === null) return
|
|
57
|
+
|
|
58
|
+
let next = node
|
|
59
|
+
|
|
60
|
+
do {
|
|
61
|
+
yield next
|
|
62
|
+
next = next.next
|
|
63
|
+
} while (node !== next)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const { Readable } = require('streamx')
|
|
2
|
+
|
|
3
|
+
module.exports = class ChangesStream extends Readable {
|
|
4
|
+
constructor(tree, options = {}) {
|
|
5
|
+
super({ eagerOpen: true })
|
|
6
|
+
|
|
7
|
+
const { head = null, activeRequests = tree.activeRequests } = options
|
|
8
|
+
|
|
9
|
+
this.tree = tree
|
|
10
|
+
|
|
11
|
+
this._head = head
|
|
12
|
+
this._context = null
|
|
13
|
+
this._activeRequests = activeRequests
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async _openp() {
|
|
17
|
+
await this.tree.bootstrap(this._activeRequests)
|
|
18
|
+
if (this._head === null) this._head = this.tree.head()
|
|
19
|
+
if (this._head !== null) this._context = this.tree.context.getContextByKey(this._head.key)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async _readp() {
|
|
23
|
+
if (!this._context || this._head.length === 0) {
|
|
24
|
+
this.push(null)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const data = {
|
|
29
|
+
head: this._head,
|
|
30
|
+
batch: []
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const seq = this._head.length - 1
|
|
34
|
+
const blk = await this._context.getBlock(seq, 0, this._activeRequests)
|
|
35
|
+
const batchStart = seq - blk.batch.start
|
|
36
|
+
const remaining = new Array(blk.batch.start)
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
39
|
+
remaining[i] = this._context.getBlock(batchStart + i, 0, this._activeRequests)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const blk of await Promise.all(remaining)) data.batch.push(blk)
|
|
43
|
+
data.batch.push(blk)
|
|
44
|
+
|
|
45
|
+
this.push(data)
|
|
46
|
+
|
|
47
|
+
if (!blk.previous) {
|
|
48
|
+
this._head = null
|
|
49
|
+
this._context = null
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this._context = await this._context.getContext(blk.previous.core, this._activeRequests)
|
|
54
|
+
this._head = { key: this._context.core.key, length: blk.previous.seq + 1 }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async _open(cb) {
|
|
58
|
+
try {
|
|
59
|
+
await this._openp()
|
|
60
|
+
} catch (err) {
|
|
61
|
+
cb(err)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
cb(null)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async _read(cb) {
|
|
69
|
+
try {
|
|
70
|
+
await this._readp()
|
|
71
|
+
} catch (err) {
|
|
72
|
+
cb(err)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
cb(null)
|
|
77
|
+
}
|
|
78
|
+
}
|
package/lib/compat.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// ported from the old protocol buffer impl in old bee
|
|
2
|
+
|
|
3
|
+
const encodings = require('protocol-buffers-encodings')
|
|
4
|
+
const varint = encodings.varint
|
|
5
|
+
const skip = encodings.skip
|
|
6
|
+
|
|
7
|
+
module.exports = decodeCompat
|
|
8
|
+
|
|
9
|
+
function decodeCompat(buffer, seq) {
|
|
10
|
+
const node = decodeNode(buffer)
|
|
11
|
+
const index = decodeYoloIndex(node.index)
|
|
12
|
+
const morphed = {
|
|
13
|
+
type: 0,
|
|
14
|
+
checkpoint: 0,
|
|
15
|
+
batch: { start: 0, end: 0 },
|
|
16
|
+
previous: seq > 1 ? { core: 0, seq: seq - 1 } : null,
|
|
17
|
+
tree: [],
|
|
18
|
+
data: [{ key: node.key, value: node.value }],
|
|
19
|
+
cores: null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const lvl of index.levels) {
|
|
23
|
+
const t = { keys: [], children: [] }
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < lvl.keys.length; i++) {
|
|
26
|
+
const seq = lvl.keys[i]
|
|
27
|
+
t.keys.push({ core: 0, seq, offset: 0 })
|
|
28
|
+
}
|
|
29
|
+
for (let i = 0; i < lvl.children.length; i += 2) {
|
|
30
|
+
const seq = lvl.children[i]
|
|
31
|
+
const offset = lvl.children[i + 1]
|
|
32
|
+
t.children.push({ core: 0, seq, offset })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
morphed.tree.push(t)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return morphed
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function decodeLevel(buf, offset, end) {
|
|
42
|
+
if (!offset) offset = 0
|
|
43
|
+
if (!end) end = buf.length
|
|
44
|
+
if (!(end <= buf.length && offset <= buf.length)) throw new Error('Decoded message is not valid')
|
|
45
|
+
|
|
46
|
+
const oldOffset = offset
|
|
47
|
+
|
|
48
|
+
const obj = {
|
|
49
|
+
keys: [],
|
|
50
|
+
children: []
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
while (true) {
|
|
54
|
+
if (end <= offset) {
|
|
55
|
+
decodeLevel.bytes = offset - oldOffset
|
|
56
|
+
return obj
|
|
57
|
+
}
|
|
58
|
+
const prefix = varint.decode(buf, offset)
|
|
59
|
+
offset += varint.decode.bytes
|
|
60
|
+
const tag = prefix >> 3
|
|
61
|
+
switch (tag) {
|
|
62
|
+
case 1: {
|
|
63
|
+
let packedEnd = varint.decode(buf, offset)
|
|
64
|
+
offset += varint.decode.bytes
|
|
65
|
+
packedEnd += offset
|
|
66
|
+
while (offset < packedEnd) {
|
|
67
|
+
obj.keys.push(encodings.varint.decode(buf, offset))
|
|
68
|
+
offset += encodings.varint.decode.bytes
|
|
69
|
+
}
|
|
70
|
+
break
|
|
71
|
+
}
|
|
72
|
+
case 2: {
|
|
73
|
+
let packedEnd = varint.decode(buf, offset)
|
|
74
|
+
offset += varint.decode.bytes
|
|
75
|
+
packedEnd += offset
|
|
76
|
+
while (offset < packedEnd) {
|
|
77
|
+
obj.children.push(encodings.varint.decode(buf, offset))
|
|
78
|
+
offset += encodings.varint.decode.bytes
|
|
79
|
+
}
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
default: {
|
|
83
|
+
offset = skip(prefix & 7, buf, offset)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function decodeYoloIndex(buf, offset, end) {
|
|
90
|
+
if (!offset) offset = 0
|
|
91
|
+
if (!end) end = buf.length
|
|
92
|
+
if (!(end <= buf.length && offset <= buf.length)) throw new Error('Decoded message is not valid')
|
|
93
|
+
const oldOffset = offset
|
|
94
|
+
const obj = {
|
|
95
|
+
levels: []
|
|
96
|
+
}
|
|
97
|
+
while (true) {
|
|
98
|
+
if (end <= offset) {
|
|
99
|
+
decodeYoloIndex.bytes = offset - oldOffset
|
|
100
|
+
return obj
|
|
101
|
+
}
|
|
102
|
+
const prefix = varint.decode(buf, offset)
|
|
103
|
+
offset += varint.decode.bytes
|
|
104
|
+
const tag = prefix >> 3
|
|
105
|
+
switch (tag) {
|
|
106
|
+
case 1: {
|
|
107
|
+
const len = varint.decode(buf, offset)
|
|
108
|
+
offset += varint.decode.bytes
|
|
109
|
+
obj.levels.push(decodeLevel(buf, offset, offset + len))
|
|
110
|
+
offset += decodeLevel.bytes
|
|
111
|
+
break
|
|
112
|
+
}
|
|
113
|
+
default: {
|
|
114
|
+
offset = skip(prefix & 7, buf, offset)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function decodeNode(buf, offset, end) {
|
|
121
|
+
if (!offset) offset = 0
|
|
122
|
+
if (!end) end = buf.length
|
|
123
|
+
if (!(end <= buf.length && offset <= buf.length)) throw new Error('Decoded message is not valid')
|
|
124
|
+
const oldOffset = offset
|
|
125
|
+
const obj = {
|
|
126
|
+
index: null,
|
|
127
|
+
key: null,
|
|
128
|
+
value: null
|
|
129
|
+
}
|
|
130
|
+
let found0 = false
|
|
131
|
+
let found1 = false
|
|
132
|
+
while (true) {
|
|
133
|
+
if (end <= offset) {
|
|
134
|
+
if (!found0 || !found1) throw new Error('Decoded message is not valid')
|
|
135
|
+
decodeNode.bytes = offset - oldOffset
|
|
136
|
+
return obj
|
|
137
|
+
}
|
|
138
|
+
const prefix = varint.decode(buf, offset)
|
|
139
|
+
offset += varint.decode.bytes
|
|
140
|
+
const tag = prefix >> 3
|
|
141
|
+
switch (tag) {
|
|
142
|
+
case 1: {
|
|
143
|
+
obj.index = encodings.bytes.decode(buf, offset)
|
|
144
|
+
offset += encodings.bytes.decode.bytes
|
|
145
|
+
found0 = true
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
case 2: {
|
|
149
|
+
obj.key = encodings.bytes.decode(buf, offset)
|
|
150
|
+
offset += encodings.bytes.decode.bytes
|
|
151
|
+
found1 = true
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
case 3: {
|
|
155
|
+
obj.value = encodings.bytes.decode(buf, offset)
|
|
156
|
+
offset += encodings.bytes.decode.bytes
|
|
157
|
+
break
|
|
158
|
+
}
|
|
159
|
+
default: {
|
|
160
|
+
offset = skip(prefix & 7, buf, offset)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
package/lib/context.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const b4a = require('b4a')
|
|
2
|
+
const { decodeBlock } = require('./encoding.js')
|
|
3
|
+
|
|
4
|
+
class CoreContext {
|
|
5
|
+
constructor(store, core, other = new Map()) {
|
|
6
|
+
this.store = store
|
|
7
|
+
this.core = core
|
|
8
|
+
this.other = other
|
|
9
|
+
this.length = 0
|
|
10
|
+
this.checkpoint = 0
|
|
11
|
+
this.opened = []
|
|
12
|
+
this.cores = []
|
|
13
|
+
this.changed = false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async update(activeRequests) {
|
|
17
|
+
await this.core.ready()
|
|
18
|
+
|
|
19
|
+
if (this.length === this.core.length || this.core.length === 0) return
|
|
20
|
+
|
|
21
|
+
const length = this.core.length
|
|
22
|
+
const seq = length - 1
|
|
23
|
+
const buffer = await this.core.get(seq, { activeRequests })
|
|
24
|
+
const block = decodeBlock(buffer, seq)
|
|
25
|
+
const checkpoint = block.checkpoint
|
|
26
|
+
|
|
27
|
+
if (checkpoint > 0) {
|
|
28
|
+
const buffer = await this.core.get(checkpoint - 1)
|
|
29
|
+
const block = decodeBlock(buffer, checkpoint - 1)
|
|
30
|
+
|
|
31
|
+
if (length < this.length) return
|
|
32
|
+
|
|
33
|
+
this.cores = block.cores || []
|
|
34
|
+
while (this.opened.length < this.cores.length) this.opened.push(null)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (length < this.length) return
|
|
38
|
+
|
|
39
|
+
this.checkpoint = checkpoint
|
|
40
|
+
this.length = length
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async getCoreOffset(context, core, activeRequests) {
|
|
44
|
+
if (core !== 0 && core - 1 >= context.cores.length) await context.update(activeRequests)
|
|
45
|
+
const key = core === 0 ? context.core.key : context.cores[core - 1]
|
|
46
|
+
|
|
47
|
+
if (b4a.equals(key, this.core.key)) return 0
|
|
48
|
+
|
|
49
|
+
// TODO: prop use a map...
|
|
50
|
+
for (let i = 0; i < this.cores.length; i++) {
|
|
51
|
+
const k = this.cores[i]
|
|
52
|
+
if (b4a.equals(k, key)) {
|
|
53
|
+
return i + 1
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.changed = true
|
|
58
|
+
this.cores.push(key)
|
|
59
|
+
return this.cores.length
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getCoreOffsetByKey(key, activeRequests) {
|
|
63
|
+
await this.core.ready()
|
|
64
|
+
|
|
65
|
+
if (b4a.equals(key, this.core.key)) return 0
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < this.cores.length; i++) {
|
|
68
|
+
const k = this.cores[i]
|
|
69
|
+
if (b4a.equals(k, key)) {
|
|
70
|
+
return i + 1
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await this.update(activeRequests)
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < this.cores.length; i++) {
|
|
77
|
+
const k = this.cores[i]
|
|
78
|
+
if (b4a.equals(k, key)) {
|
|
79
|
+
return i + 1
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.changed = true
|
|
84
|
+
this.cores.push(key)
|
|
85
|
+
return this.cores.length
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getCore(index) {
|
|
89
|
+
if (index === 0) return this.core
|
|
90
|
+
if (index > this.cores.length) throw new Error('Bad core index: ' + index)
|
|
91
|
+
if (this.opened[index - 1] === null)
|
|
92
|
+
this.opened[index - 1] = this.store.get(this.cores[index - 1])
|
|
93
|
+
return this.opened[index - 1]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getBlock(seq, core, activeRequests) {
|
|
97
|
+
if (core !== 0) await this.update(activeRequests)
|
|
98
|
+
const hc = this.getCore(core)
|
|
99
|
+
const buffer = await hc.get(seq, { activeRequests })
|
|
100
|
+
const block = decodeBlock(buffer, seq)
|
|
101
|
+
return block
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getContextByKey(key) {
|
|
105
|
+
const hex = b4a.toString(key, 'hex')
|
|
106
|
+
if (this.other.has(hex)) return this.other.get(hex)
|
|
107
|
+
|
|
108
|
+
const ctx = new CoreContext(this.store, this.store.get(key))
|
|
109
|
+
this.other.set(hex, ctx)
|
|
110
|
+
return ctx
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async getContext(core, activeRequests) {
|
|
114
|
+
if (core === 0) return this
|
|
115
|
+
if (core > this.cores.length) await this.update(activeRequests)
|
|
116
|
+
if (core > this.cores.length) throw new Error('Bad core index: ' + core)
|
|
117
|
+
|
|
118
|
+
const hex = b4a.toString(this.cores[core - 1], 'hex')
|
|
119
|
+
if (this.other.has(hex)) return this.other.get(hex)
|
|
120
|
+
|
|
121
|
+
const hc = this.getCore(core)
|
|
122
|
+
const ctx = new CoreContext(this.store, hc)
|
|
123
|
+
this.other.set(hex, ctx)
|
|
124
|
+
return ctx
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = CoreContext
|
package/lib/encoding.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const c = require('compact-encoding')
|
|
2
|
+
const { getEncoding } = require('../spec/hyperschema')
|
|
3
|
+
const decodeCompatBlock = require('./compat.js')
|
|
4
|
+
|
|
5
|
+
const Block = getEncoding('@bee/block')
|
|
6
|
+
|
|
7
|
+
exports.Block = Block
|
|
8
|
+
exports.decodeBlock = decodeBlock
|
|
9
|
+
|
|
10
|
+
function decodeBlock(buffer, seq) {
|
|
11
|
+
const isCompat = buffer.length > 0 && buffer[0] === 0x0a
|
|
12
|
+
const block = isCompat ? decodeCompatBlock(buffer, seq) : c.decode(Block, buffer)
|
|
13
|
+
return block
|
|
14
|
+
}
|