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 ADDED
@@ -0,0 +1,19 @@
1
+ # hyperbee2
2
+
3
+ ```
4
+ npm install hyperbee2
5
+ ```
6
+
7
+ ## Usage
8
+
9
+ ```js
10
+ const Hyperbee2 = require('hyperbee2')
11
+
12
+ const db = new Hyperbee2(store)
13
+
14
+ // see tests for more
15
+ ```
16
+
17
+ ## License
18
+
19
+ Apache-2.0
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
@@ -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
+ }