hyperbee2 2.8.0 → 2.9.1

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,28 @@ class Hyperbee extends EventEmitter {
138
136
  return new WriteBatch(this, opts)
139
137
  }
140
138
 
139
+ update(root = null) {
140
+ if (root === null) root = this._lastNodeInCore()
141
+ this._setRoot(root, true)
142
+ }
143
+
144
+ _lastNodeInCore() {
145
+ return this._nodeAtSeq(this.context.core.length - 1)
146
+ }
147
+
148
+ _nodeAtSeq(seq) {
149
+ return seq < 0 ? EMPTY : this.context.createTreeNode(0, seq, 0, false, null)
150
+ }
151
+
141
152
  async ready() {
142
153
  if (!this.core.opened) await this.core.ready()
143
154
  if (this.root) return
144
155
  if (this.preload) await this.preload()
145
156
  if (this.root) return
146
157
 
147
- this.root =
148
- this.context.core.length === 0
149
- ? EMPTY
150
- : this.context.createTreeNode(0, this.core.length - 1, 0, false, null)
151
-
158
+ this._setRoot(this._lastNodeInCore(), false)
152
159
  if (this.autoUpdate) {
153
- this.core.on('append', () => {
154
- this.update()
155
- })
160
+ this.core.on('append', this.update.bind(this, null))
156
161
  }
157
162
 
158
163
  this.emit('ready')
@@ -202,45 +207,16 @@ class Hyperbee extends EventEmitter {
202
207
  return ptr.value
203
208
  }
204
209
 
205
- // TODO: unslab these
206
210
  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)
211
+ if (!ptr.value) {
212
+ await inflate(ptr, config)
230
213
  }
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
214
  this.bump(ptr)
238
-
239
215
  return ptr.value
240
216
  }
241
217
 
242
218
  async finalizeKeyPointer(key, config) {
243
- const value = key.value || (await this.inflateValue(key, config))
219
+ const value = key.value || (await inflateValue(key, config))
244
220
 
245
221
  return {
246
222
  core: key.context.getCore(key.core),
@@ -251,30 +227,6 @@ class Hyperbee extends EventEmitter {
251
227
  }
252
228
  }
253
229
 
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
230
  async bootstrap(config) {
279
231
  if (!this.root) await this.ready()
280
232
  if (this.unbatch) await this._rollback(config)
@@ -304,16 +256,16 @@ class Hyperbee extends EventEmitter {
304
256
 
305
257
  if (expected === this.unbatch) {
306
258
  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')
259
+ this._setRoot(this._nodeAtSeq(length - 1), true)
310
260
  }
311
261
  }
312
262
 
313
- update(root = null) {
314
- this.root = root
263
+ _setRoot(root, emit) {
264
+ if (root === null || !root.equivalentTo(this.root)) {
265
+ this.root = root
266
+ if (emit) this.emit('update')
267
+ }
315
268
  this.unbatch = 0
316
- this.emit('update')
317
269
  }
318
270
 
319
271
  async get(key, opts) {
@@ -353,89 +305,6 @@ module.exports = Hyperbee
353
305
 
354
306
  function noop() {}
355
307
 
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
308
  function toEncryptionProvider(encryption) {
440
309
  if (encryption) return (key) => encryption
441
310
  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.1",
4
4
  "description": "Scalable P2P BTree",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -41,12 +41,12 @@
41
41
  },
42
42
  "repository": {
43
43
  "type": "git",
44
- "url": "https://github.com/mafintosh/hyperbee2.git"
44
+ "url": "https://github.com/holepunchto/hyperbee2.git"
45
45
  },
46
- "author": "Mathias Buus (@mafintosh)",
46
+ "author": "Holepunch Inc.",
47
47
  "license": "Apache-2.0",
48
48
  "bugs": {
49
- "url": "https://github.com/mafintosh/hyperbee2/issues"
49
+ "url": "https://github.com/holepunchto/hyperbee2/issues"
50
50
  },
51
- "homepage": "https://github.com/mafintosh/hyperbee2"
51
+ "homepage": "https://github.com/holepunchto/hyperbee2"
52
52
  }