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/index.js CHANGED
@@ -6,15 +6,24 @@ 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 { DataPointer, TreeNode, TreeNodePointer, EMPTY } = require('./lib/tree.js')
9
+ const {
10
+ Pointer,
11
+ KeyPointer,
12
+ ValuePointer,
13
+ TreeNode,
14
+ TreeNodePointer,
15
+ EMPTY
16
+ } = require('./lib/tree.js')
17
+ const { DeltaOp, DeltaCohort, OP_COHORT } = require('./lib/compression.js')
10
18
 
11
19
  class Hyperbee {
12
20
  constructor(store, options = {}) {
13
21
  const {
22
+ t = 128, // legacy number for now, should be 128 now
14
23
  key = null,
15
24
  encryption = null,
16
25
  core = key ? store.get(key) : store.get({ key, name: 'bee', encryption }),
17
- context = new CoreContext(store, core, core, encryption),
26
+ context = new CoreContext(store, core, core, encryption, t),
18
27
  maxCacheSize = 4096,
19
28
  cache = new NodeCache(maxCacheSize),
20
29
  root = null,
@@ -111,12 +120,12 @@ class Hyperbee {
111
120
  if (!this.view) await this.store.close()
112
121
  }
113
122
 
114
- createReadStream(range) {
115
- return new RangeStream(this, range)
123
+ createReadStream(options) {
124
+ return new RangeStream(this, options)
116
125
  }
117
126
 
118
- createDiffStream(right, range) {
119
- return new DiffStream(this, right, range)
127
+ createDiffStream(right, options) {
128
+ return new DiffStream(this, right, options)
120
129
  }
121
130
 
122
131
  createChangesStream(options) {
@@ -124,12 +133,21 @@ class Hyperbee {
124
133
  }
125
134
 
126
135
  async peek(range = {}) {
127
- const rs = new KeyValueStream(this, { ...range, limit: 1 })
136
+ const rs = new RangeStream(this, { ...range, limit: 1 })
128
137
  let entry = null
129
138
  for await (const data of rs) entry = data
130
139
  return entry
131
140
  }
132
141
 
142
+ download(range = {}) {
143
+ const rs = new RangeStream(this, range)
144
+ rs.resume()
145
+ return new Promise((resolve, reject) => {
146
+ rs.once('error', reject)
147
+ rs.once('close', resolve)
148
+ })
149
+ }
150
+
133
151
  bump(ptr) {
134
152
  if (ptr.changed) return ptr.value
135
153
  this.cache.bump(ptr)
@@ -137,40 +155,75 @@ class Hyperbee {
137
155
  return ptr.value
138
156
  }
139
157
 
158
+ // TODO: unslab these and parallize
140
159
  async inflate(ptr, activeRequests = this.activeRequests) {
141
160
  if (ptr.value) {
142
161
  this.bump(ptr)
143
162
  return ptr.value
144
163
  }
145
164
 
146
- const block = await ptr.context.getBlock(ptr.seq, ptr.core, activeRequests)
147
- const context = await ptr.context.getContext(ptr.core, activeRequests)
165
+ const [block, context] = await Promise.all([
166
+ ptr.context.getBlock(ptr.seq, ptr.core, activeRequests),
167
+ ptr.context.getContext(ptr.core, activeRequests)
168
+ ])
169
+
148
170
  const tree = block.tree[ptr.offset]
149
171
 
150
172
  const keys = new Array(tree.keys.length)
151
173
  const children = new Array(tree.children.length)
152
174
 
153
175
  for (let i = 0; i < keys.length; i++) {
154
- const k = tree.keys[i]
155
- const blk =
156
- k.seq === ptr.seq && k.core === 0 && ptr.core === 0
157
- ? block
158
- : await context.getBlock(k.seq, k.core, activeRequests)
159
- const d = blk.data[k.offset]
160
- keys[i] = new DataPointer(context, k.core, k.seq, k.offset, false, d.key, d.value)
176
+ const d = tree.keys[i]
177
+ keys[i] = inflateKey(context, d, ptr, block, activeRequests)
161
178
  }
162
179
 
163
180
  for (let i = 0; i < children.length; i++) {
164
- const c = tree.children[i]
165
- children[i] = new TreeNodePointer(context, c.core, c.seq, c.offset, false, null)
181
+ const d = tree.children[i]
182
+ children[i] = inflateChild(context, d, ptr, block, activeRequests)
166
183
  }
167
184
 
168
- ptr.value = new TreeNode(keys, children)
185
+ ptr.value = new TreeNode(await Promise.all(keys), await Promise.all(children))
169
186
  this.bump(ptr)
170
187
 
171
188
  return ptr.value
172
189
  }
173
190
 
191
+ async finalizeKeyPointer(key, activeRequests = this.activeRequests) {
192
+ const value = key.value || (await this.inflateValue(key, activeRequests))
193
+
194
+ return {
195
+ core: key.context.getCore(key.core),
196
+ offset: key.offset,
197
+ seq: key.seq,
198
+ key: key.key,
199
+ value
200
+ }
201
+ }
202
+
203
+ async inflateValue(key, activeRequests = this.activeRequests) {
204
+ if (key.value) return key.value
205
+ if (!key.valuePointer) return null
206
+
207
+ const ptr = key.valuePointer
208
+
209
+ if (ptr.split === 0) {
210
+ const block = await ptr.context.getBlock(ptr.seq, ptr.core, activeRequests)
211
+ return block.values[ptr.offset]
212
+ }
213
+
214
+ const blockPromises = new Array(ptr.split + 1)
215
+ for (let i = 0; i < blockPromises.length; i++) {
216
+ blockPromises[i] = ptr.context.getBlock(ptr.seq - ptr.split + i, ptr.core, activeRequests)
217
+ }
218
+ const blocks = await Promise.all(blockPromises)
219
+ const splitValue = new Array(blockPromises.length)
220
+ for (let i = 0; i < splitValue.length - 1; i++) {
221
+ splitValue[i] = blocks[i].values[0]
222
+ }
223
+ splitValue[splitValue.length - 1] = blocks[blocks.length - 1].buffer[ptr.offset]
224
+ return b4a.concat(splitValue)
225
+ }
226
+
174
227
  async bootstrap(activeRequests = this.activeRequests) {
175
228
  if (!this.root) await this.ready()
176
229
  if (this.unbatch) await this._rollback(activeRequests)
@@ -223,11 +276,11 @@ class Hyperbee {
223
276
 
224
277
  while (s < e) {
225
278
  const mid = (s + e) >> 1
226
- const m = v.keys[mid]
279
+ const m = v.keys.get(mid)
227
280
 
228
281
  c = b4a.compare(key, m.key)
229
282
 
230
- if (c === 0) return m
283
+ if (c === 0) return this.finalizeKeyPointer(m, activeRequests)
231
284
 
232
285
  if (c < 0) e = mid
233
286
  else s = mid + 1
@@ -236,7 +289,7 @@ class Hyperbee {
236
289
  if (!v.children.length) return null
237
290
 
238
291
  const i = c < 0 ? e : s
239
- ptr = v.children[i]
292
+ ptr = v.children.get(i)
240
293
  }
241
294
  }
242
295
  }
@@ -244,3 +297,84 @@ class Hyperbee {
244
297
  module.exports = Hyperbee
245
298
 
246
299
  function noop() {}
300
+
301
+ function inflateKey(context, d, ptr, block, activeRequests) {
302
+ if (d.type === OP_COHORT) return inflateKeyCohort(context, d, ptr, block, activeRequests)
303
+ return inflateKeyDelta(context, d, ptr, block, activeRequests)
304
+ }
305
+
306
+ async function inflateKeyDelta(context, d, ptr, block, activeRequests) {
307
+ const k = d.pointer
308
+
309
+ if (!k) return new DeltaOp(false, d.type, d.index, null)
310
+
311
+ const blk =
312
+ k.seq === ptr.seq && k.core === 0 && ptr.core === 0
313
+ ? block
314
+ : await context.getBlock(k.seq, k.core, activeRequests)
315
+
316
+ const bk = blk.keys[k.offset]
317
+
318
+ let vp = null
319
+
320
+ if (bk.valuePointer) {
321
+ const p = bk.valuePointer
322
+ const ctx = await context.getContext(k.core, activeRequests)
323
+ vp = new ValuePointer(ctx, p.core, p.seq, p.offset, p.split)
324
+ }
325
+
326
+ const kp = new KeyPointer(context, k.core, k.seq, k.offset, false, bk.key, bk.value, vp)
327
+ return new DeltaOp(false, d.type, d.index, kp)
328
+ }
329
+
330
+ async function inflateKeyCohort(context, d, ptr, block, activeRequests) {
331
+ const co = d.pointer
332
+
333
+ const blk =
334
+ co.seq === ptr.seq && co.core === 0 && ptr.core === 0
335
+ ? block
336
+ : await context.getBlock(co.seq, co.core, activeRequests)
337
+
338
+ const cohort = blk.cohorts[co.offset]
339
+ const promises = new Array(cohort.length)
340
+
341
+ for (let i = 0; i < cohort.length; i++) {
342
+ const p = cohort[i]
343
+ const k = inflateKeyDelta(context, p, co, blk, activeRequests)
344
+ promises[i] = k
345
+ }
346
+
347
+ const p = new Pointer(context, co.core, co.seq, co.offset)
348
+ return new DeltaCohort(false, p, await Promise.all(promises))
349
+ }
350
+
351
+ function inflateChild(context, d, ptr, block, activeRequests) {
352
+ if (d.type === OP_COHORT) return inflateChildCohort(context, d, ptr, block, activeRequests)
353
+ return Promise.resolve(inflateChildDelta(context, d, ptr, block, activeRequests))
354
+ }
355
+
356
+ function inflateChildDelta(context, d, ptr, block, activeRequests) {
357
+ const p = d.pointer
358
+ const c = p && new TreeNodePointer(context, p.core, p.seq, p.offset, false, null)
359
+ return new DeltaOp(false, d.type, d.index, c)
360
+ }
361
+
362
+ async function inflateChildCohort(context, d, ptr, block, activeRequests) {
363
+ const co = d.pointer
364
+
365
+ const blk =
366
+ co.seq === ptr.seq && co.core === 0 && ptr.core === 0
367
+ ? block
368
+ : await context.getBlock(co.seq, co.core, activeRequests)
369
+
370
+ const cohort = blk.cohorts[co.offset]
371
+ const deltas = new Array(cohort.length)
372
+
373
+ for (let i = 0; i < cohort.length; i++) {
374
+ const c = cohort[i]
375
+ deltas[i] = inflateChildDelta(context, c, co, blk, activeRequests)
376
+ }
377
+
378
+ const p = new Pointer(context, co.core, co.seq, co.offset)
379
+ return new DeltaCohort(false, p, deltas)
380
+ }
package/lib/cache.js CHANGED
@@ -9,6 +9,13 @@ module.exports = class NodeCache {
9
9
  return this.latest ? this.latest.next : null
10
10
  }
11
11
 
12
+ empty() {
13
+ const maxSize = this.maxSize
14
+ this.maxSize = 0
15
+ this.gc()
16
+ this.maxSize = maxSize
17
+ }
18
+
12
19
  gc() {
13
20
  while (this.size > this.maxSize) {
14
21
  const old = this.oldest()
package/lib/compat.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const encodings = require('protocol-buffers-encodings')
4
4
  const b4a = require('b4a')
5
+ const { OP_COHORT, OP_INSERT } = require('./compression')
5
6
  const varint = encodings.varint
6
7
  const skip = encodings.skip
7
8
 
@@ -12,26 +13,43 @@ function decodeCompat(buffer, seq) {
12
13
  const node = buffer.length > 1 ? decodeNode(buffer) : null
13
14
  const index = node ? decodeYoloIndex(node.index) : { levels: [] }
14
15
  const morphed = {
15
- type: 0,
16
+ type: 1,
16
17
  checkpoint: 0,
17
18
  batch: { start: 0, end: 0 },
18
19
  previous: seq > 1 ? { core: 0, seq: seq - 1 } : null,
20
+ metadata: null,
19
21
  tree: [],
20
- data: node ? [{ key: node.key, value: node.value }] : [],
21
- cores: null
22
+ keys: node ? [{ key: node.key, value: node.value, valuePointer: null }] : [],
23
+ values: [],
24
+ cohorts: []
22
25
  }
23
26
 
24
27
  for (const lvl of index.levels) {
25
28
  const t = { keys: [], children: [] }
26
29
 
27
- for (let i = 0; i < lvl.keys.length; i++) {
28
- const seq = lvl.keys[i]
29
- t.keys.push({ core: 0, seq, offset: 0 })
30
+ if (lvl.keys.length) {
31
+ const cohort = []
32
+ const offset = morphed.cohorts.push(cohort) - 1
33
+
34
+ for (let i = 0; i < lvl.keys.length; i++) {
35
+ const seq = lvl.keys[i]
36
+ cohort.push({ type: OP_INSERT, index: i, pointer: { core: 0, seq, offset: 0 } })
37
+ }
38
+
39
+ t.keys.push({ type: OP_COHORT, index: 0, pointer: { core: 0, seq, offset } })
30
40
  }
31
- for (let i = 0; i < lvl.children.length; i += 2) {
32
- const seq = lvl.children[i]
33
- const offset = lvl.children[i + 1]
34
- t.children.push({ core: 0, seq, offset })
41
+
42
+ if (lvl.children.length) {
43
+ const cohort = []
44
+ const offset = morphed.cohorts.push(cohort) - 1
45
+
46
+ for (let i = 0; i < lvl.children.length; i += 2) {
47
+ const seq = lvl.children[i]
48
+ const offset = lvl.children[i + 1]
49
+ cohort.push({ type: OP_INSERT, index: i / 2, pointer: { core: 0, seq, offset } })
50
+ }
51
+
52
+ t.children.push({ type: OP_COHORT, index: 0, pointer: { core: 0, seq, offset } })
35
53
  }
36
54
 
37
55
  morphed.tree.push(t)
@@ -46,8 +64,16 @@ function encodeCompat(node) {
46
64
  if (node.tree) {
47
65
  for (const t of node.tree) {
48
66
  const lvl = { keys: [], children: [] }
49
- for (const k of t.keys) lvl.keys.push(k.seq)
50
- for (const c of t.children) lvl.children.push(c.seq, c.offset)
67
+ for (const k of t.keys) {
68
+ for (const kk of node.cohorts[k.pointer.offset]) {
69
+ lvl.keys.push(kk.pointer.seq)
70
+ }
71
+ }
72
+ for (const c of t.children) {
73
+ for (const cc of node.cohorts[c.pointer.offset]) {
74
+ lvl.children.push(cc.pointer.seq, cc.pointer.offset)
75
+ }
76
+ }
51
77
  index.levels.push(lvl)
52
78
  }
53
79
  }
@@ -56,8 +82,8 @@ function encodeCompat(node) {
56
82
  encodeYoloIndex(index, bufIndex, 0)
57
83
 
58
84
  const n = {
59
- key: node.data[0].key,
60
- value: node.data[0].value,
85
+ key: node.keys[0].key,
86
+ value: node.keys[0].value,
61
87
  index: bufIndex
62
88
  }
63
89
 
@@ -65,28 +91,6 @@ function encodeCompat(node) {
65
91
  return encodeNode(n, buf, 0)
66
92
  }
67
93
 
68
- function deflateIndex(index) {
69
- const levels = index.map((l) => {
70
- const keys = []
71
- const children = []
72
-
73
- for (let i = 0; i < l.value.keys.length; i++) {
74
- keys.push(l.value.keys[i].seq)
75
- }
76
-
77
- for (let i = 0; i < l.value.children.length; i++) {
78
- children.push(l.value.children[i].seq, l.value.children[i].offset)
79
- }
80
-
81
- return { keys, children }
82
- })
83
-
84
- const idx = { levels }
85
- const buf = b4a.allocUnsafe(encodingLengthYoloIndex(idx))
86
-
87
- return encodeYoloIndex(idx, buf, 0)
88
- }
89
-
90
94
  function decodeLevel(buf, offset, end) {
91
95
  if (!offset) offset = 0
92
96
  if (!end) end = buf.length
@@ -141,7 +145,7 @@ function encodingLengthLevel(obj) {
141
145
  let packedLen = 0
142
146
  for (let i = 0; i < obj.keys.length; i++) {
143
147
  if (!obj.keys[i]) continue
144
- let len = encodings.varint.encodingLength(obj.keys[i])
148
+ const len = encodings.varint.encodingLength(obj.keys[i])
145
149
  packedLen += len
146
150
  }
147
151
  if (packedLen) {
@@ -152,7 +156,7 @@ function encodingLengthLevel(obj) {
152
156
  let packedLen = 0
153
157
  for (let i = 0; i < obj.children.length; i++) {
154
158
  if (!obj.children[i]) continue
155
- let len = encodings.varint.encodingLength(obj.children[i])
159
+ const len = encodings.varint.encodingLength(obj.children[i])
156
160
  packedLen += len
157
161
  }
158
162
  if (packedLen) {
@@ -164,7 +168,7 @@ function encodingLengthLevel(obj) {
164
168
 
165
169
  function encodeLevel(obj, buf, offset) {
166
170
  if (!offset) offset = 0
167
- let oldOffset = offset
171
+ const oldOffset = offset
168
172
  if (obj.keys) {
169
173
  let packedLen = 0
170
174
  for (let i = 0; i < obj.keys.length; i++) {
@@ -239,7 +243,7 @@ function encodingLengthYoloIndex(obj) {
239
243
  if (obj.levels) {
240
244
  for (let i = 0; i < obj.levels.length; i++) {
241
245
  if (!obj.levels[i]) continue
242
- let len = encodingLengthLevel(obj.levels[i])
246
+ const len = encodingLengthLevel(obj.levels[i])
243
247
  length += varint.encodingLength(len)
244
248
  length += 1 + len
245
249
  }
@@ -249,7 +253,7 @@ function encodingLengthYoloIndex(obj) {
249
253
 
250
254
  function encodeYoloIndex(obj, buf, offset) {
251
255
  if (!offset) offset = 0
252
- let oldOffset = offset
256
+ const oldOffset = offset
253
257
  if (obj.levels) {
254
258
  for (let i = 0; i < obj.levels.length; i++) {
255
259
  if (!obj.levels[i]) continue
@@ -318,7 +322,7 @@ function encodingLengthNode(obj) {
318
322
  len = encodings.bytes.encodingLength(obj.key)
319
323
  length += 1 + len
320
324
  if (obj.value) {
321
- let len = encodings.bytes.encodingLength(obj.value)
325
+ const len = encodings.bytes.encodingLength(obj.value)
322
326
  length += 1 + len
323
327
  }
324
328
  return length
@@ -326,7 +330,7 @@ function encodingLengthNode(obj) {
326
330
 
327
331
  function encodeNode(obj, buf, offset) {
328
332
  if (!offset) offset = 0
329
- let oldOffset = offset
333
+ const oldOffset = offset
330
334
  buf[offset++] = 10
331
335
  encodings.bytes.encode(obj.index, buf, offset)
332
336
  offset += encodings.bytes.encode.bytes
@@ -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
+ }