hypercore 10.12.0 → 10.14.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
@@ -303,6 +303,16 @@ To cancel downloading a range simply destroy the range instance.
303
303
  range.destroy()
304
304
  ```
305
305
 
306
+ #### `const session = await core.session([options])`
307
+
308
+ Creates a new Hypercore instance that shares the same underlying core.
309
+
310
+ You must close any session you make.
311
+
312
+ Options are inherited from the parent instance, unless they are re-set.
313
+
314
+ `options` are the same as in the constructor.
315
+
306
316
  #### `const info = await core.info([options])`
307
317
 
308
318
  Get information about this core, such as its total size in bytes.
package/index.js CHANGED
@@ -14,8 +14,16 @@ 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 Batch = require('./lib/batch')
17
18
  const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
18
- const { BAD_ARGUMENT, SESSION_CLOSED, SESSION_NOT_WRITABLE, SNAPSHOT_NOT_AVAILABLE } = require('./lib/errors')
19
+ const {
20
+ BAD_ARGUMENT,
21
+ BATCH_ALREADY_EXISTS,
22
+ BATCH_UNFLUSHED,
23
+ SESSION_CLOSED,
24
+ SESSION_NOT_WRITABLE,
25
+ SNAPSHOT_NOT_AVAILABLE
26
+ } = require('./lib/errors')
19
27
 
20
28
  const promises = Symbol.for('hypercore.promises')
21
29
  const inspect = Symbol.for('nodejs.util.inspect.custom')
@@ -82,6 +90,7 @@ module.exports = class Hypercore extends EventEmitter {
82
90
 
83
91
  this._preappend = preappend.bind(this)
84
92
  this._snapshot = null
93
+ this._batch = opts._batch || null
85
94
  this._findingPeers = 0
86
95
  }
87
96
 
@@ -175,13 +184,14 @@ module.exports = class Hypercore extends EventEmitter {
175
184
  const toLock = opts.unlocked ? null : (opts.lock || 'oplog')
176
185
  const pool = opts.pool || (opts.poolSize ? RAF.createPool(opts.poolSize) : null)
177
186
  const rmdir = !!opts.rmdir
187
+ const writable = opts.writable !== false
178
188
 
179
189
  return createFile
180
190
 
181
191
  function createFile (name) {
182
192
  const lock = toLock === null ? false : isFile(name, toLock)
183
193
  const sparse = isFile(name, 'data') || isFile(name, 'bitfield') || isFile(name, 'tree')
184
- return new RAF(name, { directory, lock, sparse, pool: lock ? null : pool, rmdir })
194
+ return new RAF(name, { directory, lock, sparse, pool: lock ? null : pool, rmdir, writable })
185
195
  }
186
196
 
187
197
  function isFile (name, n) {
@@ -214,7 +224,8 @@ module.exports = class Hypercore extends EventEmitter {
214
224
  timeout,
215
225
  writable,
216
226
  _opening: this.opening,
217
- _sessions: this.sessions
227
+ _sessions: this.sessions,
228
+ _batch: this._batch
218
229
  })
219
230
 
220
231
  s._passCapabilities(this)
@@ -321,11 +332,13 @@ module.exports = class Hypercore extends EventEmitter {
321
332
  async _openCapabilities (keyPair, storage, opts) {
322
333
  if (opts.from) return this._openFromExisting(opts.from, opts)
323
334
 
324
- this.storage = Hypercore.defaultStorage(opts.storage || storage)
335
+ const unlocked = !!opts.unlocked
336
+ this.storage = Hypercore.defaultStorage(opts.storage || storage, { unlocked, writable: !unlocked })
325
337
 
326
338
  this.core = await Core.open(this.storage, {
327
339
  force: opts.force,
328
340
  createIfMissing: opts.createIfMissing,
341
+ readonly: unlocked,
329
342
  overwrite: opts.overwrite,
330
343
  keyPair,
331
344
  crypto: this.crypto,
@@ -684,6 +697,13 @@ module.exports = class Hypercore extends EventEmitter {
684
697
  return true
685
698
  }
686
699
 
700
+ batch () {
701
+ if (this._batch !== null) throw BATCH_ALREADY_EXISTS()
702
+ const batch = new Batch(this)
703
+ for (const session of this.sessions) session._batch = batch
704
+ return batch
705
+ }
706
+
687
707
  async seek (bytes, opts) {
688
708
  if (this.opened === false) await this.opening
689
709
 
@@ -846,6 +866,7 @@ module.exports = class Hypercore extends EventEmitter {
846
866
  }
847
867
 
848
868
  async truncate (newLength = 0, fork = -1) {
869
+ if (this._batch && !this._batch.flushed) throw BATCH_UNFLUSHED()
849
870
  if (this.opened === false) await this.opening
850
871
  if (this.writable === false) throw SESSION_NOT_WRITABLE()
851
872
 
@@ -857,6 +878,7 @@ module.exports = class Hypercore extends EventEmitter {
857
878
  }
858
879
 
859
880
  async append (blocks) {
881
+ if (this._batch && !this._batch.flushed) throw BATCH_UNFLUSHED()
860
882
  if (this.opened === false) await this.opening
861
883
  if (this.writable === false) throw SESSION_NOT_WRITABLE()
862
884
 
package/lib/batch.js ADDED
@@ -0,0 +1,114 @@
1
+ const { SESSION_NOT_WRITABLE, BATCH_ALREADY_FLUSHED } = require('./errors')
2
+
3
+ module.exports = class Batch {
4
+ constructor (session) {
5
+ this.session = session
6
+ this.flushed = false
7
+
8
+ this._appends = []
9
+ this._byteLength = 0
10
+ }
11
+
12
+ get length () {
13
+ return this.session.length + this._appends.length
14
+ }
15
+
16
+ ready () {
17
+ return this.session.ready()
18
+ }
19
+
20
+ async info (opts) {
21
+ const session = this.session
22
+ const info = await session.info(opts)
23
+
24
+ if (info.contiguousLength === info.length) {
25
+ info.contiguousLength = info.length += this._appends.length
26
+ } else {
27
+ info.length += this._appends.length
28
+ }
29
+
30
+ info.byteLength += this._byteLength
31
+
32
+ return info
33
+ }
34
+
35
+ async get (index, opts) {
36
+ const session = this.session
37
+ if (session.opened === false) await session.opening
38
+
39
+ const length = this.session.length
40
+ if (index < length) return this.session.get(index, opts)
41
+
42
+ return this._appends[index - length] || null
43
+ }
44
+
45
+ async truncate (newLength) {
46
+ if (this.flushed) throw BATCH_ALREADY_FLUSHED()
47
+
48
+ const session = this.session
49
+ if (session.opened === false) await session.opening
50
+ if (session.writable === false) throw SESSION_NOT_WRITABLE()
51
+
52
+ const length = session.length
53
+ if (newLength < length) throw new Error('Cannot truncate committed blocks')
54
+
55
+ this._appends.length = newLength - length
56
+ }
57
+
58
+ async append (blocks) {
59
+ if (this.flushed) throw BATCH_ALREADY_FLUSHED()
60
+
61
+ const session = this.session
62
+ if (session.opened === false) await session.opening
63
+ if (session.writable === false) throw SESSION_NOT_WRITABLE()
64
+
65
+ blocks = Array.isArray(blocks) ? blocks : [blocks]
66
+
67
+ const buffers = session.encodeBatch !== null
68
+ ? session.encodeBatch(blocks)
69
+ : new Array(blocks.length)
70
+
71
+ if (session.encodeBatch === null) {
72
+ for (let i = 0; i < blocks.length; i++) {
73
+ const buffer = session._encode(session.valueEncoding, blocks[i])
74
+ buffers[i] = buffer
75
+ this._byteLength += buffer.byteLength
76
+ }
77
+ }
78
+
79
+ this._appends.push(...buffers)
80
+
81
+ const byteLength = session.byteLength + this._byteLength
82
+
83
+ return { length: this.length, byteLength }
84
+ }
85
+
86
+ async flush () {
87
+ if (this.flushed) throw BATCH_ALREADY_FLUSHED()
88
+ this.flushed = true
89
+
90
+ try {
91
+ if (this._appends.length) await this.session.append(this._appends)
92
+ } finally {
93
+ this._clearBatch()
94
+ this._clearAppends()
95
+ }
96
+ }
97
+
98
+ async close () {
99
+ if (this.flushed) throw BATCH_ALREADY_FLUSHED()
100
+ this.flushed = true
101
+
102
+ this._clearBatch()
103
+ this._clearAppends()
104
+ }
105
+
106
+ _clearAppends () {
107
+ this._appends = []
108
+ this._byteLength = 0
109
+ }
110
+
111
+ _clearBatch () {
112
+ for (const session of this.session.sessions) session._batch = null
113
+ }
114
+ }
package/lib/core.js CHANGED
@@ -72,7 +72,8 @@ module.exports = class Core {
72
72
 
73
73
  const oplog = new Oplog(oplogFile, {
74
74
  headerEncoding: m.oplog.header,
75
- entryEncoding: m.oplog.entry
75
+ entryEncoding: m.oplog.entry,
76
+ headerOnly: opts.readonly
76
77
  })
77
78
 
78
79
  let { header, entries } = await oplog.open()
package/lib/errors.js CHANGED
@@ -64,6 +64,18 @@ module.exports = class HypercoreError extends Error {
64
64
  return new HypercoreError(msg, 'SESSION_CLOSED', HypercoreError.SESSION_CLOSED)
65
65
  }
66
66
 
67
+ static BATCH_UNFLUSHED (msg = 'Batch not yet flushed') {
68
+ return new HypercoreError(msg, 'BATCH_UNFLUSHED', HypercoreError.BATCH_UNFLUSHED)
69
+ }
70
+
71
+ static BATCH_ALREADY_EXISTS (msg = 'Batch already exists') {
72
+ return new HypercoreError(msg, 'BATCH_ALREADY_EXISTS', HypercoreError.BATCH_ALREADY_EXISTS)
73
+ }
74
+
75
+ static BATCH_ALREADY_FLUSHED (msg = 'Batch has already been flushed') {
76
+ return new HypercoreError(msg, 'BATCH_ALREADY_FLUSHED', HypercoreError.BATCH_ALREADY_FLUSHED)
77
+ }
78
+
67
79
  static OPLOG_CORRUPT (msg = 'Oplog file appears corrupt or out of date') {
68
80
  return new HypercoreError(msg, 'OPLOG_CORRUPT', HypercoreError.OPLOG_CORRUPT)
69
81
  }
package/lib/oplog.js CHANGED
@@ -4,10 +4,11 @@ const { crc32 } = require('crc-universal')
4
4
  const { OPLOG_CORRUPT } = require('./errors')
5
5
 
6
6
  module.exports = class Oplog {
7
- constructor (storage, { pageSize = 4096, headerEncoding = cenc.raw, entryEncoding = cenc.raw } = {}) {
7
+ constructor (storage, { pageSize = 4096, headerEncoding = cenc.raw, entryEncoding = cenc.raw, headerOnly = false } = {}) {
8
8
  this.storage = storage
9
9
  this.headerEncoding = headerEncoding
10
10
  this.entryEncoding = entryEncoding
11
+ this.headerOnly = headerOnly
11
12
  this.flushed = false
12
13
  this.byteLength = 0
13
14
  this.length = 0
@@ -101,6 +102,8 @@ module.exports = class Oplog {
101
102
 
102
103
  result.header = header ? h2.message : h1.message
103
104
 
105
+ if (this.headerOnly) return result
106
+
104
107
  while (true) {
105
108
  const entry = this._decodeEntry(state, this.entryEncoding)
106
109
  if (!entry) break
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.12.0",
3
+ "version": "10.14.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {