hyperbee2 1.0.0 → 1.1.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/index.js CHANGED
@@ -11,8 +11,9 @@ class Hyperbee {
11
11
  constructor(store, options = {}) {
12
12
  const {
13
13
  key = null,
14
- core = key ? store.get(key) : store.get({ key, name: 'bee' }),
15
- context = new CoreContext(store, core, core),
14
+ encryption = null,
15
+ core = key ? store.get(key) : store.get({ key, name: 'bee', encryption }),
16
+ context = new CoreContext(store, core, core, encryption),
16
17
  maxCacheSize = 4096,
17
18
  cache = new NodeCache(maxCacheSize),
18
19
  root = null,
package/lib/context.js CHANGED
@@ -1,11 +1,14 @@
1
1
  const b4a = require('b4a')
2
+ const ScopeLock = require('scope-lock')
2
3
  const { decodeBlock } = require('./encoding.js')
3
4
 
4
5
  class CoreContext {
5
- constructor(store, local, core, other = new Map()) {
6
+ constructor(store, local, core, encryption, lock = new ScopeLock(), other = new Map()) {
6
7
  this.store = store
7
8
  this.local = local
8
9
  this.core = core
10
+ this.encryption = encryption
11
+ this.lock = lock
9
12
  this.other = other
10
13
  this.length = 0
11
14
  this.checkpoint = 0
@@ -43,7 +46,7 @@ class CoreContext {
43
46
 
44
47
  async getCoreOffset(context, core, activeRequests) {
45
48
  if (core !== 0 && core - 1 >= context.cores.length) await context.update(activeRequests)
46
- const key = core === 0 ? context.core.key : context.cores[core - 1]
49
+ const key = core === 0 ? context.core.key : context.cores[core - 1].key
47
50
 
48
51
  if (b4a.equals(key, this.core.key)) return 0
49
52
 
@@ -90,7 +93,8 @@ class CoreContext {
90
93
  if (index === 0) return this.core
91
94
  if (index > this.cores.length) throw new Error('Bad core index: ' + index)
92
95
  if (this.opened[index - 1] === null) {
93
- this.opened[index - 1] = this.store.get(this.cores[index - 1].key)
96
+ const key = this.cores[index - 1].key
97
+ this.opened[index - 1] = this.store.get({ key, encryption: this.encryption })
94
98
  }
95
99
  return this.opened[index - 1]
96
100
  }
@@ -113,7 +117,8 @@ class CoreContext {
113
117
  const hex = b4a.toString(key, 'hex')
114
118
  if (this.other.has(hex)) return this.other.get(hex)
115
119
 
116
- const ctx = new CoreContext(this.store, this.local, this.store.get(key))
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)
117
122
  this.other.set(hex, ctx)
118
123
  return ctx
119
124
  }
@@ -127,7 +132,7 @@ class CoreContext {
127
132
  if (this.other.has(hex)) return this.other.get(hex)
128
133
 
129
134
  const hc = this.getCore(core)
130
- const ctx = new CoreContext(this.store, this.local, hc)
135
+ const ctx = new CoreContext(this.store, this.local, hc, this.encryption, this.lock, this.other)
131
136
  this.other.set(hex, ctx)
132
137
  return ctx
133
138
  }
package/lib/tree.js CHANGED
@@ -4,6 +4,10 @@ const T = 5
4
4
  const MIN_KEYS = T - 1
5
5
  const MAX_CHILDREN = MIN_KEYS * 2 + 1
6
6
 
7
+ const UNCHANGED = 0
8
+ const CHANGED = 1
9
+ const NEEDS_SPLIT = 2
10
+
7
11
  class DataPointer {
8
12
  constructor(context, core, seq, offset, changed, key, value) {
9
13
  this.context = context
@@ -40,6 +44,10 @@ class TreeNode {
40
44
  this.children = children
41
45
  }
42
46
 
47
+ isEmpty() {
48
+ return this.keys.length === 0 && this.children.length === 0
49
+ }
50
+
43
51
  put(context, key, value, child) {
44
52
  let s = 0
45
53
  let e = this.keys.length
@@ -52,8 +60,9 @@ class TreeNode {
52
60
  c = b4a.compare(key, k.key)
53
61
 
54
62
  if (c === 0) {
63
+ if (b4a.equals(k.value, value)) return UNCHANGED
55
64
  this.keys[mid] = new DataPointer(context, 0, 0, 0, true, key, value)
56
- return true
65
+ return CHANGED
57
66
  }
58
67
 
59
68
  if (c < 0) e = mid
@@ -64,7 +73,7 @@ class TreeNode {
64
73
  this.keys.splice(i, 0, new DataPointer(context, 0, 0, 0, true, key, value))
65
74
  if (child) this.children.splice(i + 1, 0, child)
66
75
 
67
- return this.keys.length < MAX_CHILDREN
76
+ return this.keys.length < MAX_CHILDREN ? CHANGED : NEEDS_SPLIT
68
77
  }
69
78
 
70
79
  setValue(context, i, value) {
@@ -123,6 +132,10 @@ exports.T = T
123
132
  exports.MIN_KEYS = MIN_KEYS
124
133
  exports.MAX_CHILDREN = MAX_CHILDREN
125
134
 
135
+ exports.UNCHANGED = UNCHANGED
136
+ exports.CHANGED = CHANGED
137
+ exports.NEEDS_SPLIT = NEEDS_SPLIT
138
+
126
139
  exports.TreeNodePointer = TreeNodePointer
127
140
  exports.TreeNode = TreeNode
128
141
  exports.DataPointer = DataPointer
package/lib/write.js CHANGED
@@ -1,7 +1,14 @@
1
1
  const b4a = require('b4a')
2
2
  const c = require('compact-encoding')
3
- const { TreeNode, TreeNodePointer, MIN_KEYS } = require('./tree.js')
4
3
  const { Block } = require('./encoding.js')
4
+ const {
5
+ TreeNode,
6
+ TreeNodePointer,
7
+ MIN_KEYS,
8
+ UNCHANGED,
9
+ CHANGED,
10
+ NEEDS_SPLIT
11
+ } = require('./tree.js')
5
12
 
6
13
  module.exports = class WriteBatch {
7
14
  constructor(tree, { length = -1, key = null, autoUpdate = true } = {}) {
@@ -11,7 +18,6 @@ module.exports = class WriteBatch {
11
18
  this.length = length
12
19
  this.key = key
13
20
  this.closed = false
14
- this.flushing = false
15
21
  this.root = null
16
22
  this.ops = []
17
23
  }
@@ -40,40 +46,44 @@ module.exports = class WriteBatch {
40
46
  }
41
47
 
42
48
  async flush() {
43
- if (this.flushing) throw new Error('Already flushed')
44
- this.flushing = true
45
-
46
- const ops = this.ops
47
- this.ops = []
48
-
49
- const root = await this.tree.bootstrap()
50
-
51
- const length = this._getLength(root)
52
- const context = this._getContext(root)
53
-
54
- const changed = length === 0
55
- const seq = length === 0 ? 0 : length - 1
56
-
57
- this.length = length
58
- this.root = new TreeNodePointer(
59
- context,
60
- 0,
61
- seq,
62
- 0,
63
- changed,
64
- changed ? new TreeNode([], []) : null
65
- )
66
-
67
- for (const op of ops) {
68
- if (op.put) await this._put(op.key, op.value)
69
- else await this._delete(op.key)
70
- }
49
+ const lock = this.tree.context.lock
50
+ await lock.lock()
51
+
52
+ try {
53
+ const ops = this.ops
54
+ this.ops = []
55
+
56
+ const root = await this.tree.bootstrap()
57
+
58
+ const length = this._getLength(root)
59
+ const context = this._getContext(root)
60
+
61
+ const changed = length === 0
62
+ const seq = length === 0 ? 0 : length - 1
63
+
64
+ this.length = length
65
+ this.root = new TreeNodePointer(
66
+ context,
67
+ 0,
68
+ seq,
69
+ 0,
70
+ changed,
71
+ changed ? new TreeNode([], []) : null
72
+ )
73
+
74
+ for (const op of ops) {
75
+ if (op.put) await this._put(op.key, op.value)
76
+ else await this._delete(op.key)
77
+ }
71
78
 
72
- await this._flush()
73
- await this.snapshot.close()
79
+ await this._flush()
80
+ await this.snapshot.close()
74
81
 
75
- if (this.autoUpdate) {
76
- this.tree.update(this.root)
82
+ if (this.autoUpdate) {
83
+ this.tree.update(this.root)
84
+ }
85
+ } finally {
86
+ lock.unlock()
77
87
  }
78
88
  }
79
89
 
@@ -105,7 +115,7 @@ module.exports = class WriteBatch {
105
115
  c = b4a.compare(target, m.key)
106
116
 
107
117
  if (c === 0) {
108
- if (b4a.compare(m.value, value)) return
118
+ if (b4a.equals(m.value, value)) return
109
119
  v.setValue(this.tree.context, mid, value)
110
120
  for (let i = 0; i < stack.length; i++) stack[i].changed = true
111
121
  return
@@ -120,27 +130,29 @@ module.exports = class WriteBatch {
120
130
  }
121
131
 
122
132
  const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
123
- let needsSplit = !v.put(this.tree.context, target, value, null)
133
+ let status = v.put(this.tree.context, target, value, null)
134
+
135
+ if (status === UNCHANGED) return
124
136
 
125
137
  ptr.changed = true
126
138
 
127
139
  for (let i = 0; i < stack.length; i++) stack[i].changed = true
128
140
 
129
- while (needsSplit) {
141
+ while (status === NEEDS_SPLIT) {
130
142
  const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
131
143
  const parent = stack.pop()
132
144
  const { median, right } = v.split(this.tree.context)
133
145
 
134
146
  if (parent) {
135
147
  const p = parent.value ? this.snapshot.bump(parent) : await this.snapshot.inflate(parent)
136
- needsSplit = !p.put(this.tree.context, median.key, median.value, right)
148
+ status = p.put(this.tree.context, median.key, median.value, right)
137
149
  ptr = parent
138
150
  } else {
139
151
  this.root = new TreeNodePointer(this.tree.context, 0, 0, 0, true, new TreeNode([], []))
140
152
  this.root.value.keys.push(median)
141
153
  this.root.value.children.push(ptr, right)
142
154
  this.snapshot.bump(this.root)
143
- needsSplit = false
155
+ status = UNCHANGED
144
156
  }
145
157
  }
146
158
  }
@@ -333,6 +345,14 @@ module.exports = class WriteBatch {
333
345
  }
334
346
  }
335
347
 
348
+ // if only the root was marked dirty and is === current bootstrap the batch is a noop - skip
349
+ if (update.node.length === 1 && update.keys.length === 0) {
350
+ const b = await this.tree.bootstrap()
351
+ const n = update.node[0]
352
+ if (b && b.context === n.context && b.core === n.core && b.seq === n.seq) return
353
+ if (!b && n.isEmpty()) return
354
+ }
355
+
336
356
  const length = context.core.length
337
357
  const blocks = new Array(batch.length)
338
358
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperbee2",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "btree",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -20,6 +20,7 @@
20
20
  "hypercore": "^11.15.0",
21
21
  "hyperschema": "^1.14.0",
22
22
  "protocol-buffers-encodings": "^1.2.0",
23
+ "scope-lock": "^1.2.4",
23
24
  "streamx": "^2.22.1"
24
25
  },
25
26
  "devDependencies": {