hyperbee2 2.2.0 → 2.3.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
@@ -6,14 +6,7 @@ const { ChangesStream } = require('./lib/changes.js')
6
6
  const NodeCache = require('./lib/cache.js')
7
7
  const WriteBatch = require('./lib/write.js')
8
8
  const CoreContext = require('./lib/context.js')
9
- const {
10
- Pointer,
11
- KeyPointer,
12
- ValuePointer,
13
- TreeNode,
14
- TreeNodePointer,
15
- EMPTY
16
- } = require('./lib/tree.js')
9
+ const { Pointer, KeyPointer, ValuePointer, TreeNode, EMPTY } = require('./lib/tree.js')
17
10
  const { DeltaOp, DeltaCohort, OP_COHORT } = require('./lib/compression.js')
18
11
 
19
12
  class Hyperbee {
@@ -22,10 +15,9 @@ class Hyperbee {
22
15
  t = 128, // legacy number for now, should be 128 now
23
16
  key = null,
24
17
  encryption = null,
25
- core = key ? store.get(key) : store.get({ key, name: 'bee', encryption }),
26
- context = new CoreContext(store, core, core, encryption, t),
27
18
  maxCacheSize = 4096,
28
- cache = new NodeCache(maxCacheSize),
19
+ core = key ? store.get(key) : store.get({ key, name: 'bee', encryption }),
20
+ context = new CoreContext(store, core, new NodeCache(maxCacheSize), core, encryption, t),
29
21
  root = null,
30
22
  activeRequests = [],
31
23
  view = false,
@@ -36,7 +28,6 @@ class Hyperbee {
36
28
 
37
29
  this.store = store
38
30
  this.root = root
39
- this.cache = cache
40
31
  this.context = context
41
32
  this.activeRequests = activeRequests
42
33
  this.view = view
@@ -58,6 +49,10 @@ class Hyperbee {
58
49
  return { length: this.root.seq + 1, key: this.root.context.core.key }
59
50
  }
60
51
 
52
+ get cache() {
53
+ return this.context.cache
54
+ }
55
+
61
56
  get core() {
62
57
  return this.context.core
63
58
  }
@@ -85,7 +80,6 @@ class Hyperbee {
85
80
  _makeView(context, root, writable, unbatch) {
86
81
  return new Hyperbee(this.store, {
87
82
  context,
88
- cache: this.cache,
89
83
  root,
90
84
  view: true,
91
85
  writable,
@@ -95,7 +89,7 @@ class Hyperbee {
95
89
 
96
90
  checkout({ length = this.core.length, key = null, writable = false } = {}) {
97
91
  const context = key ? this.context.getContextByKey(key) : this.context
98
- const root = length === 0 ? EMPTY : new TreeNodePointer(context, 0, length - 1, 0, false, null)
92
+ const root = length === 0 ? EMPTY : context.createTreeNode(0, length - 1, 0, false, null)
99
93
  return this._makeView(context, root, writable, 0)
100
94
  }
101
95
 
@@ -119,7 +113,7 @@ class Hyperbee {
119
113
  this.root =
120
114
  this.context.core.length === 0
121
115
  ? EMPTY
122
- : new TreeNodePointer(this.context, 0, this.core.length - 1, 0, false, null)
116
+ : this.context.createTreeNode(0, this.core.length - 1, 0, false, null)
123
117
 
124
118
  if (this._autoUpdate) {
125
119
  this.core.on('append', () => {
@@ -163,12 +157,12 @@ class Hyperbee {
163
157
 
164
158
  bump(ptr) {
165
159
  if (ptr.changed) return ptr.value
166
- this.cache.bump(ptr)
167
- this.cache.gc()
160
+ this.context.cache.bump(ptr)
161
+ this.context.cache.gc()
168
162
  return ptr.value
169
163
  }
170
164
 
171
- // TODO: unslab these and parallize
165
+ // TODO: unslab these
172
166
  async inflate(ptr, activeRequests = this.activeRequests) {
173
167
  if (ptr.value) {
174
168
  this.bump(ptr)
@@ -268,7 +262,7 @@ class Hyperbee {
268
262
 
269
263
  if (expected === this.unbatch) {
270
264
  this.context = context
271
- this.root = length === 0 ? EMPTY : new TreeNodePointer(context, 0, length - 1, 0, false, null)
265
+ this.root = length === 0 ? EMPTY : context.createTreeNode(0, length - 1, 0, false, null)
272
266
  this.unbatch = 0
273
267
  }
274
268
  }
@@ -370,7 +364,7 @@ function inflateChild(context, d, ptr, block, activeRequests) {
370
364
 
371
365
  function inflateChildDelta(context, d, ptr, block, activeRequests) {
372
366
  const p = d.pointer
373
- const c = p && new TreeNodePointer(context, p.core, p.seq, p.offset, false, null)
367
+ const c = p && context.createTreeNode(p.core, p.seq, p.offset, false, null)
374
368
  return new DeltaOp(false, d.type, d.index, c)
375
369
  }
376
370
 
package/lib/cache.js CHANGED
@@ -3,6 +3,8 @@ module.exports = class NodeCache {
3
3
  this.size = 0
4
4
  this.maxSize = maxSize
5
5
  this.latest = null
6
+ this.retained = 0
7
+ this.byId = new Map()
6
8
  }
7
9
 
8
10
  oldest() {
@@ -20,7 +22,7 @@ module.exports = class NodeCache {
20
22
  gc() {
21
23
  while (this.size > this.maxSize) {
22
24
  const old = this.oldest()
23
- if (old.changed) break
25
+ if (old.retained > this.retained) break
24
26
  this.remove(old)
25
27
  old.value = null
26
28
  }
@@ -29,6 +31,10 @@ module.exports = class NodeCache {
29
31
  bump(node) {
30
32
  if (node === this.latest) return
31
33
 
34
+ if (node.next === null && node.prev === null) {
35
+ this.byId.set(cacheId(node), node)
36
+ }
37
+
32
38
  if (node.prev) this.remove(node)
33
39
  this.size++
34
40
 
@@ -44,6 +50,10 @@ module.exports = class NodeCache {
44
50
  }
45
51
  }
46
52
 
53
+ get(node) {
54
+ return this.byId.get(cacheId(node)) || null
55
+ }
56
+
47
57
  remove(node) {
48
58
  if (node.prev) {
49
59
  this.size--
@@ -57,6 +67,11 @@ module.exports = class NodeCache {
57
67
  }
58
68
 
59
69
  node.prev = node.next = null
70
+
71
+ const id = cacheId(node)
72
+ if (this.byId.get(id) === node) {
73
+ this.byId.delete(id)
74
+ }
60
75
  }
61
76
 
62
77
  *[Symbol.iterator]() {
@@ -71,3 +86,8 @@ module.exports = class NodeCache {
71
86
  } while (node !== next)
72
87
  }
73
88
  }
89
+
90
+ function cacheId(node) {
91
+ const id = node.core === 0 ? node.context.core.id : node.context.opened[node.core - 1].id
92
+ return id + '@' + node.seq + '.' + node.offset
93
+ }
@@ -23,6 +23,8 @@ class CompressedArray {
23
23
  constructor(delta) {
24
24
  this.entries = []
25
25
  this.delta = delta
26
+ this.uentries = null
27
+ this.updates = 0
26
28
 
27
29
  for (const d of delta) {
28
30
  if (d.type === OP_COHORT) {
@@ -39,8 +41,32 @@ class CompressedArray {
39
41
  return this.entries.length
40
42
  }
41
43
 
42
- touch(index) {
43
- const pointer = this.entries[index]
44
+ get ulength() {
45
+ return this.uentries ? this.uentries.length : this.entries.length
46
+ }
47
+
48
+ commit() {
49
+ const c = new CompressedArray([])
50
+
51
+ c.delta = this.delta
52
+ c.entries = this.uentries || this.entries.slice(0)
53
+
54
+ this.delta = this.delta.slice(0, this.updates)
55
+ this.uentries = null
56
+ this.updates = 0
57
+
58
+ return c
59
+ }
60
+
61
+ _update() {
62
+ if (this.uentries) return
63
+ this.uentries = this.entries.slice(0)
64
+ }
65
+
66
+ touch(index, pointer) {
67
+ if (pointer) this.entries[index] = pointer
68
+ else pointer = this.entries[index]
69
+
44
70
  if (pointer.changedBy === this) return
45
71
  this.set(index, pointer)
46
72
  }
@@ -49,8 +75,13 @@ class CompressedArray {
49
75
  return this.entries[index]
50
76
  }
51
77
 
78
+ uget(index) {
79
+ return this.uentries ? this.uentries[index] : this.entries[index]
80
+ }
81
+
52
82
  push(pointer) {
53
- this.insert(this.entries.length, pointer)
83
+ if (!this.uentries) this._update()
84
+ this.insert(this.uentries.length, pointer)
54
85
  }
55
86
 
56
87
  unshift(pointer) {
@@ -58,37 +89,43 @@ class CompressedArray {
58
89
  }
59
90
 
60
91
  pop() {
61
- if (this.entries.length === 0) return
62
- const head = this.entries[this.entries.length - 1]
63
- this.delete(this.entries.length - 1)
92
+ if (!this.uentries) this._update()
93
+ if (this.uentries.length === 0) return
94
+ const head = this.uentries[this.uentries.length - 1]
95
+ this.delete(this.uentries.length - 1)
64
96
  return head
65
97
  }
66
98
 
67
99
  shift() {
68
- if (this.entries.length === 0) return
69
- const tail = this.entries[0]
100
+ if (!this.uentries) this._update()
101
+ if (this.uentries.length === 0) return
102
+ const tail = this.uentries[0]
70
103
  this.delete(0)
71
104
  return tail
72
105
  }
73
106
 
74
107
  _touch(pointer) {
108
+ this.updates++
75
109
  if (pointer) pointer.changedBy = this
76
110
  }
77
111
 
78
112
  insert(index, pointer) {
79
- if (!insert(this.entries, index, pointer)) return
113
+ if (!this.uentries) this._update()
114
+ if (!insert(this.uentries, index, pointer)) return
80
115
  this._touch(pointer)
81
116
  this.delta.push(new DeltaOp(true, OP_INSERT, index, pointer))
82
117
  }
83
118
 
84
119
  delete(index) {
85
- if (!del(this.entries, index)) return
120
+ if (!this.uentries) this._update()
121
+ if (!del(this.uentries, index)) return
86
122
  this._touch(null)
87
123
  this.delta.push(new DeltaOp(true, OP_DEL, index, null))
88
124
  }
89
125
 
90
126
  set(index, pointer) {
91
- if (!set(this.entries, index, pointer)) return
127
+ if (!this.uentries) this._update()
128
+ if (!set(this.uentries, index, pointer)) return
92
129
  this._touch(pointer)
93
130
  this.delta.push(new DeltaOp(true, OP_SET, index, pointer))
94
131
  }
package/lib/context.js CHANGED
@@ -1,11 +1,13 @@
1
1
  const b4a = require('b4a')
2
2
  const ScopeLock = require('scope-lock')
3
+ const { TreeNodePointer } = require('./tree.js')
3
4
  const { decodeBlock } = require('./encoding.js')
4
5
 
5
6
  class CoreContext {
6
- constructor(store, local, core, encryption, t, lock = new ScopeLock(), other = new Map()) {
7
+ constructor(store, local, cache, core, encryption, t, lock = new ScopeLock(), other = new Map()) {
7
8
  this.store = store
8
9
  this.local = local
10
+ this.cache = cache
9
11
  this.core = core
10
12
  this.t = t
11
13
  this.minKeys = t - 1
@@ -52,6 +54,14 @@ class CoreContext {
52
54
  this.length = length
53
55
  }
54
56
 
57
+ createTreeNode(core, seq, offset, changed, value) {
58
+ const ptr = new TreeNodePointer(this, core, seq, offset, changed, value)
59
+ if (ptr.value || changed) return ptr
60
+ const existing = this.cache.get(ptr)
61
+ if (existing) return existing
62
+ return ptr
63
+ }
64
+
55
65
  getCoreOffsetLocal(context, core) {
56
66
  if (context === this) return core
57
67
 
@@ -171,11 +181,12 @@ class CoreContext {
171
181
  _createContext(core) {
172
182
  const store = this.store
173
183
  const local = this.local
184
+ const cache = this.cache
174
185
  const encryption = this.encryption
175
186
  const t = this.t
176
187
  const lock = this.lock
177
188
  const other = this.other
178
- return new CoreContext(store, local, core, encryption, t, lock, other)
189
+ return new CoreContext(store, local, cache, core, encryption, t, lock, other)
179
190
  }
180
191
  }
181
192
 
package/lib/tree.js CHANGED
@@ -23,9 +23,14 @@ class Pointer {
23
23
  this.seq = seq
24
24
  this.offset = offset
25
25
  this.changed = changed
26
+ this.retained = 0
26
27
 
27
28
  this.changedBy = null
28
29
  }
30
+
31
+ retain() {
32
+ this.retained = this.context.cache.retained + 1
33
+ }
29
34
  }
30
35
 
31
36
  class KeyPointer extends Pointer {
@@ -38,11 +43,9 @@ class KeyPointer extends Pointer {
38
43
  }
39
44
 
40
45
  // 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
+ [Symbol.for('nodejs.util.inspect.custom')]() {
47
+ return `[KeyPointer core=${this.core} seq=${this.seq}, offset=${this.offset}, key="${b4a.toString(this.key)}"]`
48
+ }
46
49
  }
47
50
 
48
51
  class TreeNodePointer extends Pointer {
@@ -55,12 +58,19 @@ class TreeNodePointer extends Pointer {
55
58
  this.prev = null
56
59
  }
57
60
 
61
+ commit() {
62
+ this.changed = false
63
+ const value = new TreeNode([], [])
64
+ value.keys = this.value.keys.commit()
65
+ value.children = this.value.children.commit()
66
+ const ptr = this.context.createTreeNode(this.core, 0, 0, true, value)
67
+ return ptr
68
+ }
69
+
58
70
  // 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
- // }
71
+ [Symbol.for('nodejs.util.inspect.custom')]() {
72
+ return `[TreeNodePointer core=${this.core} seq=${this.seq} offset=${this.offset} changed=${this.changed}]`
73
+ }
64
74
  }
65
75
 
66
76
  class TreeNode {
@@ -70,17 +80,17 @@ class TreeNode {
70
80
  }
71
81
 
72
82
  isEmpty() {
73
- return this.keys.length === 0 && this.children.length === 0
83
+ return this.keys.ulength === 0 && this.children.ulength === 0
74
84
  }
75
85
 
76
86
  insertLeaf(context, key, value) {
77
87
  let s = 0
78
- let e = this.keys.length
88
+ let e = this.keys.ulength
79
89
  let c = 0
80
90
 
81
91
  while (s < e) {
82
92
  const mid = (s + e) >> 1
83
- const k = this.keys.get(mid)
93
+ const k = this.keys.uget(mid)
84
94
 
85
95
  c = b4a.compare(key, k.key)
86
96
 
@@ -91,19 +101,20 @@ class TreeNode {
91
101
  }
92
102
 
93
103
  const i = c < 0 ? e : s
104
+
94
105
  this.keys.insert(i, new KeyPointer(context, 0, 0, 0, true, key, value, null))
95
106
 
96
- return this.keys.length <= context.maxKeys ? INSERTED : NEEDS_SPLIT
107
+ return this.keys.ulength <= context.maxKeys ? INSERTED : NEEDS_SPLIT
97
108
  }
98
109
 
99
110
  insertNode(context, keyPointer, treePointer) {
100
111
  let s = 0
101
- let e = this.keys.length
112
+ let e = this.keys.ulength
102
113
  let c = 0
103
114
 
104
115
  while (s < e) {
105
116
  const mid = (s + e) >> 1
106
- const k = this.keys.get(mid)
117
+ const k = this.keys.uget(mid)
107
118
 
108
119
  c = b4a.compare(keyPointer.key, k.key)
109
120
 
@@ -117,16 +128,16 @@ class TreeNode {
117
128
  this.keys.insert(i, keyPointer)
118
129
  this.children.insert(i + 1, treePointer)
119
130
 
120
- return this.keys.length <= context.maxKeys ? INSERTED : NEEDS_SPLIT
131
+ return this.keys.ulength <= context.maxKeys ? INSERTED : NEEDS_SPLIT
121
132
  }
122
133
 
123
134
  setValue(context, i, value) {
124
- this.keys.set(i, new KeyPointer(context, 0, 0, 0, true, this.keys.get(i).key, value, null))
135
+ this.keys.set(i, new KeyPointer(context, 0, 0, 0, true, this.keys.uget(i).key, value, null))
125
136
  }
126
137
 
127
138
  removeKey(i) {
128
139
  this.keys.delete(i)
129
- if (this.children.length) {
140
+ if (this.children.ulength) {
130
141
  this.children.delete(i + 1)
131
142
  }
132
143
  }
@@ -134,11 +145,11 @@ class TreeNode {
134
145
  siblings(parent) {
135
146
  const pc = parent.children
136
147
 
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
148
+ for (let i = 0; i < pc.ulength; i++) {
149
+ if (pc.uget(i).value !== this) continue // TODO: move to a seq/offset check instead
139
150
 
140
- const left = i ? pc.get(i - 1) : null
141
- const right = i < pc.length - 1 ? pc.get(i + 1) : null
151
+ const left = i ? pc.uget(i - 1) : null
152
+ const right = i < pc.ulength - 1 ? pc.uget(i + 1) : null
142
153
  return { left, index: i, right }
143
154
  }
144
155
 
@@ -152,13 +163,13 @@ class TreeNode {
152
163
 
153
164
  this.keys.push(median)
154
165
 
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))
166
+ for (let i = 0; i < keys.ulength; i++) this.keys.push(keys.uget(i))
167
+ for (let i = 0; i < children.ulength; i++) this.children.push(children.uget(i))
157
168
  }
158
169
 
159
170
  split(context) {
160
- const len = this.keys.length >> 1
161
- const right = new TreeNodePointer(context, 0, 0, 0, true, new TreeNode([], []))
171
+ const len = this.keys.ulength >> 1
172
+ const right = context.createTreeNode(0, 0, 0, true, new TreeNode([], []))
162
173
 
163
174
  const k = []
164
175
  while (k.length < len) k.push(this.keys.pop())
@@ -166,7 +177,7 @@ class TreeNode {
166
177
 
167
178
  const median = this.keys.pop()
168
179
 
169
- if (this.children.length) {
180
+ if (this.children.ulength) {
170
181
  const c = []
171
182
  while (c.length < len + 1) c.push(this.children.pop())
172
183
  for (let i = c.length - 1; i >= 0; i--) right.value.children.push(c[i])
package/lib/write.js CHANGED
@@ -2,15 +2,7 @@ const b4a = require('b4a')
2
2
  const c = require('compact-encoding')
3
3
  const { OP_COHORT } = require('./compression.js')
4
4
  const { encodeBlock, TYPE_COMPAT, TYPE_LATEST } = require('./encoding.js')
5
- const {
6
- Pointer,
7
- KeyPointer,
8
- ValuePointer,
9
- TreeNode,
10
- TreeNodePointer,
11
- INSERTED,
12
- NEEDS_SPLIT
13
- } = require('./tree.js')
5
+ const { Pointer, KeyPointer, ValuePointer, TreeNode, INSERTED, NEEDS_SPLIT } = require('./tree.js')
14
6
 
15
7
  const PREFERRED_BLOCK_SIZE = 4096
16
8
  const INLINE_VALUE_SIZE = 4096
@@ -86,7 +78,7 @@ module.exports = class WriteBatch {
86
78
  const value = changed ? new TreeNode([], []) : null
87
79
 
88
80
  this.length = length
89
- this.root = new TreeNodePointer(context, 0, seq, 0, changed, value)
81
+ this.root = context.createTreeNode(0, seq, 0, changed, value)
90
82
 
91
83
  for (const op of ops) {
92
84
  if (op.put) op.applied = await this._put(op.key, op.value)
@@ -113,27 +105,28 @@ module.exports = class WriteBatch {
113
105
  async _put(key, value) {
114
106
  const stack = []
115
107
  const target = key
108
+ const snap = this.snapshot
116
109
 
117
110
  let ptr = this.root
118
111
 
119
112
  while (true) {
120
- const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
121
- if (!v.children.length) break
113
+ const v = await retainAndInflate(ptr, snap)
114
+ if (!v.children.ulength) break
122
115
 
123
116
  stack.push(ptr)
124
117
 
125
118
  let s = 0
126
- let e = v.keys.length
119
+ let e = v.keys.ulength
127
120
  let c = 0
128
121
 
129
122
  while (s < e) {
130
123
  const mid = (s + e) >> 1
131
- const m = v.keys.get(mid)
124
+ const m = v.keys.uget(mid)
132
125
 
133
126
  c = b4a.compare(target, m.key)
134
127
 
135
128
  if (c === 0) {
136
- const existing = await this.snapshot.inflateValue(m)
129
+ const existing = await snap.inflateValue(m)
137
130
  if (b4a.equals(existing, value)) return false
138
131
  v.setValue(this.tree.context, mid, value)
139
132
  for (let i = 0; i < stack.length; i++) stack[i].changed = true
@@ -145,16 +138,16 @@ module.exports = class WriteBatch {
145
138
  }
146
139
 
147
140
  const i = c < 0 ? e : s
148
- ptr = v.children.get(i)
141
+ ptr = v.children.uget(i)
149
142
  }
150
143
 
151
- const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
144
+ const v = await retainAndInflate(ptr, snap)
152
145
  let status = v.insertLeaf(this.tree.context, target, value)
153
146
 
154
147
  if (status >= 0) {
155
148
  // already exists, upsert if changed
156
- const m = v.keys.get(status)
157
- const existing = await this.snapshot.inflateValue(m)
149
+ const m = v.keys.uget(status)
150
+ const existing = await snap.inflateValue(m)
158
151
  if (b4a.equals(existing, value)) return false
159
152
  v.setValue(this.tree.context, status, value)
160
153
  }
@@ -163,20 +156,19 @@ module.exports = class WriteBatch {
163
156
  for (let i = 0; i < stack.length; i++) stack[i].changed = true
164
157
 
165
158
  while (status === NEEDS_SPLIT) {
166
- const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
159
+ const v = await retainAndInflate(ptr, snap)
167
160
  const parent = stack.pop()
168
161
  const { median, right } = v.split(this.tree.context)
169
162
 
170
163
  if (parent) {
171
- const p = parent.value ? this.snapshot.bump(parent) : await this.snapshot.inflate(parent)
164
+ const p = await retainAndInflate(parent, snap)
172
165
  status = p.insertNode(this.tree.context, median, right)
173
166
  ptr = parent
174
167
  } else {
175
- this.root = new TreeNodePointer(this.tree.context, 0, 0, 0, true, new TreeNode([], []))
168
+ this.root = this.tree.context.createTreeNode(0, 0, 0, true, new TreeNode([], []))
176
169
  this.root.value.keys.push(median)
177
170
  this.root.value.children.push(ptr)
178
171
  this.root.value.children.push(right)
179
- this.snapshot.bump(this.root)
180
172
  status = INSERTED
181
173
  }
182
174
  }
@@ -190,19 +182,19 @@ module.exports = class WriteBatch {
190
182
  const stack = []
191
183
 
192
184
  while (true) {
193
- const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
185
+ const v = await retainAndInflate(ptr, this.snapshot)
194
186
  stack.push(ptr)
195
187
 
196
188
  let s = 0
197
- let e = v.keys.length
189
+ let e = v.keys.ulength
198
190
  let c = 0
199
191
 
200
192
  while (s < e) {
201
193
  const mid = (s + e) >> 1
202
- c = b4a.compare(key, v.keys.get(mid).key)
194
+ c = b4a.compare(key, v.keys.uget(mid).key)
203
195
 
204
196
  if (c === 0) {
205
- if (v.children.length) await this._setKeyToNearestLeaf(v, mid, stack)
197
+ if (v.children.ulength) await this._setKeyToNearestLeaf(v, mid, stack)
206
198
  else v.removeKey(mid)
207
199
  // we mark these as changed late, so we don't rewrite them if it is a 404
208
200
  for (let i = 0; i < stack.length; i++) stack[i].changed = true
@@ -214,88 +206,92 @@ module.exports = class WriteBatch {
214
206
  else s = mid + 1
215
207
  }
216
208
 
217
- if (!v.children.length) return false
209
+ if (!v.children.ulength) return false
218
210
 
219
211
  const i = c < 0 ? e : s
220
- ptr = v.children.get(i)
212
+ ptr = v.children.uget(i)
221
213
  }
222
214
 
223
215
  return false
224
216
  }
225
217
 
226
218
  async _setKeyToNearestLeaf(v, index, stack) {
227
- let left = v.children.get(index)
228
- let right = v.children.get(index + 1)
219
+ const snap = this.snapshot
220
+
221
+ let left = v.children.uget(index)
222
+ let right = v.children.uget(index + 1)
229
223
 
230
224
  const [ls, rs] = await Promise.all([this._leafSize(left, false), this._leafSize(right, true)])
231
225
 
232
226
  if (ls < rs) {
233
227
  // if fewer leaves on the left
234
228
  stack.push(right)
235
- let r = right.value ? this.snapshot.bump(right) : await this.snapshot.inflate(right)
236
- while (r.children.length) {
237
- right = r.children.get(0)
229
+ let r = await retainAndInflate(right, snap)
230
+ while (r.children.ulength) {
231
+ right = r.children.uget(0)
238
232
  stack.push(right)
239
- r = right.value ? this.snapshot.bump(right) : await this.snapshot.inflate(right)
233
+ r = await retainAndInflate(right, snap)
240
234
  }
241
235
  v.keys.set(index, r.keys.shift())
242
236
  } else {
243
237
  // if fewer leaves on the right
244
238
  stack.push(left)
245
- let l = left.value ? this.snapshot.bump(left) : await this.snapshot.inflate(left)
246
- while (l.children.length) {
247
- left = l.children.get(l.children.length - 1)
239
+ let l = await retainAndInflate(left, snap)
240
+ while (l.children.ulength) {
241
+ left = l.children.uget(l.children.ulength - 1)
248
242
  stack.push(left)
249
- l = left.value ? this.snapshot.bump(left) : await this.snapshot.inflate(left)
243
+ l = await retainAndInflate(left, snap)
250
244
  }
251
245
  v.keys.set(index, l.keys.pop())
252
246
  }
253
247
  }
254
248
 
255
249
  async _leafSize(ptr, goLeft) {
256
- let v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
257
- while (v.children.length) {
258
- ptr = v.children.get(goLeft ? 0 : v.children.length - 1)
259
- v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
250
+ const snap = this.snapshot
251
+
252
+ let v = await retainAndInflate(ptr, snap)
253
+ while (v.children.ulength) {
254
+ ptr = v.children.uget(goLeft ? 0 : v.children.ulength - 1)
255
+ v = await retainAndInflate(ptr, snap)
260
256
  }
261
- return v.keys.length
257
+ return v.keys.ulength
262
258
  }
263
259
 
264
260
  async _rebalance(stack) {
265
261
  const root = stack[0]
266
262
  const minKeys = this.tree.context.minKeys
263
+ const snap = this.snapshot
267
264
 
268
265
  while (stack.length > 1) {
269
266
  const ptr = stack.pop()
270
267
  const parent = stack[stack.length - 1]
271
268
 
272
- const v = ptr.value ? this.snapshot.bump(ptr) : await this.snapshot.inflate(ptr)
269
+ const v = await retainAndInflate(ptr, snap)
273
270
 
274
- if (v.keys.length >= minKeys) return root
271
+ if (v.keys.ulength >= minKeys) return root
275
272
 
276
- const p = parent.value ? this.snapshot.bump(parent) : await this.snapshot.inflate(parent)
273
+ const p = await retainAndInflate(parent, snap)
277
274
 
278
275
  let { left, index, right } = v.siblings(p)
279
276
 
280
- let l = left && (left.value ? this.snapshot.bump(left) : await this.snapshot.inflate(left))
277
+ let l = left && (await retainAndInflate(left, snap))
281
278
 
282
279
  // maybe borrow from left sibling?
283
- if (l && l.keys.length > minKeys) {
280
+ if (l && l.keys.ulength > minKeys) {
284
281
  left.changed = true
285
- v.keys.unshift(p.keys.get(index - 1))
286
- if (l.children.length) v.children.unshift(l.children.pop())
282
+ v.keys.unshift(p.keys.uget(index - 1))
283
+ if (l.children.ulength) v.children.unshift(l.children.pop())
287
284
  p.keys.set(index - 1, l.keys.pop())
288
285
  return root
289
286
  }
290
287
 
291
- let r =
292
- right && (right.value ? this.snapshot.bump(right) : await this.snapshot.inflate(right))
288
+ let r = right && (await retainAndInflate(right, snap))
293
289
 
294
290
  // maybe borrow from right sibling?
295
- if (r && r.keys.length > minKeys) {
291
+ if (r && r.keys.ulength > minKeys) {
296
292
  right.changed = true
297
- v.keys.push(p.keys.get(index))
298
- if (r.children.length) v.children.push(r.children.shift())
293
+ v.keys.push(p.keys.uget(index))
294
+ if (r.children.ulength) v.children.push(r.children.shift())
299
295
  p.keys.set(index, r.keys.shift())
300
296
  return root
301
297
  }
@@ -311,15 +307,15 @@ module.exports = class WriteBatch {
311
307
  }
312
308
 
313
309
  left.changed = true
314
- l.merge(r, p.keys.get(index))
310
+ l.merge(r, p.keys.uget(index))
315
311
 
316
312
  parent.changed = true
317
313
  p.removeKey(index)
318
314
  }
319
315
 
320
- const r = root.value ? this.snapshot.bump(root) : await this.snapshot.inflate(root)
316
+ const r = await retainAndInflate(root, snap)
321
317
  // check if the tree shrunk
322
- if (!r.keys.length && r.children.length) return r.children.get(0)
318
+ if (!r.keys.ulength && r.children.ulength) return r.children.uget(0)
323
319
  return root
324
320
  }
325
321
 
@@ -339,6 +335,8 @@ module.exports = class WriteBatch {
339
335
  let update = { size: 0, nodes: [], keys: [], values: [] }
340
336
  let minValue = -1
341
337
 
338
+ this.root = this.root.commit()
339
+
342
340
  const batch = [update]
343
341
  const stack = [this.root]
344
342
  const values = []
@@ -382,8 +380,9 @@ module.exports = class WriteBatch {
382
380
  for (let i = 0; i < children.entries.length; i++) {
383
381
  const c = children.entries[i]
384
382
  if (!c.value || !c.changed) continue
385
- children.touch(i)
386
- stack.push(c)
383
+ const node = c.commit()
384
+ children.touch(i, node)
385
+ stack.push(node)
387
386
  }
388
387
  }
389
388
 
@@ -515,6 +514,9 @@ module.exports = class WriteBatch {
515
514
  this.snapshot.bump(node)
516
515
  }
517
516
  }
517
+
518
+ context.cache.retained++
519
+ context.cache.gc()
518
520
  }
519
521
  }
520
522
 
@@ -597,3 +599,12 @@ function prepareCohorts(context, block, seq, deltas, keys) {
597
599
  function supportsCompression(type) {
598
600
  return type !== TYPE_COMPAT && type !== 0
599
601
  }
602
+
603
+ async function retainAndInflate(ptr, snap) {
604
+ if (ptr.value) {
605
+ ptr.retain()
606
+ return ptr.value
607
+ }
608
+ ptr.retain()
609
+ return await snap.inflate(ptr)
610
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperbee2",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "btree",
5
5
  "main": "index.js",
6
6
  "files": [