hyperbee2 1.2.0 → 2.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/lib/context.js CHANGED
@@ -3,10 +3,13 @@ const ScopeLock = require('scope-lock')
3
3
  const { decodeBlock } = require('./encoding.js')
4
4
 
5
5
  class CoreContext {
6
- constructor(store, local, core, encryption, lock = new ScopeLock(), other = new Map()) {
6
+ constructor(store, local, core, encryption, t, lock = new ScopeLock(), other = new Map()) {
7
7
  this.store = store
8
8
  this.local = local
9
9
  this.core = core
10
+ this.t = t
11
+ this.minKeys = t - 1
12
+ this.maxKeys = 2 * t - 1
10
13
  this.encryption = encryption
11
14
  this.lock = lock
12
15
  this.other = other
@@ -17,6 +20,11 @@ class CoreContext {
17
20
  this.changed = false
18
21
  }
19
22
 
23
+ // TODO: remove, left here for easier debugging for now
24
+ // [Symbol.for('nodejs.util.inspect.custom')]() {
25
+ // return '[CoreContext]'
26
+ // }
27
+
20
28
  async update(activeRequests) {
21
29
  await this.core.ready()
22
30
 
@@ -34,7 +42,7 @@ class CoreContext {
34
42
 
35
43
  if (length < this.length) return
36
44
 
37
- this.cores = block.cores || []
45
+ this.cores = block.metadata ? block.metadata.cores : []
38
46
  while (this.opened.length < this.cores.length) this.opened.push(null)
39
47
  }
40
48
 
@@ -44,8 +52,9 @@ class CoreContext {
44
52
  this.length = length
45
53
  }
46
54
 
47
- async getCoreOffset(context, core, activeRequests) {
48
- if (core !== 0 && core - 1 >= context.cores.length) await context.update(activeRequests)
55
+ getCoreOffsetLocal(context, core) {
56
+ if (context === this) return core
57
+
49
58
  const key = core === 0 ? context.core.key : context.cores[core - 1].key
50
59
 
51
60
  if (b4a.equals(key, this.core.key)) return 0
@@ -65,6 +74,11 @@ class CoreContext {
65
74
  return this.cores.length
66
75
  }
67
76
 
77
+ async getCoreOffset(context, core, activeRequests) {
78
+ if (core !== 0 && core - 1 >= context.cores.length) await context.update(activeRequests)
79
+ return this.getCoreOffsetLocal(context, core)
80
+ }
81
+
68
82
  async getCoreOffsetByKey(key, activeRequests) {
69
83
  await this.core.ready()
70
84
 
@@ -112,7 +126,7 @@ class CoreContext {
112
126
  }
113
127
 
114
128
  async getBlock(seq, core, activeRequests) {
115
- if (core !== 0) await this.update(activeRequests)
129
+ if (core !== 0 && core - 1 >= this.cores.length) await this.update(activeRequests)
116
130
  const hc = this.getCore(core)
117
131
  const buffer = await hc.get(seq, { activeRequests })
118
132
  const block = decodeBlock(buffer, seq)
@@ -129,8 +143,7 @@ class CoreContext {
129
143
  const hex = b4a.toString(key, 'hex')
130
144
  if (this.other.has(hex)) return this.other.get(hex)
131
145
 
132
- const hc = this.store.get({ key, encryption: this.encryption })
133
- const ctx = new CoreContext(this.store, this.local, hc, this.encryption, this.lock, this.other)
146
+ const ctx = this._createContext(this.store.get({ key, encryption: this.encryption }))
134
147
  this.other.set(hex, ctx)
135
148
  return ctx
136
149
  }
@@ -143,11 +156,27 @@ class CoreContext {
143
156
  const hex = b4a.toString(this.cores[core - 1].key, 'hex')
144
157
  if (this.other.has(hex)) return this.other.get(hex)
145
158
 
146
- const hc = this.getCore(core)
147
- const ctx = new CoreContext(this.store, this.local, hc, this.encryption, this.lock, this.other)
159
+ const ctx = this._createContext(this.getCore(core))
148
160
  this.other.set(hex, ctx)
149
161
  return ctx
150
162
  }
163
+
164
+ flush() {
165
+ this.changed = false
166
+ return {
167
+ cores: this.cores
168
+ }
169
+ }
170
+
171
+ _createContext(core) {
172
+ const store = this.store
173
+ const local = this.local
174
+ const encryption = this.encryption
175
+ const t = this.t
176
+ const lock = this.lock
177
+ const other = this.other
178
+ return new CoreContext(store, local, core, encryption, t, lock, other)
179
+ }
151
180
  }
152
181
 
153
182
  module.exports = CoreContext
package/lib/diff.js CHANGED
@@ -3,11 +3,15 @@ const b4a = require('b4a')
3
3
  const { RangeIterator } = require('./ranges.js')
4
4
 
5
5
  class DiffIterator {
6
- constructor(left, right, { limit = -1 } = {}) {
6
+ constructor(tree, left, right, { limit = -1, activeRequests = [] } = {}) {
7
+ this.tree = tree
8
+
7
9
  this.left = left
8
10
  this.right = right
9
11
 
10
12
  this.left.limit = this.right.limit = -1
13
+
14
+ this.activeRequests = activeRequests
11
15
  this.limit = limit
12
16
 
13
17
  this.nextLeft = null
@@ -18,15 +22,15 @@ class DiffIterator {
18
22
  await Promise.all([this.left.open(), this.right.open()])
19
23
  }
20
24
 
21
- _shiftRight() {
22
- const right = this.nextRight
25
+ async _shiftRight() {
26
+ const right = await this.tree.finalizeKeyPointer(this.nextRight, this.activeRequests)
23
27
  this.nextRight = null
24
28
  if (this.limit > 0) this.limit--
25
29
  return { left: null, right }
26
30
  }
27
31
 
28
- _shiftLeft() {
29
- const left = this.nextLeft
32
+ async _shiftLeft() {
33
+ const left = await this.tree.finalizeKeyPointer(this.nextLeft, this.activeRequests)
30
34
  this.nextLeft = null
31
35
  if (this.limit > 0) this.limit--
32
36
  return { left, right: null }
@@ -47,8 +51,8 @@ class DiffIterator {
47
51
 
48
52
  async next() {
49
53
  while (this.limit === -1 || this.limit > 0) {
50
- const leftPromise = this.nextLeft ? null : this.left.next()
51
- const rightPromise = this.nextRight ? null : this.right.next()
54
+ const leftPromise = this.nextLeft ? null : this.left.nextKey()
55
+ const rightPromise = this.nextRight ? null : this.right.nextKey()
52
56
 
53
57
  const [l, r] = await Promise.all([leftPromise, rightPromise])
54
58
 
@@ -62,16 +66,21 @@ class DiffIterator {
62
66
  const cmp = b4a.compare(this.nextLeft.key, this.nextRight.key)
63
67
 
64
68
  if (cmp === 0) {
65
- const left = this.nextLeft
66
- const right = this.nextRight
69
+ const leftKey = this.nextLeft
70
+ const rightKey = this.nextRight
67
71
 
68
72
  this.nextRight = this.nextLeft = null
69
73
 
70
- if (isSame(left, right)) {
74
+ if (isSame(leftKey, rightKey)) {
71
75
  this._fastForward()
72
76
  continue
73
77
  }
74
78
 
79
+ const [left, right] = await Promise.all([
80
+ this.tree.finalizeKeyPointer(leftKey, this.activeRequests),
81
+ this.tree.finalizeKeyPointer(rightKey, this.activeRequests)
82
+ ])
83
+
75
84
  if (this.limit > 0) this.limit--
76
85
  return { left, right }
77
86
  }
@@ -92,6 +101,7 @@ class DiffStream extends Readable {
92
101
  this.left = left
93
102
  this.right = right
94
103
  this.iterator = new DiffIterator(
104
+ left,
95
105
  new RangeIterator(left, options),
96
106
  new RangeIterator(right, options),
97
107
  options
package/lib/encoding.js CHANGED
@@ -1,19 +1,144 @@
1
1
  const c = require('compact-encoding')
2
2
  const { getEncoding } = require('../spec/hyperschema')
3
+ const { OP_COHORT, OP_INSERT } = require('./compression.js')
3
4
  const compat = require('./compat.js')
4
5
 
5
- const Block = getEncoding('@bee/block')
6
+ const Block0 = getEncoding('@bee/block-0')
7
+ const Block1 = getEncoding('@bee/block-1')
6
8
 
7
- exports.Block = Block
9
+ const TYPE_COMPAT = 10 // compat with bee1, all blocks starts with 0x10
10
+
11
+ exports.TYPE_COMPAT = TYPE_COMPAT
12
+ exports.TYPE_LATEST = 1
13
+
14
+ exports.Block = Block1
8
15
  exports.encodeBlock = encodeBlock
9
16
  exports.decodeBlock = decodeBlock
10
17
 
11
- function encodeBlock(block, format) {
12
- return format === 2 ? compat.encode(block) : c.encode(Block, block)
18
+ function encodeBlock(block) {
19
+ if (block.type === 1) {
20
+ return c.encode(Block1, block)
21
+ }
22
+
23
+ if (block.type === 0) {
24
+ return encodeBlock0(block)
25
+ }
26
+
27
+ if (block.type === TYPE_COMPAT) {
28
+ return compat.encode(block)
29
+ }
30
+
31
+ throw new Error('Unknown block type: ' + block.type)
13
32
  }
14
33
 
15
34
  function decodeBlock(buffer, seq) {
16
- const isCompat = buffer.length > 0 && buffer[0] === 0x0a
17
- const block = isCompat ? compat.decode(buffer, seq) : c.decode(Block, buffer)
18
- return block
35
+ const state = { start: 0, end: buffer.byteLength, buffer }
36
+ const type = state.end > 0 ? c.uint.decode(state) : 0
37
+
38
+ state.start = 0
39
+
40
+ if (type === 1) {
41
+ return Block1.decode(state)
42
+ }
43
+
44
+ if (type === 0) {
45
+ return decodeBlock0(state, seq)
46
+ }
47
+
48
+ if (type === TYPE_COMPAT) {
49
+ return compat.decode(buffer, seq)
50
+ }
51
+
52
+ throw new Error('Unknown block type: ' + type)
53
+ }
54
+
55
+ function dataToKey(d) {
56
+ return { ...d, valuePointer: null }
57
+ }
58
+
59
+ function keyToData(d) {
60
+ return { key: d.key, value: d.value }
61
+ }
62
+
63
+ function decodeBlock0(state, seq) {
64
+ const cohorts = []
65
+ const blk = Block0.decode(state)
66
+ const tree = []
67
+
68
+ for (const t of blk.tree) {
69
+ const next = {
70
+ keys: toCohort(seq, t.keys, cohorts),
71
+ children: toCohort(seq, t.children, cohorts)
72
+ }
73
+
74
+ tree.push(next)
75
+ }
76
+
77
+ return {
78
+ type: 0,
79
+ checkpoint: blk.checkpoint,
80
+ batch: blk.batch,
81
+ previous: blk.previous,
82
+ metadata: { cores: blk.cores },
83
+ tree,
84
+ keys: blk.data.map(dataToKey),
85
+ values: null,
86
+ cohorts
87
+ }
88
+ }
89
+
90
+ function encodeBlock0(blk) {
91
+ const tree = []
92
+
93
+ for (const t of blk.tree) {
94
+ tree.push({
95
+ keys: fromCohort(t.keys, blk.cohorts),
96
+ children: fromCohort(t.children, blk.cohorts)
97
+ })
98
+ }
99
+
100
+ return c.encode(Block0, {
101
+ type: 0,
102
+ checkpoint: blk.checkpoint,
103
+ batch: blk.batch,
104
+ previous: blk.previous,
105
+ tree,
106
+ data: blk.keys.map(keyToData),
107
+ cores: blk.metadata ? blk.metadata.cores : null
108
+ })
109
+ }
110
+
111
+ function fromCohort(deltas, cohorts) {
112
+ if (deltas.length === 0) return []
113
+ const cohort = cohorts[deltas[0].pointer.offset]
114
+ const pointers = []
115
+ for (const d of cohort) pointers.push(d.pointer)
116
+ return pointers
117
+ }
118
+
119
+ function toCohort(seq, pointers, cohorts) {
120
+ if (!pointers.length) return []
121
+
122
+ const cohort = []
123
+ const offset = cohorts.push(cohort) - 1
124
+
125
+ for (let i = 0; i < pointers.length; i++) {
126
+ cohort.push({
127
+ type: OP_INSERT,
128
+ index: i,
129
+ pointer: pointers[i]
130
+ })
131
+ }
132
+
133
+ return [
134
+ {
135
+ type: OP_COHORT,
136
+ index: 0,
137
+ pointer: {
138
+ core: 0,
139
+ seq,
140
+ offset
141
+ }
142
+ }
143
+ ]
19
144
  }
package/lib/ranges.js CHANGED
@@ -37,8 +37,8 @@ class RangeIterator {
37
37
  const j = this.reverse ? v.keys.length - 1 - i : i
38
38
 
39
39
  const c = this.reverse
40
- ? b4a.compare(v.keys[j].key, this.end)
41
- : b4a.compare(this.start, v.keys[j].key)
40
+ ? b4a.compare(v.keys.get(j).key, this.end)
41
+ : b4a.compare(this.start, v.keys.get(j).key)
42
42
 
43
43
  if (c < 0) break
44
44
  top.offset = 2 * i + 1 + (c === 0 ? offset : 1)
@@ -52,7 +52,7 @@ class RangeIterator {
52
52
  const j = this.reverse ? v.children.length - 1 - k : k
53
53
 
54
54
  this.stack.push({
55
- node: v.children[j],
55
+ node: v.children.get(j),
56
56
  offset: 0
57
57
  })
58
58
 
@@ -61,6 +61,11 @@ class RangeIterator {
61
61
  }
62
62
 
63
63
  async next() {
64
+ const key = await this.nextKey()
65
+ return key === null ? key : this.tree.finalizeKeyPointer(key, this.activeRequests)
66
+ }
67
+
68
+ async nextKey() {
64
69
  while (this.stack.length && (this.limit === -1 || this.limit > 0)) {
65
70
  const top = this.stack.pop()
66
71
  const v = top.node.value
@@ -75,7 +80,7 @@ class RangeIterator {
75
80
  this.stack.push(top)
76
81
  if (k < v.children.length) {
77
82
  const j = this.reverse ? v.children.length - 1 - k : k
78
- this.stack.push({ node: v.children[j], offset: 0 })
83
+ this.stack.push({ node: v.children.get(j), offset: 0 })
79
84
  }
80
85
  continue
81
86
  }
@@ -83,7 +88,7 @@ class RangeIterator {
83
88
  if (k < v.keys.length) {
84
89
  const j = this.reverse ? v.keys.length - 1 - k : k
85
90
 
86
- const data = top.node.value.keys[j]
91
+ const data = top.node.value.keys.get(j)
87
92
 
88
93
  const c = this.reverse
89
94
  ? this.start
package/lib/tree.js CHANGED
@@ -1,29 +1,22 @@
1
1
  const b4a = require('b4a')
2
+ const { CompressedArray } = require('./compression.js')
2
3
 
3
- const T = 5
4
- const MIN_KEYS = T - 1
5
- const MAX_CHILDREN = MIN_KEYS * 2 + 1
4
+ const INSERTED = -1
5
+ const NEEDS_SPLIT = -2
6
6
 
7
- const UNCHANGED = 0
8
- const CHANGED = 1
9
- const NEEDS_SPLIT = 2
10
-
11
- class DataPointer {
12
- constructor(context, core, seq, offset, changed, key, value) {
7
+ class ValuePointer {
8
+ constructor(context, core, seq, offset, split) {
13
9
  this.context = context
14
10
 
15
11
  this.core = core
16
12
  this.seq = seq
17
13
  this.offset = offset
18
- this.changed = changed
19
-
20
- this.key = key
21
- this.value = value
14
+ this.split = split
22
15
  }
23
16
  }
24
17
 
25
- class TreeNodePointer {
26
- constructor(context, core, seq, offset, changed, value) {
18
+ class Pointer {
19
+ constructor(context, core, seq, offset, changed) {
27
20
  this.context = context
28
21
 
29
22
  this.core = core
@@ -31,68 +24,121 @@ class TreeNodePointer {
31
24
  this.offset = offset
32
25
  this.changed = changed
33
26
 
27
+ this.changedBy = null
28
+ }
29
+ }
30
+
31
+ class KeyPointer extends Pointer {
32
+ constructor(context, core, seq, offset, changed, key, value, valuePointer) {
33
+ super(context, core, seq, offset, changed)
34
+
35
+ this.key = key
36
+ this.value = value
37
+ this.valuePointer = valuePointer
38
+ }
39
+
40
+ // TODO: remove, left here for easier debugging for now
41
+ // [Symbol.for('nodejs.util.inspect.custom')]() {
42
+ // return (
43
+ // `[KeyPointer core=${this.core} seq=${this.seq}, offset=${this.offset}, key="${b4a.toString(this.key)}"]`
44
+ // )
45
+ // }
46
+ }
47
+
48
+ class TreeNodePointer extends Pointer {
49
+ constructor(context, core, seq, offset, changed, value) {
50
+ super(context, core, seq, offset, changed)
51
+
34
52
  this.value = value
35
53
 
36
54
  this.next = null
37
55
  this.prev = null
38
56
  }
57
+
58
+ // TODO: remove, left here for easier debugging for now
59
+ // [Symbol.for('nodejs.util.inspect.custom')]() {
60
+ // return (
61
+ // `[TreeNodePointer core=${this.core} seq=${this.seq} offset=${this.offset} changed=${this.changed}]`
62
+ // )
63
+ // }
39
64
  }
40
65
 
41
66
  class TreeNode {
42
67
  constructor(keys, children) {
43
- this.keys = keys
44
- this.children = children
68
+ this.keys = new CompressedArray(keys)
69
+ this.children = new CompressedArray(children)
45
70
  }
46
71
 
47
72
  isEmpty() {
48
73
  return this.keys.length === 0 && this.children.length === 0
49
74
  }
50
75
 
51
- put(context, key, value, child) {
76
+ insertLeaf(context, key, value) {
52
77
  let s = 0
53
78
  let e = this.keys.length
54
- let c
79
+ let c = 0
55
80
 
56
81
  while (s < e) {
57
82
  const mid = (s + e) >> 1
58
- const k = this.keys[mid]
83
+ const k = this.keys.get(mid)
59
84
 
60
85
  c = b4a.compare(key, k.key)
61
86
 
62
- if (c === 0) {
63
- if (b4a.equals(k.value, value)) return UNCHANGED
64
- this.keys[mid] = new DataPointer(context, 0, 0, 0, true, key, value)
65
- return CHANGED
66
- }
87
+ if (c === 0) return mid
67
88
 
68
89
  if (c < 0) e = mid
69
90
  else s = mid + 1
70
91
  }
71
92
 
72
93
  const i = c < 0 ? e : s
73
- this.keys.splice(i, 0, new DataPointer(context, 0, 0, 0, true, key, value))
74
- if (child) this.children.splice(i + 1, 0, child)
94
+ this.keys.insert(i, new KeyPointer(context, 0, 0, 0, true, key, value, null))
75
95
 
76
- return this.keys.length < MAX_CHILDREN ? CHANGED : NEEDS_SPLIT
96
+ return this.keys.length <= context.maxKeys ? INSERTED : NEEDS_SPLIT
97
+ }
98
+
99
+ insertNode(context, keyPointer, treePointer) {
100
+ let s = 0
101
+ let e = this.keys.length
102
+ let c = 0
103
+
104
+ while (s < e) {
105
+ const mid = (s + e) >> 1
106
+ const k = this.keys.get(mid)
107
+
108
+ c = b4a.compare(keyPointer.key, k.key)
109
+
110
+ if (c === 0) return mid
111
+
112
+ if (c < 0) e = mid
113
+ else s = mid + 1
114
+ }
115
+
116
+ const i = c < 0 ? e : s
117
+ this.keys.insert(i, keyPointer)
118
+ this.children.insert(i + 1, treePointer)
119
+
120
+ return this.keys.length <= context.maxKeys ? INSERTED : NEEDS_SPLIT
77
121
  }
78
122
 
79
123
  setValue(context, i, value) {
80
- this.keys[i] = new DataPointer(context, 0, 0, 0, true, this.keys[i].key, value)
124
+ this.keys.set(i, new KeyPointer(context, 0, 0, 0, true, this.keys.get(i).key, value, null))
81
125
  }
82
126
 
83
127
  removeKey(i) {
84
- this.keys.splice(i, 1)
128
+ this.keys.delete(i)
85
129
  if (this.children.length) {
86
- this.children.splice(i + 1, 1)
130
+ this.children.delete(i + 1)
87
131
  }
88
132
  }
89
133
 
90
134
  siblings(parent) {
91
- for (let i = 0; i < parent.children.length; i++) {
92
- if (parent.children[i].value !== this) continue // TODO: move to a seq/offset check instead
135
+ const pc = parent.children
93
136
 
94
- const left = i ? parent.children[i - 1] : null
95
- const right = i < parent.children.length - 1 ? parent.children[i + 1] : null
137
+ for (let i = 0; i < pc.length; i++) {
138
+ if (pc.get(i).value !== this) continue // TODO: move to a seq/offset check instead
139
+
140
+ const left = i ? pc.get(i - 1) : null
141
+ const right = i < pc.length - 1 ? pc.get(i + 1) : null
96
142
  return { left, index: i, right }
97
143
  }
98
144
 
@@ -101,23 +147,29 @@ class TreeNode {
101
147
  }
102
148
 
103
149
  merge(node, median) {
150
+ const keys = node.keys
151
+ const children = node.children
152
+
104
153
  this.keys.push(median)
105
- for (let i = 0; i < node.keys.length; i++) this.keys.push(node.keys[i])
106
- for (let i = 0; i < node.children.length; i++) this.children.push(node.children[i])
154
+
155
+ for (let i = 0; i < keys.length; i++) this.keys.push(keys.get(i))
156
+ for (let i = 0; i < children.length; i++) this.children.push(children.get(i))
107
157
  }
108
158
 
109
159
  split(context) {
110
160
  const len = this.keys.length >> 1
111
161
  const right = new TreeNodePointer(context, 0, 0, 0, true, new TreeNode([], []))
112
162
 
113
- while (right.value.keys.length < len) right.value.keys.push(this.keys.pop())
114
- right.value.keys.reverse()
163
+ const k = []
164
+ while (k.length < len) k.push(this.keys.pop())
165
+ for (let i = k.length - 1; i >= 0; i--) right.value.keys.push(k[i])
115
166
 
116
167
  const median = this.keys.pop()
117
168
 
118
169
  if (this.children.length) {
119
- while (right.value.children.length < len + 1) right.value.children.push(this.children.pop())
120
- right.value.children.reverse()
170
+ const c = []
171
+ while (c.length < len + 1) c.push(this.children.pop())
172
+ for (let i = c.length - 1; i >= 0; i--) right.value.children.push(c[i])
121
173
  }
122
174
 
123
175
  return {
@@ -128,16 +180,13 @@ class TreeNode {
128
180
  }
129
181
  }
130
182
 
131
- exports.T = T
132
- exports.MIN_KEYS = MIN_KEYS
133
- exports.MAX_CHILDREN = MAX_CHILDREN
134
-
135
- exports.UNCHANGED = UNCHANGED
136
- exports.CHANGED = CHANGED
183
+ exports.INSERTED = INSERTED
137
184
  exports.NEEDS_SPLIT = NEEDS_SPLIT
138
185
 
139
186
  exports.TreeNodePointer = TreeNodePointer
140
187
  exports.TreeNode = TreeNode
141
- exports.DataPointer = DataPointer
188
+ exports.KeyPointer = KeyPointer
189
+ exports.ValuePointer = ValuePointer
190
+ exports.Pointer = Pointer
142
191
 
143
192
  exports.EMPTY = new TreeNodePointer(null, 0, 0, 0, false, null)