hypercore 10.9.2 → 10.11.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/README.md CHANGED
@@ -197,7 +197,37 @@ for await (const data of fullStream) {
197
197
  }
198
198
  ```
199
199
 
200
- #### `await core.clear(start, [end])`
200
+ #### `const bs = core.createByteStream([options])`
201
+
202
+ Make a byte stream to read a range of bytes.
203
+
204
+ ``` js
205
+ // Read the full core
206
+ const fullStream = core.createByteStream()
207
+
208
+ // Read from byte 3, and from there read 50 bytes
209
+ const partialStream = core.createByteStream({ byteOffset: 3, byteLength: 50 })
210
+
211
+ // Consume it as an async iterator
212
+ for await (const data of fullStream) {
213
+ console.log('data:', data)
214
+ }
215
+
216
+ // Or pipe it somewhere like any stream:
217
+ partialStream.pipe(process.stdout)
218
+ ```
219
+
220
+ `options` include:
221
+
222
+ ``` js
223
+ {
224
+ byteOffset: 0,
225
+ byteLength: core.byteLength - options.byteOffset,
226
+ prefetch: 32
227
+ }
228
+ ```
229
+
230
+ #### `const cleared = await core.clear(start, [end], [options])`
201
231
 
202
232
  Clear stored blocks between `start` and `end`, reclaiming storage when possible.
203
233
 
@@ -208,6 +238,13 @@ await core.clear(0, 10) // clear block 0-10 from your local cache
208
238
 
209
239
  The core will also gossip to peers it is connected to, that is no longer has these blocks.
210
240
 
241
+ `options` include:
242
+ ```js
243
+ {
244
+ diff: false // Returned `cleared` bytes object is null unless you enable this
245
+ }
246
+ ```
247
+
211
248
  #### `await core.truncate(newLength, [forkId])`
212
249
 
213
250
  Truncate the core to a smaller length.
@@ -215,6 +252,10 @@ Truncate the core to a smaller length.
215
252
  Per default this will update the fork id of the core to `+ 1`, but you can set the fork id you prefer with the option.
216
253
  Note that the fork id should be monotonely incrementing.
217
254
 
255
+ #### `await core.purge()`
256
+
257
+ Purge the hypercore from your storage, completely removing all data.
258
+
218
259
  #### `const hash = await core.treeHash([length])`
219
260
 
220
261
  Get the Merkle Tree hash of the core at a given length, defaulting to the current length of the core.
package/index.js CHANGED
@@ -14,7 +14,7 @@ const Core = require('./lib/core')
14
14
  const BlockEncryption = require('./lib/block-encryption')
15
15
  const Info = require('./lib/info')
16
16
  const Download = require('./lib/download')
17
- const { ReadStream, WriteStream } = require('./lib/streams')
17
+ const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
18
18
  const { BAD_ARGUMENT, SESSION_CLOSED, SESSION_NOT_WRITABLE, SNAPSHOT_NOT_AVAILABLE } = require('./lib/errors')
19
19
 
20
20
  const promises = Symbol.for('hypercore.promises')
@@ -173,13 +173,14 @@ module.exports = class Hypercore extends EventEmitter {
173
173
  const directory = storage
174
174
  const toLock = opts.unlocked ? null : (opts.lock || 'oplog')
175
175
  const pool = opts.pool || (opts.poolSize ? RAF.createPool(opts.poolSize) : null)
176
+ const rmdir = !!opts.rmdir
176
177
 
177
178
  return createFile
178
179
 
179
180
  function createFile (name) {
180
181
  const lock = toLock === null ? false : isFile(name, toLock)
181
182
  const sparse = isFile(name, 'data') || isFile(name, 'bitfield') || isFile(name, 'tree')
182
- return new RAF(name, { directory, lock, sparse, pool: lock ? null : pool })
183
+ return new RAF(name, { directory, lock, sparse, pool: lock ? null : pool, rmdir })
183
184
  }
184
185
 
185
186
  function isFile (name, n) {
@@ -528,9 +529,16 @@ module.exports = class Hypercore extends EventEmitter {
528
529
  for (const s of this.sessions) s.emit('conflict', proof.upgrade.length, proof.fork, proof)
529
530
 
530
531
  const err = new Error('Two conflicting signatures exist for length ' + proof.upgrade.length)
532
+ await this._closeAllSessions(err)
533
+ }
534
+
535
+ async _closeAllSessions (err) {
536
+ // this.sessions modifies itself when a session closes
537
+ // This way we ensure we indeed iterate over all sessions
538
+ const sessions = [...this.sessions]
531
539
 
532
540
  const all = []
533
- for (const s of this.sessions) all.push(s.close(err))
541
+ for (const s of sessions) all.push(s.close(err))
534
542
  await Promise.allSettled(all)
535
543
  }
536
544
 
@@ -737,9 +745,18 @@ module.exports = class Hypercore extends EventEmitter {
737
745
  end = start + 1
738
746
  }
739
747
 
740
- if (start >= end) return
748
+ const cleared = (opts && opts.diff) ? { blocks: 0 } : null
749
+
750
+ if (start >= end) return cleared
751
+
752
+ await this.core.clear(start, end, cleared)
741
753
 
742
- await this.core.clear(start, end)
754
+ return cleared
755
+ }
756
+
757
+ async purge () {
758
+ await this._closeAllSessions(null)
759
+ await this.core.purge()
743
760
  }
744
761
 
745
762
  async _get (index, opts) {
@@ -794,6 +811,10 @@ module.exports = class Hypercore extends EventEmitter {
794
811
  return new WriteStream(this, opts)
795
812
  }
796
813
 
814
+ createByteStream (opts) {
815
+ return new ByteStream(this, opts)
816
+ }
817
+
797
818
  download (range) {
798
819
  const req = this._download(range)
799
820
 
package/lib/core.js CHANGED
@@ -5,6 +5,7 @@ const Mutex = require('./mutex')
5
5
  const MerkleTree = require('./merkle-tree')
6
6
  const BlockStore = require('./block-store')
7
7
  const Bitfield = require('./bitfield')
8
+ const Info = require('./info')
8
9
  const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = require('./errors')
9
10
  const m = require('./messages')
10
11
 
@@ -238,7 +239,7 @@ module.exports = class Core {
238
239
  }
239
240
  }
240
241
 
241
- async clear (start, end) {
242
+ async clear (start, end, cleared) {
242
243
  await this._mutex.lock()
243
244
 
244
245
  try {
@@ -265,13 +266,20 @@ module.exports = class Core {
265
266
  end = this.bitfield.firstSet(end)
266
267
 
267
268
  if (end === -1) end = this.tree.length
269
+ if (start >= end || start >= this.tree.length) return
268
270
 
269
271
  const offset = await this.tree.byteOffset(start * 2)
270
272
  const [byteEnd, byteEndLength] = await this.tree.byteRange((end - 1) * 2)
271
273
  const length = (byteEnd + byteEndLength) - offset
272
274
 
275
+ const before = cleared ? await Info.bytesUsed(this.blocks.storage) : null
276
+
273
277
  await this.blocks.clear(offset, length)
274
278
 
279
+ const after = cleared ? await Info.bytesUsed(this.blocks.storage) : null
280
+
281
+ if (cleared) cleared.blocks = Math.max(before - after, 0)
282
+
275
283
  this.onupdate(0, entry.bitfield, null, null)
276
284
 
277
285
  if (this._shouldFlush()) await this._flushOplog()
@@ -280,6 +288,25 @@ module.exports = class Core {
280
288
  }
281
289
  }
282
290
 
291
+ async purge () {
292
+ return new Promise((resolve, reject) => {
293
+ let missing = 4
294
+ let error = null
295
+
296
+ this.oplog.storage.unlink(done)
297
+ this.tree.storage.unlink(done)
298
+ this.bitfield.storage.unlink(done)
299
+ this.blocks.storage.unlink(done)
300
+
301
+ function done (err) {
302
+ if (err) error = err
303
+ if (--missing) return
304
+ if (error) reject(error)
305
+ else resolve()
306
+ }
307
+ })
308
+ }
309
+
283
310
  async append (values, auth = this.defaultAuth, hooks = {}) {
284
311
  await this._mutex.lock()
285
312
 
package/lib/info.js CHANGED
@@ -27,27 +27,27 @@ module.exports = class Info {
27
27
  const { oplog, tree, blocks, bitfield } = session.core
28
28
  try {
29
29
  return {
30
- oplog: await bytesUsed(oplog.storage),
31
- tree: await bytesUsed(tree.storage),
32
- blocks: await bytesUsed(blocks.storage),
33
- bitfield: await bytesUsed(bitfield.storage)
30
+ oplog: await Info.bytesUsed(oplog.storage),
31
+ tree: await Info.bytesUsed(tree.storage),
32
+ blocks: await Info.bytesUsed(blocks.storage),
33
+ bitfield: await Info.bytesUsed(bitfield.storage)
34
34
  }
35
35
  } catch {
36
36
  return null
37
37
  }
38
+ }
38
39
 
39
- function bytesUsed (file) {
40
- return new Promise((resolve, reject) => {
41
- file.stat((err, st) => {
42
- if (err) {
43
- resolve(0) // prob just file not found (TODO, improve)
44
- } else if (typeof st.blocks !== 'number') {
45
- reject(new Error('cannot determine bytes used'))
46
- } else {
47
- resolve(st.blocks * 512)
48
- }
49
- })
40
+ static bytesUsed (file) {
41
+ return new Promise((resolve, reject) => {
42
+ file.stat((err, st) => {
43
+ if (err) {
44
+ resolve(0) // prob just file not found (TODO, improve)
45
+ } else if (typeof st.blocks !== 'number') {
46
+ reject(new Error('cannot determine bytes used'))
47
+ } else {
48
+ resolve(st.blocks * 512)
49
+ }
50
50
  })
51
- }
51
+ })
52
52
  }
53
53
  }
package/lib/streams.js CHANGED
@@ -54,3 +54,77 @@ class WriteStream extends Writable {
54
54
  }
55
55
 
56
56
  exports.WriteStream = WriteStream
57
+
58
+ class ByteStream extends Readable {
59
+ constructor (core, opts = {}) {
60
+ super()
61
+
62
+ this._core = core
63
+ this._index = 0
64
+ this._range = null
65
+
66
+ this._byteOffset = opts.byteOffset || 0
67
+ this._byteLength = typeof opts.byteLength === 'number' ? opts.byteLength : -1
68
+ this._prefetch = typeof opts.prefetch === 'number' ? opts.prefetch : 32
69
+
70
+ this._applyOffset = this._byteOffset > 0
71
+ }
72
+
73
+ _open (cb) {
74
+ this._openp().then(cb, cb)
75
+ }
76
+
77
+ _read (cb) {
78
+ this._readp().then(cb, cb)
79
+ }
80
+
81
+ async _openp () {
82
+ if (this._byteLength === -1) {
83
+ await this._core.update()
84
+ this._byteLength = Math.max(this._core.byteLength - this._byteOffset, 0)
85
+ }
86
+ }
87
+
88
+ async _readp () {
89
+ let data = null
90
+
91
+ if (this._byteLength === 0) {
92
+ this.push(null)
93
+ return
94
+ }
95
+
96
+ let relativeOffset = 0
97
+
98
+ if (this._applyOffset) {
99
+ this._applyOffset = false
100
+
101
+ const [block, byteOffset] = await this._core.seek(this._byteOffset)
102
+
103
+ this._index = block
104
+ relativeOffset = byteOffset
105
+ }
106
+
107
+ this._predownload(this._index + 1)
108
+ data = await this._core.get(this._index++)
109
+
110
+ if (relativeOffset > 0) data = data.subarray(relativeOffset)
111
+
112
+ if (data.byteLength > this._byteLength) data = data.subarray(0, this._byteLength)
113
+ this._byteLength -= data.byteLength
114
+
115
+ this.push(data)
116
+ if (this._byteLength === 0) this.push(null)
117
+ }
118
+
119
+ _predownload (index) {
120
+ if (this._range) this._range.destroy()
121
+ this._range = this._core.download({ start: index, end: index + this._prefetch, linear: true })
122
+ }
123
+
124
+ _destroy (cb) {
125
+ if (this._range) this._range.destroy()
126
+ cb(null)
127
+ }
128
+ }
129
+
130
+ exports.ByteStream = ByteStream
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.9.2",
3
+ "version": "10.11.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -57,6 +57,7 @@
57
57
  "hyperswarm": "^4.3.6",
58
58
  "random-access-memory": "^6.1.0",
59
59
  "random-access-memory-overlay": "^3.0.0",
60
+ "range-parser": "^1.2.1",
60
61
  "standard": "^17.0.0",
61
62
  "tmp-promise": "^3.0.2"
62
63
  }