hyperbee2 1.1.2 → 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.
@@ -0,0 +1,163 @@
1
+ const OP_SET = 0
2
+ const OP_INSERT = 1
3
+ const OP_DEL = 2
4
+ const OP_COHORT = 3
5
+
6
+ class DeltaOp {
7
+ constructor(changed, type, index, pointer) {
8
+ this.type = type
9
+ this.index = index
10
+ this.pointer = pointer
11
+ this.changed = changed
12
+ }
13
+ }
14
+
15
+ class DeltaCohort extends DeltaOp {
16
+ constructor(changed, pointer, deltas) {
17
+ super(changed, OP_COHORT, 0, pointer)
18
+ this.deltas = deltas
19
+ }
20
+ }
21
+
22
+ class CompressedArray {
23
+ constructor(delta) {
24
+ this.entries = []
25
+ this.delta = delta
26
+
27
+ for (const d of delta) {
28
+ if (d.type === OP_COHORT) {
29
+ for (const dd of d.deltas) {
30
+ apply(this.entries, dd.type, dd.index, dd.pointer)
31
+ }
32
+ } else {
33
+ apply(this.entries, d.type, d.index, d.pointer)
34
+ }
35
+ }
36
+ }
37
+
38
+ get length() {
39
+ return this.entries.length
40
+ }
41
+
42
+ touch(index) {
43
+ const pointer = this.entries[index]
44
+ if (pointer.changedBy === this) return
45
+ this.set(index, pointer)
46
+ }
47
+
48
+ get(index) {
49
+ return this.entries[index]
50
+ }
51
+
52
+ push(pointer) {
53
+ this.insert(this.entries.length, pointer)
54
+ }
55
+
56
+ unshift(pointer) {
57
+ this.insert(0, pointer)
58
+ }
59
+
60
+ pop() {
61
+ if (this.entries.length === 0) return
62
+ const head = this.entries[this.entries.length - 1]
63
+ this.delete(this.entries.length - 1)
64
+ return head
65
+ }
66
+
67
+ shift() {
68
+ if (this.entries.length === 0) return
69
+ const tail = this.entries[0]
70
+ this.delete(0)
71
+ return tail
72
+ }
73
+
74
+ _touch(pointer) {
75
+ if (pointer) pointer.changedBy = this
76
+ }
77
+
78
+ insert(index, pointer) {
79
+ if (!insert(this.entries, index, pointer)) return
80
+ this._touch(pointer)
81
+ this.delta.push(new DeltaOp(true, OP_INSERT, index, pointer))
82
+ }
83
+
84
+ delete(index) {
85
+ if (!del(this.entries, index)) return
86
+ this._touch(null)
87
+ this.delta.push(new DeltaOp(true, OP_DEL, index, null))
88
+ }
89
+
90
+ set(index, pointer) {
91
+ if (!set(this.entries, index, pointer)) return
92
+ this._touch(pointer)
93
+ this.delta.push(new DeltaOp(true, OP_SET, index, pointer))
94
+ }
95
+
96
+ flush(max, min) {
97
+ if (this.delta.length <= max) return this.delta
98
+
99
+ const direct = []
100
+ while (this.delta.length && this.delta[this.delta.length - 1].type !== OP_COHORT) {
101
+ direct.push(this.delta.pop())
102
+ }
103
+ direct.reverse()
104
+
105
+ if (direct.length > min && direct.length < this.entries.length) {
106
+ const co = new DeltaCohort(true, null, [])
107
+ for (const d of direct) {
108
+ co.deltas.push(d)
109
+ }
110
+ this.delta.push(co)
111
+ } else {
112
+ const co = new DeltaCohort(true, null, [])
113
+ for (let i = 0; i < this.entries.length; i++) {
114
+ const d = new DeltaOp(true, OP_INSERT, i, this.entries[i])
115
+ co.deltas.push(d)
116
+ }
117
+ this.delta = [co]
118
+ }
119
+
120
+ return this.delta
121
+ }
122
+ }
123
+
124
+ exports.CompressedArray = CompressedArray
125
+ exports.DeltaOp = DeltaOp
126
+ exports.DeltaCohort = DeltaCohort
127
+
128
+ exports.OP_SET = OP_SET
129
+ exports.OP_INSERT = OP_INSERT
130
+ exports.OP_DEL = OP_DEL
131
+ exports.OP_COHORT = OP_COHORT
132
+
133
+ function del(entries, index) {
134
+ if (index >= entries.length) return false
135
+ entries.splice(index, 1)
136
+ return true
137
+ }
138
+
139
+ function insert(entries, index, pointer) {
140
+ if (index >= entries.length + 1) return false
141
+ entries.splice(index, 0, pointer)
142
+ return true
143
+ }
144
+
145
+ function set(entries, index, pointer) {
146
+ if (index >= entries.length) return false
147
+ // if (entries[index] === pointer) return false
148
+ entries[index] = pointer
149
+ return true
150
+ }
151
+
152
+ function apply(entries, type, index, pointer) {
153
+ if (type === OP_INSERT) {
154
+ return insert(entries, index, pointer)
155
+ }
156
+ if (type === OP_DEL) {
157
+ return del(entries, index)
158
+ }
159
+ if (type === OP_SET) {
160
+ return set(entries, index, pointer)
161
+ }
162
+ return false
163
+ }
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
@@ -60,9 +69,16 @@ class CoreContext {
60
69
 
61
70
  this.changed = true
62
71
  this.cores.push({ key, fork: 0, length: 0, treeHash: null })
72
+ this.opened.push(null)
73
+
63
74
  return this.cores.length
64
75
  }
65
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
+
66
82
  async getCoreOffsetByKey(key, activeRequests) {
67
83
  await this.core.ready()
68
84
 
@@ -86,6 +102,7 @@ class CoreContext {
86
102
 
87
103
  this.changed = true
88
104
  this.cores.push({ key, fork: 0, length: 0, treeHash: null })
105
+ this.opened.push(null)
89
106
  return this.cores.length
90
107
  }
91
108
 
@@ -99,8 +116,17 @@ class CoreContext {
99
116
  return this.opened[index - 1]
100
117
  }
101
118
 
119
+ getCoreKey(index) {
120
+ if (index === 0) return this.core.key
121
+ if (index > this.cores.length) throw new Error('Bad core index: ' + index)
122
+ if (this.opened[index - 1] !== null && this.opened[index - 1].key) {
123
+ return this.opened[index - 1].key
124
+ }
125
+ return this.cores[index - 1].key
126
+ }
127
+
102
128
  async getBlock(seq, core, activeRequests) {
103
- if (core !== 0) await this.update(activeRequests)
129
+ if (core !== 0 && core - 1 >= this.cores.length) await this.update(activeRequests)
104
130
  const hc = this.getCore(core)
105
131
  const buffer = await hc.get(seq, { activeRequests })
106
132
  const block = decodeBlock(buffer, seq)
@@ -117,8 +143,7 @@ class CoreContext {
117
143
  const hex = b4a.toString(key, 'hex')
118
144
  if (this.other.has(hex)) return this.other.get(hex)
119
145
 
120
- const hc = this.store.get({ key, encryption: this.encryption })
121
- 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 }))
122
147
  this.other.set(hex, ctx)
123
148
  return ctx
124
149
  }
@@ -131,11 +156,27 @@ class CoreContext {
131
156
  const hex = b4a.toString(this.cores[core - 1].key, 'hex')
132
157
  if (this.other.has(hex)) return this.other.get(hex)
133
158
 
134
- const hc = this.getCore(core)
135
- const ctx = new CoreContext(this.store, this.local, hc, this.encryption, this.lock, this.other)
159
+ const ctx = this._createContext(this.getCore(core))
136
160
  this.other.set(hex, ctx)
137
161
  return ctx
138
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
+ }
139
180
  }
140
181
 
141
182
  module.exports = CoreContext
package/lib/diff.js ADDED
@@ -0,0 +1,142 @@
1
+ const { Readable } = require('streamx')
2
+ const b4a = require('b4a')
3
+ const { RangeIterator } = require('./ranges.js')
4
+
5
+ class DiffIterator {
6
+ constructor(tree, left, right, { limit = -1, activeRequests = [] } = {}) {
7
+ this.tree = tree
8
+
9
+ this.left = left
10
+ this.right = right
11
+
12
+ this.left.limit = this.right.limit = -1
13
+
14
+ this.activeRequests = activeRequests
15
+ this.limit = limit
16
+
17
+ this.nextLeft = null
18
+ this.nextRight = null
19
+ }
20
+
21
+ async open() {
22
+ await Promise.all([this.left.open(), this.right.open()])
23
+ }
24
+
25
+ async _shiftRight() {
26
+ const right = await this.tree.finalizeKeyPointer(this.nextRight, this.activeRequests)
27
+ this.nextRight = null
28
+ if (this.limit > 0) this.limit--
29
+ return { left: null, right }
30
+ }
31
+
32
+ async _shiftLeft() {
33
+ const left = await this.tree.finalizeKeyPointer(this.nextLeft, this.activeRequests)
34
+ this.nextLeft = null
35
+ if (this.limit > 0) this.limit--
36
+ return { left, right: null }
37
+ }
38
+
39
+ _fastForward() {
40
+ while (this.left.stack.length && this.right.stack.length) {
41
+ const a = this.left.stack[this.left.stack.length - 1]
42
+ const b = this.right.stack[this.right.stack.length - 1]
43
+
44
+ if (a.offset !== b.offset) return
45
+ if (!isSame(a.node, b.node)) return
46
+
47
+ this.left.stack.pop()
48
+ this.right.stack.pop()
49
+ }
50
+ }
51
+
52
+ async next() {
53
+ while (this.limit === -1 || this.limit > 0) {
54
+ const leftPromise = this.nextLeft ? null : this.left.nextKey()
55
+ const rightPromise = this.nextRight ? null : this.right.nextKey()
56
+
57
+ const [l, r] = await Promise.all([leftPromise, rightPromise])
58
+
59
+ if (leftPromise) this.nextLeft = l
60
+ if (rightPromise) this.nextRight = r
61
+
62
+ if (!this.nextLeft && !this.nextRight) return null
63
+ if (!this.nextLeft) return this._shiftRight()
64
+ if (!this.nextRight) return this._shiftLeft()
65
+
66
+ const cmp = b4a.compare(this.nextLeft.key, this.nextRight.key)
67
+
68
+ if (cmp === 0) {
69
+ const leftKey = this.nextLeft
70
+ const rightKey = this.nextRight
71
+
72
+ this.nextRight = this.nextLeft = null
73
+
74
+ if (isSame(leftKey, rightKey)) {
75
+ this._fastForward()
76
+ continue
77
+ }
78
+
79
+ const [left, right] = await Promise.all([
80
+ this.tree.finalizeKeyPointer(leftKey, this.activeRequests),
81
+ this.tree.finalizeKeyPointer(rightKey, this.activeRequests)
82
+ ])
83
+
84
+ if (this.limit > 0) this.limit--
85
+ return { left, right }
86
+ }
87
+
88
+ if (cmp < 0) return this._shiftLeft()
89
+ return this._shiftRight()
90
+ }
91
+
92
+ return null
93
+ }
94
+ }
95
+
96
+ class DiffStream extends Readable {
97
+ constructor(left, right, options = {}) {
98
+ const { highWaterMark } = options
99
+ super({ eagerOpen: true, highWaterMark })
100
+
101
+ this.left = left
102
+ this.right = right
103
+ this.iterator = new DiffIterator(
104
+ left,
105
+ new RangeIterator(left, options),
106
+ new RangeIterator(right, options),
107
+ options
108
+ )
109
+ }
110
+
111
+ async _open(cb) {
112
+ try {
113
+ await this.iterator.open()
114
+ } catch (err) {
115
+ cb(err)
116
+ return
117
+ }
118
+ cb(null)
119
+ }
120
+
121
+ async _read(cb) {
122
+ try {
123
+ this.push(await this.iterator.next())
124
+ } catch (err) {
125
+ cb(err)
126
+ return
127
+ }
128
+ cb(null)
129
+ }
130
+ }
131
+
132
+ exports.DiffIterator = DiffIterator
133
+ exports.DiffStream = DiffStream
134
+
135
+ function isSame(a, b) {
136
+ if (a.seq !== b.seq || a.offset !== b.offset) return false
137
+
138
+ const k1 = a.context.getCoreKey(a.core)
139
+ const k2 = b.context.getCoreKey(b.core)
140
+
141
+ return k1 === k2 || b4a.equals(k1, k2) ? true : false
142
+ }
package/lib/encoding.js CHANGED
@@ -1,14 +1,144 @@
1
1
  const c = require('compact-encoding')
2
2
  const { getEncoding } = require('../spec/hyperschema')
3
- const decodeCompatBlock = require('./compat.js')
3
+ const { OP_COHORT, OP_INSERT } = require('./compression.js')
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
15
+ exports.encodeBlock = encodeBlock
8
16
  exports.decodeBlock = decodeBlock
9
17
 
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)
32
+ }
33
+
10
34
  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
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
+ ]
14
144
  }