hyperbee2 2.8.0 → 2.9.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
@@ -8,8 +8,8 @@ const NodeCache = require('./lib/cache.js')
8
8
  const WriteBatch = require('./lib/write.js')
9
9
  const CoreContext = require('./lib/context.js')
10
10
  const SessionConfig = require('./lib/session-config.js')
11
- const { Pointer, KeyPointer, ValuePointer, TreeNode, EMPTY } = require('./lib/tree.js')
12
- const { DeltaOp, DeltaCohort, OP_COHORT } = require('./lib/compression.js')
11
+ const { inflate, inflateValue } = require('./lib/inflate.js')
12
+ const { EMPTY } = require('./lib/tree.js')
13
13
 
14
14
  class Hyperbee extends EventEmitter {
15
15
  constructor(store, opts = {}) {
@@ -21,10 +21,11 @@ class Hyperbee extends EventEmitter {
21
21
  encryption = null,
22
22
  getEncryptionProvider = toEncryptionProvider(encryption),
23
23
  maxCacheSize = 4096,
24
- config = new SessionConfig([], 0, true),
24
+ config = new SessionConfig([], 0, true, null),
25
25
  activeRequests = config.activeRequests,
26
26
  timeout = config.timeout,
27
27
  wait = config.wait,
28
+ trace = config.trace,
28
29
  core = key
29
30
  ? store.get({ key, encryption: getEncryptionProvider(key) })
30
31
  : store.get({ key, name: 'bee', encryption: getEncryptionProvider(key) }),
@@ -40,14 +41,14 @@ class Hyperbee extends EventEmitter {
40
41
  view = false,
41
42
  writable = true,
42
43
  unbatch = 0,
43
- autoUpdate = false,
44
+ autoUpdate = !writable && !view,
44
45
  preload = null
45
46
  } = opts
46
47
 
47
48
  this.store = store
48
49
  this.root = root
49
50
  this.context = context
50
- this.config = config.sub(activeRequests, timeout, wait)
51
+ this.config = config.sub(activeRequests, timeout, wait, trace)
51
52
  this.view = view
52
53
  this.writable = writable
53
54
  this.unbatch = unbatch
@@ -117,12 +118,9 @@ class Hyperbee extends EventEmitter {
117
118
  }
118
119
 
119
120
  move({ length = this.core.length, key = null, writable = this.writable } = {}) {
120
- const context = key ? this.context.getContextByKey(key) : this.context
121
- const root = length === 0 ? EMPTY : context.createTreeNode(0, length - 1, 0, false, null)
122
- this.context = context
121
+ this.context = key ? this.context.getContextByKey(key) : this.context
123
122
  this.writable = writable
124
- this.root = root
125
- this.emit('update')
123
+ this._setRoot(this._nodeAtSeq(length - 1), true)
126
124
  }
127
125
 
128
126
  snapshot() {
@@ -138,21 +136,23 @@ class Hyperbee extends EventEmitter {
138
136
  return new WriteBatch(this, opts)
139
137
  }
140
138
 
139
+ _lastNodeInCore() {
140
+ return this._nodeAtSeq(this.context.core.length - 1)
141
+ }
142
+
143
+ _nodeAtSeq(seq) {
144
+ return seq < 0 ? EMPTY : this.context.createTreeNode(0, seq, 0, false, null)
145
+ }
146
+
141
147
  async ready() {
142
148
  if (!this.core.opened) await this.core.ready()
143
149
  if (this.root) return
144
150
  if (this.preload) await this.preload()
145
151
  if (this.root) return
146
152
 
147
- this.root =
148
- this.context.core.length === 0
149
- ? EMPTY
150
- : this.context.createTreeNode(0, this.core.length - 1, 0, false, null)
151
-
153
+ this._setRoot(this._lastNodeInCore(), false)
152
154
  if (this.autoUpdate) {
153
- this.core.on('append', () => {
154
- this.update()
155
- })
155
+ this.core.on('append', () => this._setRoot(this._lastNodeInCore(), true))
156
156
  }
157
157
 
158
158
  this.emit('ready')
@@ -202,45 +202,16 @@ class Hyperbee extends EventEmitter {
202
202
  return ptr.value
203
203
  }
204
204
 
205
- // TODO: unslab these
206
205
  async inflate(ptr, config) {
207
- if (ptr.value) {
208
- this.bump(ptr)
209
- return ptr.value
210
- }
211
-
212
- const [block, context] = await Promise.all([
213
- ptr.context.getBlock(ptr.seq, ptr.core, config),
214
- ptr.context.getContext(ptr.core, config)
215
- ])
216
-
217
- const tree = block.tree[ptr.offset]
218
-
219
- const keys = new Array(tree.keys.length)
220
- const children = new Array(tree.children.length)
221
-
222
- for (let i = 0; i < keys.length; i++) {
223
- const d = tree.keys[i]
224
- keys[i] = inflateKey(context, d, ptr, block, config)
225
- }
226
-
227
- for (let i = 0; i < children.length; i++) {
228
- const d = tree.children[i]
229
- children[i] = inflateChild(context, d, ptr, block, config)
206
+ if (!ptr.value) {
207
+ await inflate(ptr, config)
230
208
  }
231
-
232
- const [k, c] = await Promise.all([Promise.all(keys), Promise.all(children)])
233
-
234
- const value = new TreeNode(k, c)
235
- if (!ptr.value) ptr.value = value
236
-
237
209
  this.bump(ptr)
238
-
239
210
  return ptr.value
240
211
  }
241
212
 
242
213
  async finalizeKeyPointer(key, config) {
243
- const value = key.value || (await this.inflateValue(key, config))
214
+ const value = key.value || (await inflateValue(key, config))
244
215
 
245
216
  return {
246
217
  core: key.context.getCore(key.core),
@@ -251,30 +222,6 @@ class Hyperbee extends EventEmitter {
251
222
  }
252
223
  }
253
224
 
254
- async inflateValue(key, config) {
255
- if (key.value) return key.value
256
- if (!key.valuePointer) return null
257
-
258
- const ptr = key.valuePointer
259
-
260
- if (ptr.split === 0) {
261
- const block = await ptr.context.getBlock(ptr.seq, ptr.core, config)
262
- return block.values[ptr.offset]
263
- }
264
-
265
- const blockPromises = new Array(ptr.split + 1)
266
- for (let i = 0; i < blockPromises.length; i++) {
267
- blockPromises[i] = ptr.context.getBlock(ptr.seq - ptr.split + i, ptr.core, config)
268
- }
269
- const blocks = await Promise.all(blockPromises)
270
- const splitValue = new Array(blockPromises.length)
271
- for (let i = 0; i < splitValue.length - 1; i++) {
272
- splitValue[i] = blocks[i].values[0]
273
- }
274
- splitValue[splitValue.length - 1] = blocks[blocks.length - 1].buffer[ptr.offset]
275
- return b4a.concat(splitValue)
276
- }
277
-
278
225
  async bootstrap(config) {
279
226
  if (!this.root) await this.ready()
280
227
  if (this.unbatch) await this._rollback(config)
@@ -304,16 +251,16 @@ class Hyperbee extends EventEmitter {
304
251
 
305
252
  if (expected === this.unbatch) {
306
253
  this.context = context
307
- this.root = length === 0 ? EMPTY : context.createTreeNode(0, length - 1, 0, false, null)
308
- this.unbatch = 0
309
- this.emit('update')
254
+ this._setRoot(this._nodeAtSeq(length - 1), true)
310
255
  }
311
256
  }
312
257
 
313
- update(root = null) {
314
- this.root = root
258
+ _setRoot(root, emit) {
259
+ if (!root.equivalentTo(this.root)) {
260
+ this.root = root
261
+ if (emit) this.emit('update')
262
+ }
315
263
  this.unbatch = 0
316
- this.emit('update')
317
264
  }
318
265
 
319
266
  async get(key, opts) {
@@ -353,89 +300,6 @@ module.exports = Hyperbee
353
300
 
354
301
  function noop() {}
355
302
 
356
- function inflateKey(context, d, ptr, block, config) {
357
- if (d.type === OP_COHORT) return inflateKeyCohort(context, d, ptr, block, config)
358
- return inflateKeyDelta(context, d, ptr, block, config)
359
- }
360
-
361
- async function inflateKeyDelta(context, d, ptr, block, config) {
362
- const k = d.pointer
363
-
364
- if (!k) return new DeltaOp(false, d.type, d.index, null)
365
-
366
- const blk =
367
- k.seq === ptr.seq && k.core === 0 && ptr.core === 0
368
- ? block
369
- : await context.getBlock(k.seq, k.core, config)
370
-
371
- const bk = blk.keys[k.offset]
372
-
373
- let vp = null
374
-
375
- if (bk.valuePointer) {
376
- const p = bk.valuePointer
377
- const ctx = await context.getContext(k.core, config)
378
- vp = new ValuePointer(ctx, p.core, p.seq, p.offset, p.split)
379
- }
380
-
381
- const kp = new KeyPointer(context, k.core, k.seq, k.offset, false, bk.key, bk.value, vp)
382
- return new DeltaOp(false, d.type, d.index, kp)
383
- }
384
-
385
- async function inflateKeyCohort(context, d, ptr, block, config) {
386
- const co = d.pointer
387
-
388
- const blk =
389
- co.seq === ptr.seq && co.core === 0 && ptr.core === 0
390
- ? block
391
- : await context.getBlock(co.seq, co.core, config)
392
-
393
- const cohort = blk.cohorts[co.offset]
394
- const promises = new Array(cohort.length)
395
-
396
- for (let i = 0; i < cohort.length; i++) {
397
- const p = cohort[i]
398
- const k = inflateKeyDelta(context, p, co, blk, config)
399
- promises[i] = k
400
- }
401
-
402
- const p = new Pointer(context, co.core, co.seq, co.offset)
403
- return new DeltaCohort(false, p, await Promise.all(promises))
404
- }
405
-
406
- async function inflateChild(context, d, ptr, block, config) {
407
- if (d.type === OP_COHORT) return inflateChildCohort(context, d, ptr, block, config)
408
- if (d.pointer && !context.hasCore(d.pointer.core)) await context.update(config)
409
- return inflateChildDelta(context, d, ptr, block, config)
410
- }
411
-
412
- function inflateChildDelta(context, d, ptr, block, config) {
413
- const p = d.pointer
414
- const c = p && context.createTreeNode(p.core, p.seq, p.offset, false, null)
415
- return new DeltaOp(false, d.type, d.index, c)
416
- }
417
-
418
- async function inflateChildCohort(context, d, ptr, block, config) {
419
- const co = d.pointer
420
-
421
- const blk =
422
- co.seq === ptr.seq && co.core === 0 && ptr.core === 0
423
- ? block
424
- : await context.getBlock(co.seq, co.core, config)
425
-
426
- const cohort = blk.cohorts[co.offset]
427
- const deltas = new Array(cohort.length)
428
-
429
- for (let i = 0; i < cohort.length; i++) {
430
- const c = cohort[i]
431
- if (c.pointer && !context.hasCore(c.pointer.core)) await context.update(config)
432
- deltas[i] = inflateChildDelta(context, c, co, blk, config)
433
- }
434
-
435
- const p = new Pointer(context, co.core, co.seq, co.offset)
436
- return new DeltaCohort(false, p, deltas)
437
- }
438
-
439
303
  function toEncryptionProvider(encryption) {
440
304
  if (encryption) return (key) => encryption
441
305
  return () => null
package/lib/context.js CHANGED
@@ -161,6 +161,9 @@ class CoreContext {
161
161
  const hc = this.getCore(core)
162
162
  const buffer = await hc.get(seq, config)
163
163
  if (buffer === null) throw BLOCK_NOT_AVAILABLE()
164
+
165
+ if (config.trace !== null) config.trace(core, seq)
166
+
164
167
  const block = decodeBlock(buffer, seq)
165
168
  return block
166
169
  }
package/lib/inflate.js ADDED
@@ -0,0 +1,140 @@
1
+ const b4a = require('b4a')
2
+ const { Pointer, KeyPointer, ValuePointer, TreeNode } = require('./tree.js')
3
+ const { DeltaOp, DeltaCohort, OP_COHORT } = require('./compression.js')
4
+
5
+ exports.inflate = async function inflate(ptr, config) {
6
+ if (ptr.value) return ptr.value
7
+
8
+ const [block, context] = await Promise.all([
9
+ ptr.context.getBlock(ptr.seq, ptr.core, config),
10
+ ptr.context.getContext(ptr.core, config)
11
+ ])
12
+
13
+ const tree = block.tree[ptr.offset]
14
+
15
+ const keys = new Array(tree.keys.length)
16
+ const children = new Array(tree.children.length)
17
+
18
+ for (let i = 0; i < keys.length; i++) {
19
+ const d = tree.keys[i]
20
+ keys[i] = inflateKey(context, d, ptr, block, config)
21
+ }
22
+
23
+ for (let i = 0; i < children.length; i++) {
24
+ const d = tree.children[i]
25
+ children[i] = inflateChild(context, d, ptr, block, config)
26
+ }
27
+
28
+ const [k, c] = await Promise.all([Promise.all(keys), Promise.all(children)])
29
+
30
+ const value = new TreeNode(k, c)
31
+ if (!ptr.value) ptr.value = value
32
+ return ptr.value
33
+ }
34
+
35
+ function inflateKey(context, d, ptr, block, config) {
36
+ if (d.type === OP_COHORT) return inflateKeyCohort(context, d, ptr, block, config)
37
+ return inflateKeyDelta(context, d, ptr, block, config)
38
+ }
39
+
40
+ async function inflateKeyDelta(context, d, ptr, block, config) {
41
+ const k = d.pointer
42
+
43
+ if (!k) return new DeltaOp(false, d.type, d.index, null)
44
+
45
+ const blk =
46
+ k.seq === ptr.seq && k.core === 0 && ptr.core === 0
47
+ ? block
48
+ : await context.getBlock(k.seq, k.core, config)
49
+
50
+ const bk = blk.keys[k.offset]
51
+
52
+ let vp = null
53
+
54
+ if (bk.valuePointer) {
55
+ const p = bk.valuePointer
56
+ const ctx = await context.getContext(k.core, config)
57
+ vp = new ValuePointer(ctx, p.core, p.seq, p.offset, p.split)
58
+ }
59
+
60
+ const kp = new KeyPointer(context, k.core, k.seq, k.offset, false, bk.key, bk.value, vp)
61
+ return new DeltaOp(false, d.type, d.index, kp)
62
+ }
63
+
64
+ exports.inflateValue = async function inflateValue(key, config) {
65
+ if (key.value) return key.value
66
+ if (!key.valuePointer) return null
67
+
68
+ const ptr = key.valuePointer
69
+
70
+ if (ptr.split === 0) {
71
+ const block = await ptr.context.getBlock(ptr.seq, ptr.core, config)
72
+ return block.values[ptr.offset]
73
+ }
74
+
75
+ const blockPromises = new Array(ptr.split + 1)
76
+ for (let i = 0; i < blockPromises.length; i++) {
77
+ blockPromises[i] = ptr.context.getBlock(ptr.seq - ptr.split + i, ptr.core, config)
78
+ }
79
+ const blocks = await Promise.all(blockPromises)
80
+ const splitValue = new Array(blockPromises.length)
81
+ for (let i = 0; i < splitValue.length - 1; i++) {
82
+ splitValue[i] = blocks[i].values[0]
83
+ }
84
+ splitValue[splitValue.length - 1] = blocks[blocks.length - 1].buffer[ptr.offset]
85
+ return b4a.concat(splitValue)
86
+ }
87
+
88
+ async function inflateKeyCohort(context, d, ptr, block, config) {
89
+ const co = d.pointer
90
+
91
+ const blk =
92
+ co.seq === ptr.seq && co.core === 0 && ptr.core === 0
93
+ ? block
94
+ : await context.getBlock(co.seq, co.core, config)
95
+
96
+ const cohort = blk.cohorts[co.offset]
97
+ const promises = new Array(cohort.length)
98
+
99
+ for (let i = 0; i < cohort.length; i++) {
100
+ const p = cohort[i]
101
+ const k = inflateKeyDelta(context, p, co, blk, config)
102
+ promises[i] = k
103
+ }
104
+
105
+ const p = new Pointer(context, co.core, co.seq, co.offset)
106
+ return new DeltaCohort(false, p, await Promise.all(promises))
107
+ }
108
+
109
+ async function inflateChild(context, d, ptr, block, config) {
110
+ if (d.type === OP_COHORT) return inflateChildCohort(context, d, ptr, block, config)
111
+ if (d.pointer && !context.hasCore(d.pointer.core)) await context.update(config)
112
+ return inflateChildDelta(context, d, ptr, block, config)
113
+ }
114
+
115
+ function inflateChildDelta(context, d, ptr, block, config) {
116
+ const p = d.pointer
117
+ const c = p && context.createTreeNode(p.core, p.seq, p.offset, false, null)
118
+ return new DeltaOp(false, d.type, d.index, c)
119
+ }
120
+
121
+ async function inflateChildCohort(context, d, ptr, block, config) {
122
+ const co = d.pointer
123
+
124
+ const blk =
125
+ co.seq === ptr.seq && co.core === 0 && ptr.core === 0
126
+ ? block
127
+ : await context.getBlock(co.seq, co.core, config)
128
+
129
+ const cohort = blk.cohorts[co.offset]
130
+ const deltas = new Array(cohort.length)
131
+
132
+ for (let i = 0; i < cohort.length; i++) {
133
+ const c = cohort[i]
134
+ if (c.pointer && !context.hasCore(c.pointer.core)) await context.update(config)
135
+ deltas[i] = inflateChildDelta(context, c, co, blk, config)
136
+ }
137
+
138
+ const p = new Pointer(context, co.core, co.seq, co.offset)
139
+ return new DeltaCohort(false, p, deltas)
140
+ }
@@ -1,22 +1,33 @@
1
1
  class SessionConfig {
2
- constructor(activeRequests, timeout, wait) {
2
+ constructor(activeRequests, timeout, wait, trace) {
3
3
  this.activeRequests = activeRequests
4
4
  this.timeout = timeout
5
5
  this.wait = wait
6
+ this.trace = trace
6
7
  }
7
8
 
8
- sub(activeRequests, timeout, wait) {
9
- if (this.activeRequests === activeRequests && this.timeout === timeout && this.wait === wait) {
9
+ sub(activeRequests, timeout, wait, trace) {
10
+ if (
11
+ this.activeRequests === activeRequests &&
12
+ this.timeout === timeout &&
13
+ this.wait === wait &&
14
+ this.trace === trace
15
+ ) {
10
16
  return this
11
17
  }
12
18
 
13
- return new SessionConfig(activeRequests, timeout, wait)
19
+ return new SessionConfig(activeRequests, timeout, wait, trace)
14
20
  }
15
21
 
16
22
  options(opts) {
17
23
  if (!opts) return this
18
- const { activeRequests = this.activeRequests, timeout = this.timeout, wait = this.wait } = opts
19
- return this.sub(activeRequests, timeout, wait)
24
+ const {
25
+ activeRequests = this.activeRequests,
26
+ timeout = this.timeout,
27
+ wait = this.wait,
28
+ trace = this.trace
29
+ } = opts
30
+ return this.sub(activeRequests, timeout, wait, trace)
20
31
  }
21
32
  }
22
33
 
package/lib/tree.js CHANGED
@@ -28,6 +28,22 @@ class Pointer {
28
28
  this.changedBy = null
29
29
  }
30
30
 
31
+ // Compare two pointers to see if they point to equivalent positions
32
+ equivalentTo(other) {
33
+ // EMPTY is a special case that can look equivalent to a first entry
34
+ // in a hypercore but actually contains different data.
35
+ if (other === exports.EMPTY) return this === exports.EMPTY
36
+ if (!other) return false
37
+
38
+ return (
39
+ this.seq === other.seq &&
40
+ this.offset === other.offset &&
41
+ this.changed === other.changed &&
42
+ this.context === other.context &&
43
+ this.core === other.core
44
+ )
45
+ }
46
+
31
47
  retain() {
32
48
  this.retained = this.context.cache.retained + 1
33
49
  }
package/lib/write.js CHANGED
@@ -1,8 +1,8 @@
1
1
  const b4a = require('b4a')
2
- const c = require('compact-encoding')
3
2
  const { OP_COHORT } = require('./compression.js')
4
3
  const { encodeBlock, TYPE_COMPAT, TYPE_LATEST } = require('./encoding.js')
5
4
  const { Pointer, KeyPointer, ValuePointer, TreeNode, INSERTED, NEEDS_SPLIT } = require('./tree.js')
5
+ const { inflateValue } = require('./inflate.js')
6
6
 
7
7
  const PREFERRED_BLOCK_SIZE = 4096
8
8
  const INLINE_VALUE_SIZE = 1024
@@ -41,14 +41,17 @@ module.exports = class WriteBatch {
41
41
  }
42
42
 
43
43
  tryPut(key, value) {
44
+ this.checkIfClosed()
44
45
  this.ops.push({ put: true, applied: false, key, value })
45
46
  }
46
47
 
47
48
  tryDelete(key) {
49
+ this.checkIfClosed()
48
50
  this.ops.push({ put: false, applied: false, key, value: null })
49
51
  }
50
52
 
51
53
  tryClear() {
54
+ this.checkIfClosed()
52
55
  this.ops = []
53
56
  this.length = 0
54
57
  }
@@ -76,9 +79,12 @@ module.exports = class WriteBatch {
76
79
  }
77
80
 
78
81
  async flush() {
82
+ this.checkIfClosed()
79
83
  await this.lock()
80
84
 
81
85
  try {
86
+ this.checkIfClosed()
87
+
82
88
  const ops = this.ops
83
89
 
84
90
  const root = await this.tree.bootstrap(this.config)
@@ -100,10 +106,10 @@ module.exports = class WriteBatch {
100
106
  }
101
107
 
102
108
  await this._flush()
103
- await this.snapshot.close()
109
+ await this.close()
104
110
 
105
111
  if (this.autoUpdate) {
106
- this.tree.update(this.root)
112
+ this.tree._setRoot(this.root, true)
107
113
  }
108
114
  } finally {
109
115
  this._unlock()
@@ -140,7 +146,7 @@ module.exports = class WriteBatch {
140
146
  c = b4a.compare(target, m.key)
141
147
 
142
148
  if (c === 0) {
143
- const existing = await snap.inflateValue(m, conf)
149
+ const existing = await inflateValue(m, conf)
144
150
  if (b4a.equals(existing, value)) return false
145
151
  v.setValue(this.tree.context, mid, value)
146
152
  for (let i = 0; i < stack.length; i++) stack[i].changed = true
@@ -161,7 +167,7 @@ module.exports = class WriteBatch {
161
167
  if (status >= 0) {
162
168
  // already exists, upsert if changed
163
169
  const m = v.keys.uget(status)
164
- const existing = await snap.inflateValue(m, conf)
170
+ const existing = await inflateValue(m, conf)
165
171
  if (b4a.equals(existing, value)) return false
166
172
  v.setValue(this.tree.context, status, value)
167
173
  }
@@ -517,9 +523,7 @@ module.exports = class WriteBatch {
517
523
  buffers[i] = encodeBlock(blocks[i])
518
524
  }
519
525
 
520
- if (this.closed) {
521
- throw new Error('Write batch is closed')
522
- }
526
+ this.checkIfClosed()
523
527
 
524
528
  await context.core.append(buffers)
525
529
 
@@ -535,6 +539,12 @@ module.exports = class WriteBatch {
535
539
  context.cache.retained++
536
540
  context.cache.gc()
537
541
  }
542
+
543
+ checkIfClosed() {
544
+ if (this.closed) {
545
+ throw new Error('Write batch is closed')
546
+ }
547
+ }
538
548
  }
539
549
 
540
550
  async function toCompatType(context, batch, ops) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperbee2",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "Scalable P2P BTree",
5
5
  "main": "index.js",
6
6
  "files": [