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.
@@ -0,0 +1,134 @@
1
+ const { Readable } = require('streamx')
2
+ const b4a = require('b4a')
3
+
4
+ module.exports = class KeyValueStream extends Readable {
5
+ constructor(tree, options = {}) {
6
+ super({ eagerOpen: true })
7
+
8
+ this.tree = tree
9
+ this.root = null
10
+
11
+ this._stack = []
12
+ this._activeRequests = options.activeRequests || this.tree.activeRequests
13
+ this._limit = options.limit === undefined ? -1 : options.limit
14
+ this._reverse = !!options.reverse
15
+ this._start = options.gte || options.gt || null
16
+ this._end = options.lte || options.lt || null
17
+ this._inclusive = !!(this._reverse ? options.lte : options.gte)
18
+ this._compare = (this._reverse ? options.gte : options.lte) ? 0 : -1
19
+ }
20
+
21
+ async _openp() {
22
+ if (!this.root) this.root = await this.tree.bootstrap(this._activeRequests)
23
+ if (!this.root) return
24
+
25
+ if (this._limit === 0) return
26
+
27
+ this._stack.push({ node: this.root, offset: 0 })
28
+
29
+ if (this._reverse ? !this._end : !this._start) return
30
+
31
+ const offset = this._inclusive ? 0 : 1
32
+
33
+ while (true) {
34
+ const top = this._stack[this._stack.length - 1]
35
+ const v = top.node.value
36
+ ? this.tree.bump(top.node)
37
+ : await this.tree.inflate(top.node, this._activeRequests)
38
+
39
+ for (let i = 0; i < v.keys.length; i++) {
40
+ const j = this._reverse ? v.keys.length - 1 - i : i
41
+
42
+ const c = this._reverse
43
+ ? b4a.compare(v.keys[j].key, this._end)
44
+ : b4a.compare(this._start, v.keys[j].key)
45
+
46
+ if (c < 0) break
47
+ top.offset = 2 * i + 1 + (c === 0 ? offset : 1)
48
+ }
49
+
50
+ const child = (top.offset & 1) === 0
51
+ const k = top.offset >> 1
52
+
53
+ if (!child || k >= v.children.length) break
54
+
55
+ const j = this._reverse ? v.children.length - 1 - k : k
56
+
57
+ this._stack.push({
58
+ node: v.children[j],
59
+ offset: 0
60
+ })
61
+
62
+ top.offset++
63
+ }
64
+ }
65
+
66
+ async _readp() {
67
+ while (this._stack.length && (this._limit === -1 || this._limit > 0)) {
68
+ const top = this._stack.pop()
69
+ const v = top.node.value
70
+ ? this.tree.bump(top.node)
71
+ : await this.tree.inflate(top.node, this._activeRequests)
72
+
73
+ const offset = top.offset++
74
+ const child = (offset & 1) === 0
75
+ const k = offset >> 1
76
+
77
+ if (child) {
78
+ this._stack.push(top)
79
+ if (k < v.children.length) {
80
+ const j = this._reverse ? v.children.length - 1 - k : k
81
+ this._stack.push({ node: v.children[j], offset: 0 })
82
+ }
83
+ continue
84
+ }
85
+
86
+ if (k < v.keys.length) {
87
+ const j = this._reverse ? v.keys.length - 1 - k : k
88
+
89
+ const data = top.node.value.keys[j]
90
+
91
+ const c = this._reverse
92
+ ? this._start
93
+ ? b4a.compare(this._start, data.key)
94
+ : -1
95
+ : this._end
96
+ ? b4a.compare(data.key, this._end)
97
+ : -1
98
+
99
+ if (c > this._compare) break
100
+
101
+ this._stack.push(top)
102
+ this.push(this._finalize(data))
103
+ if (this._limit !== -1) this._limit--
104
+ return
105
+ }
106
+ }
107
+
108
+ this.push(null)
109
+ }
110
+
111
+ _finalize(kv) {
112
+ return kv
113
+ }
114
+
115
+ async _open(cb) {
116
+ try {
117
+ await this._openp()
118
+ } catch (err) {
119
+ cb(err)
120
+ return
121
+ }
122
+ cb(null)
123
+ }
124
+
125
+ async _read(cb) {
126
+ try {
127
+ await this._readp()
128
+ } catch (err) {
129
+ cb(err)
130
+ return
131
+ }
132
+ cb(null)
133
+ }
134
+ }
package/lib/tree.js ADDED
@@ -0,0 +1,130 @@
1
+ const b4a = require('b4a')
2
+
3
+ const T = 5
4
+ const MIN_KEYS = T - 1
5
+ const MAX_CHILDREN = MIN_KEYS * 2 + 1
6
+
7
+ class DataPointer {
8
+ constructor(context, core, seq, offset, changed, key, value) {
9
+ this.context = context
10
+
11
+ this.core = core
12
+ this.seq = seq
13
+ this.offset = offset
14
+ this.changed = changed
15
+
16
+ this.key = key
17
+ this.value = value
18
+ }
19
+ }
20
+
21
+ class TreeNodePointer {
22
+ constructor(context, core, seq, offset, changed, value) {
23
+ this.context = context
24
+
25
+ this.core = core
26
+ this.seq = seq
27
+ this.offset = offset
28
+ this.changed = changed
29
+
30
+ this.value = value
31
+
32
+ this.next = null
33
+ this.prev = null
34
+ }
35
+ }
36
+
37
+ class TreeNode {
38
+ constructor(keys, children) {
39
+ this.keys = keys
40
+ this.children = children
41
+ }
42
+
43
+ put(context, key, value, child) {
44
+ let s = 0
45
+ let e = this.keys.length
46
+ let c
47
+
48
+ while (s < e) {
49
+ const mid = (s + e) >> 1
50
+ const k = this.keys[mid]
51
+
52
+ c = b4a.compare(key, k.key)
53
+
54
+ if (c === 0) {
55
+ this.keys[mid] = new DataPointer(context, 0, 0, 0, true, key, value)
56
+ return true
57
+ }
58
+
59
+ if (c < 0) e = mid
60
+ else s = mid + 1
61
+ }
62
+
63
+ const i = c < 0 ? e : s
64
+ this.keys.splice(i, 0, new DataPointer(context, 0, 0, 0, true, key, value))
65
+ if (child) this.children.splice(i + 1, 0, child)
66
+
67
+ return this.keys.length < MAX_CHILDREN
68
+ }
69
+
70
+ setValue(context, i, value) {
71
+ this.keys[i] = new DataPointer(context, 0, 0, 0, true, this.keys[i].key, value)
72
+ }
73
+
74
+ removeKey(i) {
75
+ this.keys.splice(i, 1)
76
+ if (this.children.length) {
77
+ this.children.splice(i + 1, 1)
78
+ }
79
+ }
80
+
81
+ siblings(parent) {
82
+ for (let i = 0; i < parent.children.length; i++) {
83
+ if (parent.children[i].value !== this) continue // TODO: move to a seq/offset check instead
84
+
85
+ const left = i ? parent.children[i - 1] : null
86
+ const right = i < parent.children.length - 1 ? parent.children[i + 1] : null
87
+ return { left, index: i, right }
88
+ }
89
+
90
+ // TODO: assert
91
+ throw new Error('Bad parent')
92
+ }
93
+
94
+ merge(node, median) {
95
+ this.keys.push(median)
96
+ for (let i = 0; i < node.keys.length; i++) this.keys.push(node.keys[i])
97
+ for (let i = 0; i < node.children.length; i++) this.children.push(node.children[i])
98
+ }
99
+
100
+ split(context) {
101
+ const len = this.keys.length >> 1
102
+ const right = new TreeNodePointer(context, 0, 0, 0, true, new TreeNode([], []))
103
+
104
+ while (right.value.keys.length < len) right.value.keys.push(this.keys.pop())
105
+ right.value.keys.reverse()
106
+
107
+ const median = this.keys.pop()
108
+
109
+ if (this.children.length) {
110
+ while (right.value.children.length < len + 1) right.value.children.push(this.children.pop())
111
+ right.value.children.reverse()
112
+ }
113
+
114
+ return {
115
+ left: this,
116
+ median,
117
+ right
118
+ }
119
+ }
120
+ }
121
+
122
+ exports.T = T
123
+ exports.MIN_KEYS = MIN_KEYS
124
+ exports.MAX_CHILDREN = MAX_CHILDREN
125
+
126
+ exports.TreeNodePointer = TreeNodePointer
127
+ exports.TreeNode = TreeNode
128
+ exports.DataPointer = DataPointer
129
+
130
+ exports.EMPTY = new TreeNodePointer(null, 0, 0, 0, false, null)
package/lib/write.js ADDED
@@ -0,0 +1,409 @@
1
+ const b4a = require('b4a')
2
+ const c = require('compact-encoding')
3
+ const { TreeNode, TreeNodePointer, MIN_KEYS } = require('./tree.js')
4
+ const { Block } = require('./encoding.js')
5
+
6
+ module.exports = class WriteBatch {
7
+ constructor(tree, { length = -1, key = null, autoUpdate = true } = {}) {
8
+ this.tree = tree
9
+ this.snapshot = tree.snapshot()
10
+ this.autoUpdate = autoUpdate
11
+ this.length = length
12
+ this.key = key
13
+ this.closed = false
14
+ this.flushing = false
15
+ this.root = null
16
+ this.ops = []
17
+ }
18
+
19
+ tryPut(key, value) {
20
+ this.ops.push({ put: true, key, value })
21
+ }
22
+
23
+ tryDelete(key) {
24
+ this.ops.push({ put: false, key, value: null })
25
+ }
26
+
27
+ tryClear() {
28
+ this.ops = []
29
+ this.length = 0
30
+ }
31
+
32
+ async flush() {
33
+ if (this.flushing) throw new Error('Already flushed')
34
+ this.flushing = true
35
+
36
+ const ops = this.ops
37
+ this.ops = []
38
+
39
+ await this.tree.ready()
40
+
41
+ const length = this.length === -1 ? this.tree.core.length : this.length
42
+
43
+ const changed = length === 0
44
+ const seq = length === 0 ? 0 : length - 1
45
+
46
+ const context = this.key ? this.tree.context.getContextByKey(this.key) : this.tree.context
47
+
48
+ this.length = length
49
+ this.root = new TreeNodePointer(
50
+ context,
51
+ 0,
52
+ seq,
53
+ 0,
54
+ changed,
55
+ changed ? new TreeNode([], []) : null
56
+ )
57
+
58
+ for (const op of ops) {
59
+ if (op.put) await this._put(op.key, op.value)
60
+ else await this._delete(op.key)
61
+ }
62
+
63
+ await this._flush()
64
+ await this.snapshot.close()
65
+
66
+ if (this.autoUpdate) {
67
+ this.tree.update(this.root)
68
+ }
69
+ }
70
+
71
+ close() {
72
+ this.closed = true
73
+ return this.snapshot.close()
74
+ }
75
+
76
+ async _put(key, value) {
77
+ const stack = []
78
+ const target = key
79
+
80
+ let ptr = this.root
81
+
82
+ while (true) {
83
+ const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
84
+ if (!v.children.length) break
85
+
86
+ stack.push(ptr)
87
+
88
+ let s = 0
89
+ let e = v.keys.length
90
+ let c = 0
91
+
92
+ while (s < e) {
93
+ const mid = (s + e) >> 1
94
+ const m = v.keys[mid]
95
+
96
+ c = b4a.compare(target, m.key)
97
+
98
+ if (c === 0) {
99
+ if (b4a.compare(m.value, value)) return
100
+ v.setValue(this.tree.context, mid, value)
101
+ for (let i = 0; i < stack.length; i++) stack[i].changed = true
102
+ return
103
+ }
104
+
105
+ if (c < 0) e = mid
106
+ else s = mid + 1
107
+ }
108
+
109
+ const i = c < 0 ? e : s
110
+ ptr = v.children[i]
111
+ }
112
+
113
+ const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
114
+ let needsSplit = !v.put(this.tree.context, target, value, null)
115
+
116
+ ptr.changed = true
117
+
118
+ for (let i = 0; i < stack.length; i++) stack[i].changed = true
119
+
120
+ while (needsSplit) {
121
+ const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
122
+ const parent = stack.pop()
123
+ const { median, right } = v.split(this.tree.context)
124
+
125
+ if (parent) {
126
+ const p = parent.value ? this.snapshot.bump(parent) : await this.snapshot.inflate(parent)
127
+ needsSplit = !p.put(this.tree.context, median.key, median.value, right)
128
+ ptr = parent
129
+ } else {
130
+ this.root = new TreeNodePointer(this.tree.context, 0, 0, 0, true, new TreeNode([], []))
131
+ this.root.value.keys.push(median)
132
+ this.root.value.children.push(ptr, right)
133
+ this.snapshot.bump(this.root)
134
+ needsSplit = false
135
+ }
136
+ }
137
+ }
138
+
139
+ async _delete(key) {
140
+ let ptr = this.root
141
+
142
+ const stack = []
143
+
144
+ while (true) {
145
+ const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
146
+ stack.push(ptr)
147
+
148
+ let s = 0
149
+ let e = v.keys.length
150
+ let c = 0
151
+
152
+ while (s < e) {
153
+ const mid = (s + e) >> 1
154
+ c = b4a.compare(key, v.keys[mid].key)
155
+
156
+ if (c === 0) {
157
+ if (v.children.length) await this._setKeyToNearestLeaf(v, mid, stack)
158
+ else v.removeKey(mid)
159
+
160
+ // we mark these as changed late, so we don't rewrite them if it is a 404
161
+ for (let i = 0; i < stack.length; i++) stack[i].changed = true
162
+ this.root = await this._rebalance(stack)
163
+ return
164
+ }
165
+
166
+ if (c < 0) e = mid
167
+ else s = mid + 1
168
+ }
169
+
170
+ if (!v.children.length) return
171
+
172
+ const i = c < 0 ? e : s
173
+ ptr = v.children[i]
174
+ }
175
+ }
176
+
177
+ async _setKeyToNearestLeaf(v, index, stack) {
178
+ let left = v.children[index]
179
+ let right = v.children[index + 1]
180
+
181
+ const [ls, rs] = await Promise.all([this._leafSize(left, false), this._leafSize(right, true)])
182
+
183
+ if (ls < rs) {
184
+ // if fewer leaves on the left
185
+ stack.push(right)
186
+ let r = right.value ? this.snapshot.bump(right) : await this.snapshot.inflate(right)
187
+ while (r.children.length) {
188
+ right = r.children[0]
189
+ stack.push(right)
190
+ r = right.value ? this.snapshot.bump(right) : await this.snapshot.inflate(right)
191
+ }
192
+ v.keys[index] = r.keys.shift()
193
+ } else {
194
+ // if fewer leaves on the right
195
+ stack.push(left)
196
+ let l = left.value ? this.snapshot.bump(left) : await this.snapshot.inflate(left)
197
+ while (l.children.length) {
198
+ left = l.children[l.children.length - 1]
199
+ stack.push(left)
200
+ l = left.value ? this.snapshot.bump(left) : await this.snapshot.inflate(left)
201
+ }
202
+ v.keys[index] = l.keys.pop()
203
+ }
204
+ }
205
+
206
+ async _leafSize(ptr, goLeft) {
207
+ let v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
208
+ while (v.children.length) {
209
+ ptr = v.children[goLeft ? 0 : v.children.length - 1]
210
+ v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
211
+ }
212
+ return v.keys.length
213
+ }
214
+
215
+ async _rebalance(stack) {
216
+ const root = stack[0]
217
+
218
+ while (stack.length > 1) {
219
+ const ptr = stack.pop()
220
+ const parent = stack[stack.length - 1]
221
+
222
+ const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
223
+
224
+ if (v.keys.length >= MIN_KEYS) return root
225
+
226
+ const p = parent.value ? this.snapshot.bump(parent) : await this.snapshot.inflate(parent)
227
+
228
+ let { left, index, right } = v.siblings(p)
229
+
230
+ let l = left && (left.value ? this.snapshot.bump(left) : await this.snapshot.inflate(left))
231
+
232
+ // maybe borrow from left sibling?
233
+ if (l && l.keys.length > MIN_KEYS) {
234
+ left.changed = true
235
+ v.keys.unshift(p.keys[index - 1])
236
+ if (l.children.length) v.children.unshift(l.children.pop())
237
+ p.keys[index - 1] = l.keys.pop()
238
+ return root
239
+ }
240
+
241
+ let r =
242
+ right && (right.value ? this.snapshot.bump(right) : await this.snapshot.inflate(right))
243
+
244
+ // maybe borrow from right sibling?
245
+ if (r && r.keys.length > MIN_KEYS) {
246
+ right.changed = true
247
+ v.keys.push(p.keys[index])
248
+ if (r.children.length) v.children.push(r.children.shift())
249
+ p.keys[index] = r.keys.shift()
250
+ return root
251
+ }
252
+
253
+ // merge node with another sibling
254
+ if (l) {
255
+ index--
256
+ r = v
257
+ right = ptr
258
+ } else {
259
+ l = v
260
+ left = ptr
261
+ }
262
+
263
+ left.changed = true
264
+ l.merge(r, p.keys[index])
265
+
266
+ parent.changed = true
267
+ p.removeKey(index)
268
+ }
269
+
270
+ const r = root.value ? this.snapshot.bump(root) : await this.snapshot.inflate(root)
271
+ // check if the tree shrunk
272
+ if (!r.keys.length && r.children.length) return r.children[0]
273
+ return root
274
+ }
275
+
276
+ async _flush() {
277
+ if (!this.root || !this.root.changed) return
278
+
279
+ const update = { node: [], keys: [] }
280
+ const batch = [update]
281
+ const stack = [{ update, node: this.root }]
282
+
283
+ await this.tree.context.update(this.tree.activeRequests)
284
+
285
+ while (stack.length > 0) {
286
+ const { update, node } = stack.pop()
287
+
288
+ node.changed = false
289
+ update.node.push(node)
290
+
291
+ for (let i = 0; i < node.value.keys.length; i++) {
292
+ const k = node.value.keys[i]
293
+
294
+ if (!k.changed) {
295
+ k.core = await this.tree.context.getCoreOffset(
296
+ k.context,
297
+ k.core,
298
+ this.tree.activeRequests
299
+ )
300
+ k.context = this.tree.context
301
+ continue
302
+ }
303
+
304
+ k.changed = false
305
+ update.keys.push(k)
306
+ }
307
+
308
+ let first = true
309
+
310
+ for (let i = 0; i < node.value.children.length; i++) {
311
+ const n = node.value.children[i]
312
+
313
+ if (!n.changed) {
314
+ n.core = await this.tree.context.getCoreOffset(
315
+ n.context,
316
+ n.core,
317
+ this.tree.activeRequests
318
+ )
319
+ n.context = this.tree.context
320
+ continue
321
+ }
322
+
323
+ if (first) {
324
+ stack.push({ update, node: n })
325
+ first = false
326
+ } else {
327
+ const update = { node: [], keys: [] }
328
+ batch.push(update)
329
+ stack.push({ update, node: n })
330
+ }
331
+ }
332
+ }
333
+
334
+ const length = this.tree.core.length
335
+ const blocks = new Array(batch.length)
336
+
337
+ for (let i = 0; i < batch.length; i++) {
338
+ const update = batch[i]
339
+ const seq = length + batch.length - i - 1
340
+
341
+ const block = {
342
+ type: 0,
343
+ checkpoint: 0,
344
+ batch: { start: batch.length - 1 - i, end: i },
345
+ previous: null,
346
+ tree: null,
347
+ data: null,
348
+ cores: null
349
+ }
350
+
351
+ for (const k of update.keys) {
352
+ if (block.data === null) block.data = []
353
+
354
+ k.core = 0
355
+ k.context = this.tree.context
356
+ k.seq = seq
357
+ k.offset = block.data.length
358
+ block.data.push(k)
359
+ }
360
+
361
+ for (const n of update.node) {
362
+ if (block.tree === null) block.tree = []
363
+
364
+ n.core = 0
365
+ n.context = this.tree.context
366
+ n.seq = seq
367
+ n.offset = block.tree.length
368
+ block.tree.push(n.value)
369
+ }
370
+
371
+ blocks[seq - length] = block
372
+ }
373
+
374
+ const buffers = new Array(blocks.length)
375
+
376
+ if (blocks.length > 0 && this.length > 0) {
377
+ const core = this.key
378
+ ? await this.tree.context.getCoreOffsetByKey(this.key, this.tree.activeRequests)
379
+ : 0
380
+ blocks[blocks.length - 1].previous = { core, seq: this.length - 1 }
381
+ }
382
+
383
+ // TODO: make this transaction safe
384
+ if (this.tree.context.changed) {
385
+ this.tree.context.changed = false
386
+ this.tree.context.checkpoint = this.tree.core.length + blocks.length
387
+ blocks[blocks.length - 1].cores = this.tree.context.cores
388
+ }
389
+
390
+ for (let i = 0; i < blocks.length; i++) {
391
+ blocks[i].checkpoint = this.tree.context.checkpoint
392
+ buffers[i] = c.encode(Block, blocks[i])
393
+ }
394
+
395
+ if (this.closed) {
396
+ throw new Error('Write batch is closed')
397
+ }
398
+
399
+ await this.tree.core.append(buffers)
400
+
401
+ for (let i = 0; i < batch.length; i++) {
402
+ const update = batch[i]
403
+
404
+ for (let j = 0; j < update.node.length; j++) {
405
+ this.snapshot.bump(update.node)
406
+ }
407
+ }
408
+ }
409
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "hyperbee2",
3
+ "version": "0.0.0",
4
+ "description": "btree",
5
+ "main": "index.js",
6
+ "files": [
7
+ "index.js",
8
+ "lib/*.js",
9
+ "spec"
10
+ ],
11
+ "scripts": {
12
+ "format": "prettier . --write",
13
+ "test": "prettier . --check && node test/all.js",
14
+ "test:bare": "bare test/all.js",
15
+ "test:generate": "brittle -r test/all.js test/*.js"
16
+ },
17
+ "dependencies": {
18
+ "b4a": "^1.6.7",
19
+ "compact-encoding": "^2.16.1",
20
+ "hypercore": "^11.15.0",
21
+ "hyperschema": "^1.14.0",
22
+ "protocol-buffers-encodings": "^1.2.0",
23
+ "streamx": "^2.22.1"
24
+ },
25
+ "devDependencies": {
26
+ "brittle": "^3.18.0",
27
+ "corestore": "^7.4.5",
28
+ "prettier": "^3.6.2",
29
+ "prettier-config-holepunch": "^2.0.0"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/mafintosh/hyperbee2.git"
34
+ },
35
+ "author": "Mathias Buus (@mafintosh)",
36
+ "license": "Apache-2.0",
37
+ "bugs": {
38
+ "url": "https://github.com/mafintosh/hyperbee2/issues"
39
+ },
40
+ "homepage": "https://github.com/mafintosh/hyperbee2"
41
+ }