hypercore 10.38.2 → 11.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
@@ -1,46 +1,42 @@
1
1
  const { EventEmitter } = require('events')
2
- const RAF = require('random-access-file')
3
2
  const isOptions = require('is-options')
4
- const hypercoreCrypto = require('hypercore-crypto')
3
+ const crypto = require('hypercore-crypto')
4
+ const CoreStorage = require('hypercore-storage')
5
5
  const c = require('compact-encoding')
6
6
  const b4a = require('b4a')
7
- const Xache = require('xache')
8
7
  const NoiseSecretStream = require('@hyperswarm/secret-stream')
9
8
  const Protomux = require('protomux')
10
- const z32 = require('z32')
11
9
  const id = require('hypercore-id-encoding')
12
10
  const safetyCatch = require('safety-catch')
13
11
  const unslab = require('unslab')
14
12
 
15
- const Replicator = require('./lib/replicator')
16
13
  const Core = require('./lib/core')
17
14
  const BlockEncryption = require('./lib/block-encryption')
18
15
  const Info = require('./lib/info')
19
16
  const Download = require('./lib/download')
20
- const Batch = require('./lib/batch')
21
17
  const { manifestHash, createManifest } = require('./lib/verifier')
22
18
  const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
23
19
  const {
24
20
  ASSERTION,
25
21
  BAD_ARGUMENT,
26
22
  SESSION_CLOSED,
23
+ SESSION_MOVED,
27
24
  SESSION_NOT_WRITABLE,
28
25
  SNAPSHOT_NOT_AVAILABLE,
29
26
  DECODING_ERROR
30
27
  } = require('hypercore-errors')
31
28
 
32
- const promises = Symbol.for('hypercore.promises')
33
29
  const inspect = Symbol.for('nodejs.util.inspect.custom')
34
30
 
35
31
  // Hypercore actually does not have any notion of max/min block sizes
36
32
  // but we enforce 15mb to ensure smooth replication (each block is transmitted atomically)
37
33
  const MAX_SUGGESTED_BLOCK_SIZE = 15 * 1024 * 1024
38
34
 
39
- module.exports = class Hypercore extends EventEmitter {
35
+ class Hypercore extends EventEmitter {
40
36
  constructor (storage, key, opts) {
41
37
  super()
42
38
 
43
- if (isOptions(storage)) {
39
+ if (isOptions(storage) && !storage.db) {
44
40
  opts = storage
45
41
  storage = null
46
42
  key = opts.key || null
@@ -54,34 +50,30 @@ module.exports = class Hypercore extends EventEmitter {
54
50
 
55
51
  if (!storage) storage = opts.storage
56
52
 
57
- this[promises] = true
58
-
59
- this.storage = null
60
- this.crypto = opts.crypto || hypercoreCrypto
61
53
  this.core = null
62
- this.replicator = null
54
+ this.state = null
63
55
  this.encryption = null
64
56
  this.extensions = new Map()
65
- this.cache = createCache(opts.cache)
66
57
 
67
58
  this.valueEncoding = null
68
59
  this.encodeBatch = null
69
60
  this.activeRequests = []
61
+ this.sessions = null
62
+ this.ongc = null
70
63
 
71
- this.id = null
72
- this.key = key || null
73
64
  this.keyPair = opts.keyPair || null
74
65
  this.readable = true
75
66
  this.writable = false
67
+ this.exclusive = false
76
68
  this.opened = false
77
69
  this.closed = false
70
+ this.weak = !!opts.weak
78
71
  this.snapshotted = !!opts.snapshot
79
- this.sparse = opts.sparse !== false
80
- this.sessions = opts._sessions || [this]
81
- this.autoClose = !!opts.autoClose
72
+ this.draft = !!opts.draft
82
73
  this.onwait = opts.onwait || null
83
74
  this.wait = opts.wait !== false
84
75
  this.timeout = opts.timeout || 0
76
+ this.preload = null
85
77
  this.closing = null
86
78
  this.opening = null
87
79
 
@@ -91,8 +83,14 @@ module.exports = class Hypercore extends EventEmitter {
91
83
  this._findingPeers = 0
92
84
  this._active = opts.active !== false
93
85
 
94
- this.opening = this._openSession(key, storage, opts)
86
+ this._sessionIndex = -1
87
+ this._stateIndex = -1 // maintained by session state
88
+ this._monitorIndex = -1 // maintained by replication state
89
+
90
+ this.opening = this._open(storage, key, opts)
95
91
  this.opening.catch(safetyCatch)
92
+
93
+ this.on('newListener', maybeAddMonitor)
96
94
  }
97
95
 
98
96
  [inspect] (depth, opts) {
@@ -129,7 +127,6 @@ module.exports = class Hypercore extends EventEmitter {
129
127
  indent + ' opened: ' + opts.stylize(this.opened, 'boolean') + '\n' +
130
128
  indent + ' closed: ' + opts.stylize(this.closed, 'boolean') + '\n' +
131
129
  indent + ' snapshotted: ' + opts.stylize(this.snapshotted, 'boolean') + '\n' +
132
- indent + ' sparse: ' + opts.stylize(this.sparse, 'boolean') + '\n' +
133
130
  indent + ' writable: ' + opts.stylize(this.writable, 'boolean') + '\n' +
134
131
  indent + ' length: ' + opts.stylize(this.length, 'number') + '\n' +
135
132
  indent + ' fork: ' + opts.stylize(this.fork, 'number') + '\n' +
@@ -147,13 +144,17 @@ module.exports = class Hypercore extends EventEmitter {
147
144
  }
148
145
 
149
146
  static discoveryKey (key) {
150
- return hypercoreCrypto.discoveryKey(key)
147
+ return crypto.discoveryKey(key)
151
148
  }
152
149
 
153
150
  static getProtocolMuxer (stream) {
154
151
  return stream.noiseStream.userData
155
152
  }
156
153
 
154
+ static createCore (storage, opts) {
155
+ return new Core(Hypercore.defaultStorage(storage), { autoClose: false, ...opts })
156
+ }
157
+
157
158
  static createProtocolStream (isInitiator, opts = {}) {
158
159
  let outerStream = Protomux.isProtomux(isInitiator)
159
160
  ? isInitiator.stream
@@ -188,29 +189,10 @@ module.exports = class Hypercore extends EventEmitter {
188
189
  }
189
190
 
190
191
  static defaultStorage (storage, opts = {}) {
191
- if (typeof storage !== 'string') {
192
- if (!isRandomAccessClass(storage)) return storage
193
- const Cls = storage // just to satisfy standard...
194
- return name => new Cls(name)
195
- }
192
+ if (CoreStorage.isCoreStorage(storage)) return storage
196
193
 
197
194
  const directory = storage
198
- const toLock = opts.unlocked ? null : (opts.lock || 'oplog')
199
- const pool = opts.pool || (opts.poolSize ? RAF.createPool(opts.poolSize) : null)
200
- const rmdir = !!opts.rmdir
201
- const writable = opts.writable !== false
202
-
203
- return createFile
204
-
205
- function createFile (name) {
206
- const lock = toLock === null ? false : isFile(name, toLock)
207
- const sparse = isFile(name, 'data') || isFile(name, 'bitfield') || isFile(name, 'tree')
208
- return new RAF(name, { directory, lock, sparse, pool: lock ? null : pool, rmdir, writable })
209
- }
210
-
211
- function isFile (name, n) {
212
- return name === n || name.endsWith('/' + n)
213
- }
195
+ return new CoreStorage(directory, opts)
214
196
  }
215
197
 
216
198
  snapshot (opts) {
@@ -224,49 +206,33 @@ module.exports = class Hypercore extends EventEmitter {
224
206
  throw SESSION_CLOSED('Cannot make sessions on a closing core')
225
207
  }
226
208
 
227
- const sparse = opts.sparse === false ? false : this.sparse
228
209
  const wait = opts.wait === false ? false : this.wait
229
210
  const writable = opts.writable === false ? false : !this._readonly
230
211
  const onwait = opts.onwait === undefined ? this.onwait : opts.onwait
231
212
  const timeout = opts.timeout === undefined ? this.timeout : opts.timeout
213
+ const weak = opts.weak === undefined ? this.weak : opts.weak
232
214
  const Clz = opts.class || Hypercore
233
- const s = new Clz(this.storage, this.key, {
215
+ const s = new Clz(null, this.key, {
234
216
  ...opts,
235
- sparse,
236
217
  wait,
237
218
  onwait,
238
219
  timeout,
239
220
  writable,
240
- _opening: this.opening,
241
- _sessions: this.sessions
221
+ weak,
222
+ parent: this
242
223
  })
243
224
 
244
- s._passCapabilities(this)
245
-
246
- // Configure the cache unless explicitly disabled.
247
- if (opts.cache !== false) {
248
- s.cache = opts.cache === true || !opts.cache ? this.cache : opts.cache
249
- }
250
-
251
- if (this.opened) ensureEncryption(s, opts)
252
- this._addSession(s)
253
-
254
225
  return s
255
226
  }
256
227
 
257
- _addSession (s) {
258
- this.sessions.push(s)
259
- if (this.core) this.core.active++
260
- }
261
-
262
228
  async setEncryptionKey (encryptionKey, opts) {
263
229
  if (!this.opened) await this.opening
264
230
  this.encryption = encryptionKey ? new BlockEncryption(encryptionKey, this.key, { compat: this.core.compat, ...opts }) : null
231
+ if (!this.core.encryption) this.core.encryption = this.encryption
265
232
  }
266
233
 
267
234
  setKeyPair (keyPair) {
268
235
  this.keyPair = keyPair
269
- this.writable = this._isWritable()
270
236
  }
271
237
 
272
238
  setActive (bool) {
@@ -274,74 +240,87 @@ module.exports = class Hypercore extends EventEmitter {
274
240
  if (active === this._active || this.closing) return
275
241
  this._active = active
276
242
  if (!this.opened) return
277
- this.replicator.updateActivity(this._active ? 1 : -1)
243
+ this.core.replicator.updateActivity(this._active ? 1 : -1)
278
244
  }
279
245
 
280
- _passCapabilities (o) {
281
- if (!this.keyPair) this.keyPair = o.keyPair
282
- this.crypto = o.crypto
283
- this.id = o.id
284
- this.key = o.key
285
- this.core = o.core
286
- this.replicator = o.replicator
287
- this.encryption = o.encryption
246
+ _setupSession (parent) {
247
+ if (!this.keyPair) this.keyPair = parent.keyPair
288
248
  this.writable = this._isWritable()
289
- this.autoClose = o.autoClose
249
+
250
+ const s = parent.state
251
+
252
+ if (s) {
253
+ const shouldSnapshot = this.snapshotted && !s.isSnapshot()
254
+ this.state = shouldSnapshot ? s.snapshot() : s.ref()
255
+ }
290
256
 
291
257
  if (this.snapshotted && this.core && !this._snapshot) this._updateSnapshot()
292
258
  }
293
259
 
294
- async _openFromExisting (from, opts) {
295
- if (!from.opened) await from.opening
260
+ async _open (storage, key, opts) {
261
+ const preload = opts.preload || (opts.parent && opts.parent.preload)
296
262
 
297
- // includes ourself as well, so the loop below also updates us
298
- const sessions = this.sessions
299
-
300
- for (const s of sessions) {
301
- s.sessions = from.sessions
302
- s._passCapabilities(from)
303
- s._addSession(s)
263
+ if (preload) {
264
+ this.sessions = [] // in case someone looks at it like with peers
265
+ this.preload = preload
266
+ opts = { ...opts, ...(await this.preload) }
267
+ this.preload = null
304
268
  }
305
269
 
306
- this.storage = from.storage
307
- this.replicator.findingPeers += this._findingPeers
270
+ const parent = opts.parent || null
271
+ const core = opts.core || (parent && parent.core)
272
+ const sessions = opts.sessions || (parent && parent.sessions)
273
+ const ongc = opts.ongc || (parent && parent.ongc)
274
+
275
+ if (core) this.core = core
276
+ if (ongc) this.ongc = ongc
277
+ if (sessions) this.sessions = sessions
278
+
279
+ if (this.sessions === null) this.sessions = []
280
+ this._sessionIndex = this.sessions.push(this) - 1
308
281
 
309
- ensureEncryption(this, opts)
282
+ if (this.core === null) initOnce(this, storage, key, opts)
283
+ if (this._monitorIndex === -2) this.core.addMonitor(this)
310
284
 
311
- // we need to manually fwd the encryption cap as the above removes it potentially
312
- if (this.encryption && !from.encryption) {
313
- for (const s of sessions) s.encryption = this.encryption
285
+ try {
286
+ await this._openSession(opts)
287
+ } catch (err) {
288
+ if (this.closing) return
289
+ if (this.core.autoClose && this.core.hasSession() === false) await this.core.close()
290
+
291
+ if (this.exclusive) this.core.unlockExclusive()
292
+
293
+ this.core.removeMonitor(this)
294
+ this._removeSession()
295
+
296
+ if (this.state !== null) this.state.removeSession(this)
297
+
298
+ this.emit('close', this.core.hasSession() === false)
299
+ throw err
314
300
  }
301
+
302
+ this.emit('ready')
315
303
  }
316
304
 
317
- async _openSession (key, storage, opts) {
318
- const isFirst = !opts._opening
305
+ _removeSession () {
306
+ if (this._sessionIndex === -1) return
307
+ const head = this.sessions.pop()
308
+ if (head !== this) this.sessions[(head._sessionIndex = this._sessionIndex)] = head
309
+ this._sessionIndex = -1
310
+ if (this.ongc !== null) this.ongc(this)
311
+ }
319
312
 
320
- if (!isFirst) {
321
- await opts._opening
322
- }
323
- if (opts.preload) opts = { ...opts, ...(await this._retryPreload(opts.preload)) }
324
- if (this.cache === null && opts.cache) this.cache = createCache(opts.cache)
325
-
326
- if (isFirst) {
327
- await this._openCapabilities(key, storage, opts)
328
-
329
- // check we are the actual root and not a opts.from session
330
- if (!opts.from) {
331
- // Only the root session should pass capabilities to other sessions.
332
- for (let i = 0; i < this.sessions.length; i++) {
333
- const s = this.sessions[i]
334
- if (s !== this) s._passCapabilities(this)
335
- }
336
- }
337
- } else {
338
- ensureEncryption(this, opts)
339
- }
313
+ async _openSession (opts) {
314
+ if (this.core.opened === false) await this.core.ready()
340
315
 
341
- if (opts.manifest && !this.core.header.manifest) {
342
- await this.core.setManifest(opts.manifest)
316
+ if (this.keyPair === null) this.keyPair = opts.keyPair || this.core.header.keyPair
317
+
318
+ if (!this.core.encryption && opts.encryptionKey) {
319
+ this.core.encryption = new BlockEncryption(opts.encryptionKey, this.key, { compat: this.core.compat, isBlockKey: opts.isBlockKey })
343
320
  }
344
321
 
322
+ if (this.core.encryption) this.encryption = this.core.encryption
323
+
345
324
  this.writable = this._isWritable()
346
325
 
347
326
  if (opts.valueEncoding) {
@@ -351,96 +330,68 @@ module.exports = class Hypercore extends EventEmitter {
351
330
  this.encodeBatch = opts.encodeBatch
352
331
  }
353
332
 
354
- // Start continous replication if not in sparse mode.
355
- if (!this.sparse) this.download({ start: 0, end: -1 })
333
+ if (opts.parent) {
334
+ if (opts.parent._stateIndex === -1) await opts.parent.ready()
335
+ this._setupSession(opts.parent)
336
+ }
356
337
 
357
- // This is a hidden option that's only used by Corestore.
358
- // It's required so that corestore can load a name from userData before 'ready' is emitted.
359
- if (opts._preready) await opts._preready(this)
338
+ if (opts.exclusive) {
339
+ this.exclusive = true
340
+ await this.core.lockExclusive()
341
+ }
360
342
 
361
- this.replicator.updateActivity(this._active ? 1 : 0)
343
+ const parent = opts.parent || this.core
362
344
 
363
- this.opened = true
364
- this.emit('ready')
365
- }
345
+ if (opts.atom) {
346
+ this.state = await parent.state.createSession(null, -1, false, opts.atom)
347
+ } else if (opts.name) {
348
+ // todo: need to make named sessions safe before ready
349
+ // atm we always copy the state in passCapabilities
350
+ const checkout = opts.checkout === undefined ? -1 : opts.checkout
351
+ const state = this.state
352
+
353
+ this.state = await parent.state.createSession(opts.name, checkout, !!opts.overwrite, null)
354
+ if (state) state.unref() // ref'ed above in setup session
366
355
 
367
- async _retryPreload (preload) {
368
- while (true) { // TODO: better long term fix is allowing lib/core.js creation from the outside...
369
- const result = await preload()
370
- const from = result && result.from
371
- if (from) {
372
- if (!from.opened) await from.ready()
373
- if (from.closing) continue
356
+ if (checkout !== -1 && checkout < this.state.length) {
357
+ await this.state.truncate(checkout, this.fork)
374
358
  }
375
- return result
359
+ } else if (this.state === null) {
360
+ this.state = this.core.state.ref()
376
361
  }
377
- }
378
362
 
379
- async _openCapabilities (key, storage, opts) {
380
- if (opts.from) return this._openFromExisting(opts.from, opts)
381
-
382
- const unlocked = !!opts.unlocked
383
- this.storage = Hypercore.defaultStorage(opts.storage || storage, { unlocked, writable: !unlocked })
384
-
385
- this.core = await Core.open(this.storage, {
386
- compat: opts.compat,
387
- force: opts.force,
388
- sessions: this.sessions,
389
- createIfMissing: opts.createIfMissing,
390
- readonly: unlocked,
391
- overwrite: opts.overwrite,
392
- key,
393
- keyPair: opts.keyPair,
394
- crypto: this.crypto,
395
- legacy: opts.legacy,
396
- manifest: opts.manifest,
397
- globalCache: opts.globalCache || null, // This is a temp option, not to be relied on unless you know what you are doing (no semver guarantees)
398
- onupdate: this._oncoreupdate.bind(this),
399
- onconflict: this._oncoreconflict.bind(this)
400
- })
363
+ if (this.snapshotted && this.core) this._updateSnapshot()
364
+
365
+ this.state.addSession(this)
366
+ // TODO: we need to rework the core reference flow, as the state and session do not always agree now due to moveTo
367
+ this.core = this.state.core // in case it was wrong...
401
368
 
402
369
  if (opts.userData) {
370
+ const tx = this.state.storage.write()
403
371
  for (const [key, value] of Object.entries(opts.userData)) {
404
- await this.core.userData(key, value)
372
+ tx.putUserData(key, value)
405
373
  }
374
+ await tx.flush()
406
375
  }
407
376
 
408
- this.key = this.core.header.key
409
- this.keyPair = this.core.header.keyPair
410
- this.id = z32.encode(this.key)
411
-
412
- this.replicator = new Replicator(this.core, this.key, {
413
- eagerUpgrade: true,
414
- notDownloadingLinger: opts.notDownloadingLinger,
415
- allowFork: opts.allowFork !== false,
416
- inflightRange: opts.inflightRange,
417
- onpeerupdate: this._onpeerupdate.bind(this),
418
- onupload: this._onupload.bind(this),
419
- oninvalid: this._oninvalid.bind(this)
420
- })
377
+ if (opts.manifest && !this.core.header.manifest) {
378
+ await this.core.setManifest(opts.manifest)
379
+ }
421
380
 
422
- this.replicator.findingPeers += this._findingPeers
381
+ this.core.replicator.updateActivity(this._active ? 1 : 0)
423
382
 
424
- if (!this.encryption && opts.encryptionKey) {
425
- this.encryption = new BlockEncryption(opts.encryptionKey, this.key, { compat: this.core.compat, isBlockKey: opts.isBlockKey })
426
- }
383
+ this.opened = true
427
384
  }
428
385
 
429
- _getSnapshot () {
430
- if (this.sparse) {
431
- return {
432
- length: this.core.tree.length,
433
- byteLength: this.core.tree.byteLength,
434
- fork: this.core.tree.fork,
435
- compatLength: this.core.tree.length
436
- }
437
- }
386
+ get replicator () {
387
+ return this.core === null ? null : this.core.replicator
388
+ }
438
389
 
390
+ _getSnapshot () {
439
391
  return {
440
- length: this.core.header.hints.contiguousLength,
441
- byteLength: 0,
442
- fork: this.core.tree.fork,
443
- compatLength: this.core.header.hints.contiguousLength
392
+ length: this.state.length,
393
+ byteLength: this.state.byteLength,
394
+ fork: this.state.fork
444
395
  }
445
396
  }
446
397
 
@@ -453,26 +404,27 @@ module.exports = class Hypercore extends EventEmitter {
453
404
  }
454
405
 
455
406
  _isWritable () {
407
+ if (this.draft) return true
456
408
  return !this._readonly && !!(this.keyPair && this.keyPair.secretKey)
457
409
  }
458
410
 
459
- close (err) {
411
+ close ({ error } = {}) {
460
412
  if (this.closing) return this.closing
461
- this.closing = this._close(err || null)
413
+
414
+ this.closing = this._close(error || null)
462
415
  return this.closing
463
416
  }
464
417
 
465
- async _close (err) {
418
+ async _close (error) {
466
419
  if (this.opened === false) await this.opening
420
+ if (this.closed === true) return
467
421
 
468
- const i = this.sessions.indexOf(this)
469
- if (i === -1) return
422
+ this.core.removeMonitor(this)
423
+ this.state.removeSession(this)
424
+ this._removeSession()
470
425
 
471
- this.sessions.splice(i, 1)
472
- this.core.active--
473
426
  this.readable = false
474
427
  this.writable = false
475
- this.closed = true
476
428
  this.opened = false
477
429
 
478
430
  const gc = []
@@ -481,31 +433,36 @@ module.exports = class Hypercore extends EventEmitter {
481
433
  }
482
434
  for (const ext of gc) ext.destroy()
483
435
 
484
- if (this.replicator !== null) {
485
- this.replicator.findingPeers -= this._findingPeers
486
- this.replicator.clearRequests(this.activeRequests, err)
487
- this.replicator.updateActivity(this._active ? -1 : 0)
488
- }
436
+ this.core.replicator.findingPeers -= this._findingPeers
437
+ this.core.replicator.clearRequests(this.activeRequests, error)
438
+ this.core.replicator.updateActivity(this._active ? -1 : 0)
489
439
 
490
440
  this._findingPeers = 0
491
441
 
492
- if (this.sessions.length || this.core.active > 0) {
493
- // if this is the last session and we are auto closing, trigger that first to enforce error handling
494
- if (this.sessions.length === 1 && this.core.active === 1 && this.autoClose) await this.sessions[0].close(err)
442
+ this.state.unref()
443
+
444
+ if (this.exclusive) this.core.unlockExclusive()
445
+
446
+ if (this.core.hasSession()) {
495
447
  // emit "fake" close as this is a session
448
+ this.closed = true
496
449
  this.emit('close', false)
497
450
  return
498
451
  }
499
452
 
500
- if (this.replicator !== null) {
501
- await this.replicator.destroy()
502
- }
503
-
504
- await this.core.close()
453
+ if (this.core.autoClose) await this.core.close()
505
454
 
455
+ this.closed = true
506
456
  this.emit('close', true)
507
457
  }
508
458
 
459
+ async commit (session, opts) {
460
+ await this.ready()
461
+ await session.ready()
462
+
463
+ return this.state.commit(session.state, { keyPair: this.keyPair, ...opts })
464
+ }
465
+
509
466
  replicate (isInitiator, opts = {}) {
510
467
  // Only limitation here is that ondiscoverykey doesn't work atm when passing a muxer directly,
511
468
  // because it doesn't really make a lot of sense.
@@ -517,68 +474,67 @@ module.exports = class Hypercore extends EventEmitter {
517
474
  const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
518
475
  const noiseStream = protocolStream.noiseStream
519
476
  const protocol = noiseStream.userData
520
- const useSession = !!opts.session
521
477
 
522
- this._attachToMuxer(protocol, useSession)
478
+ this._attachToMuxer(protocol)
523
479
 
524
480
  return protocolStream
525
481
  }
526
482
 
527
483
  _isAttached (stream) {
528
- return stream.userData && this.replicator && this.replicator.attached(stream.userData)
484
+ return stream.userData && this.core && this.core.replicator && this.core.replicator.attached(stream.userData)
529
485
  }
530
486
 
531
- _attachToMuxer (mux, useSession) {
487
+ _attachToMuxer (mux) {
532
488
  if (this.opened) {
533
- this._attachToMuxerOpened(mux, useSession)
489
+ this.core.replicator.attachTo(mux)
534
490
  } else {
535
- this.opening.then(this._attachToMuxerOpened.bind(this, mux, useSession), mux.destroy.bind(mux))
491
+ this.opening.then(() => this.core.replicator.attachTo(mux), mux.destroy.bind(mux))
536
492
  }
537
493
 
538
494
  return mux
539
495
  }
540
496
 
541
- _attachToMuxerOpened (mux, useSession) {
542
- // If the user wants to, we can make this replication run in a session
543
- // that way the core wont close "under them" during replication
544
- this.replicator.attachTo(mux, useSession)
497
+ get id () {
498
+ return this.core === null ? null : this.core.id
499
+ }
500
+
501
+ get key () {
502
+ return this.core === null ? null : this.core.key
545
503
  }
546
504
 
547
505
  get discoveryKey () {
548
- return this.replicator === null ? null : this.replicator.discoveryKey
506
+ return this.core === null ? null : this.core.discoveryKey
549
507
  }
550
508
 
551
509
  get manifest () {
552
- return this.core === null ? null : this.core.header.manifest
510
+ return this.core === null ? null : this.core.manifest
553
511
  }
554
512
 
555
513
  get length () {
556
514
  if (this._snapshot) return this._snapshot.length
557
- if (this.core === null) return 0
558
- if (!this.sparse) return this.contiguousLength
559
- return this.core.tree.length
515
+ return this.opened === false ? 0 : this.state.length
560
516
  }
561
517
 
562
518
  get signedLength () {
563
- return this.length
564
- }
519
+ if (this.opened === false) return 0
520
+ if (this.state === this.core.state) return this.core.state.length
521
+ const flushed = this.state.flushedLength()
565
522
 
566
- get indexedLength () {
567
- return this.length
523
+ return flushed === -1 ? this.state.length : flushed
568
524
  }
569
525
 
570
526
  /**
571
527
  * Deprecated. Use `const { byteLength } = await core.info()`.
572
528
  */
573
529
  get byteLength () {
530
+ if (this.opened === false) return 0
574
531
  if (this._snapshot) return this._snapshot.byteLength
575
- if (this.core === null) return 0
576
- if (!this.sparse) return this.contiguousByteLength
577
- return this.core.tree.byteLength - (this.core.tree.length * this.padding)
532
+ return this.state.byteLength - (this.state.length * this.padding)
578
533
  }
579
534
 
580
535
  get contiguousLength () {
581
- return this.core === null ? 0 : Math.min(this.core.tree.length, this.core.header.hints.contiguousLength)
536
+ if (this.opened === false) return 0
537
+ return Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
582
538
  }
583
539
 
584
540
  get contiguousByteLength () {
@@ -586,11 +542,12 @@ module.exports = class Hypercore extends EventEmitter {
586
542
  }
587
543
 
588
544
  get fork () {
589
- return this.core === null ? 0 : this.core.tree.fork
545
+ if (this.opened === false) return 0
546
+ return this.state.fork
590
547
  }
591
548
 
592
549
  get peers () {
593
- return this.replicator === null ? [] : this.replicator.peers
550
+ return this.opened === false ? [] : this.core.replicator.peers
594
551
  }
595
552
 
596
553
  get encryptionKey () {
@@ -602,155 +559,69 @@ module.exports = class Hypercore extends EventEmitter {
602
559
  }
603
560
 
604
561
  get globalCache () {
605
- return this.core && this.core.globalCache
562
+ return this.opened === false ? null : this.core.globalCache
606
563
  }
607
564
 
608
565
  ready () {
609
566
  return this.opening
610
567
  }
611
568
 
612
- _onupload (index, value, from) {
613
- const byteLength = value.byteLength - this.padding
614
-
615
- for (let i = 0; i < this.sessions.length; i++) {
616
- this.sessions[i].emit('upload', index, byteLength, from)
617
- }
618
- }
619
-
620
- _oninvalid (err, req, res, from) {
621
- for (let i = 0; i < this.sessions.length; i++) {
622
- this.sessions[i].emit('verification-error', err, req, res, from)
623
- }
624
- }
625
-
626
- async _oncoreconflict (proof, from) {
627
- await this.replicator.onconflict(from)
628
-
629
- for (const s of this.sessions) s.emit('conflict', proof.upgrade.length, proof.fork, proof)
630
-
631
- const err = new Error('Two conflicting signatures exist for length ' + proof.upgrade.length)
632
- await this._closeAllSessions(err)
569
+ async setUserData (key, value) {
570
+ if (this.opened === false) await this.opening
571
+ await this.state.setUserData(key, value)
633
572
  }
634
573
 
635
- async _closeAllSessions (err) {
636
- // this.sessions modifies itself when a session closes
637
- // This way we ensure we indeed iterate over all sessions
638
- const sessions = [...this.sessions]
639
-
640
- const all = []
641
- for (const s of sessions) all.push(s.close(err))
642
- await Promise.allSettled(all)
574
+ async getUserData (key) {
575
+ if (this.opened === false) await this.opening
576
+ const batch = this.state.storage.read()
577
+ const p = batch.getUserData(key)
578
+ batch.tryFlush()
579
+ return p
643
580
  }
644
581
 
645
- _oncoreupdate (status, bitfield, value, from) {
646
- if (status !== 0) {
647
- const truncatedNonSparse = (status & 0b1000) !== 0
648
- const appendedNonSparse = (status & 0b0100) !== 0
649
- const truncated = (status & 0b0010) !== 0
650
- const appended = (status & 0b0001) !== 0
582
+ transferSession (core) {
583
+ // todo: validate we can move
651
584
 
652
- if (truncated) {
653
- this.replicator.ontruncate(bitfield.start, bitfield.length)
654
- }
655
-
656
- if ((status & 0b10011) !== 0) {
657
- this.replicator.onupgrade()
658
- }
659
-
660
- if (status & 0b10000) {
661
- for (let i = 0; i < this.sessions.length; i++) {
662
- const s = this.sessions[i]
663
-
664
- if (s.encryption && s.encryption.compat !== this.core.compat) {
665
- s.encryption = new BlockEncryption(s.encryption.key, this.key, { compat: this.core.compat, isBlockKey: s.encryption.isBlockKey })
666
- }
667
- }
668
-
669
- for (let i = 0; i < this.sessions.length; i++) {
670
- this.sessions[i].emit('manifest')
671
- }
672
- }
673
-
674
- for (let i = 0; i < this.sessions.length; i++) {
675
- const s = this.sessions[i]
676
-
677
- if (truncated) {
678
- if (s.cache) s.cache.clear()
679
-
680
- // If snapshotted, make sure to update our compat so we can fail gets
681
- if (s._snapshot && bitfield.start < s._snapshot.compatLength) s._snapshot.compatLength = bitfield.start
682
- }
683
-
684
- if (s.sparse ? truncated : truncatedNonSparse) {
685
- s.emit('truncate', bitfield.start, this.core.tree.fork)
686
- }
687
-
688
- // For sparse sessions, immediately emit appends. If non-sparse, emit if contig length has updated
689
- if (s.sparse ? appended : appendedNonSparse) {
690
- s.emit('append')
691
- }
692
- }
693
-
694
- const contig = this.core.header.hints.contiguousLength
695
-
696
- // When the contig length catches up, broadcast the non-sparse length to peers
697
- if (appendedNonSparse && contig === this.core.tree.length) {
698
- for (const peer of this.peers) {
699
- if (peer.broadcastedNonSparse) continue
700
-
701
- peer.broadcastRange(0, contig)
702
- peer.broadcastedNonSparse = true
703
- }
704
- }
585
+ if (this.weak === false) {
586
+ this.core.activeSessions--
587
+ core.activeSessions++
705
588
  }
706
589
 
707
- if (bitfield) {
708
- this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
590
+ if (this._monitorIndex >= 0) {
591
+ this.core.removeMonitor(this)
592
+ core.addMonitor(this)
709
593
  }
710
594
 
711
- if (value) {
712
- const byteLength = value.byteLength - this.padding
713
-
714
- for (let i = 0; i < this.sessions.length; i++) {
715
- this.sessions[i].emit('download', bitfield.start, byteLength, from)
716
- }
717
- }
718
- }
595
+ const old = this.core
719
596
 
720
- _onpeerupdate (added, peer) {
721
- const name = added ? 'peer-add' : 'peer-remove'
597
+ this.core = core
722
598
 
723
- for (let i = 0; i < this.sessions.length; i++) {
724
- this.sessions[i].emit(name, peer)
599
+ old.replicator.clearRequests(this.activeRequests, SESSION_MOVED())
725
600
 
726
- if (added) {
727
- for (const ext of this.sessions[i].extensions.values()) {
728
- peer.extensions.set(ext.name, ext)
729
- }
730
- }
731
- }
601
+ this.emit('migrate', this.key)
732
602
  }
733
603
 
734
- async setUserData (key, value, { flush = false } = {}) {
735
- if (this.opened === false) await this.opening
736
- return this.core.userData(key, value, flush)
604
+ createTreeBatch () {
605
+ return this.state.createTreeBatch()
737
606
  }
738
607
 
739
- async getUserData (key) {
608
+ async restoreBatch (length, additionalBlocks = []) {
740
609
  if (this.opened === false) await this.opening
741
- for (const { key: savedKey, value } of this.core.header.userData) {
742
- if (key === savedKey) return value
610
+ const batch = this.state.createTreeBatch()
611
+
612
+ if (length > batch.length + additionalBlocks.length) {
613
+ throw BAD_ARGUMENT('Insufficient additional blocks were passed')
743
614
  }
744
- return null
745
- }
746
615
 
747
- createTreeBatch () {
748
- return this.core.tree.batch()
616
+ let i = 0
617
+ while (batch.length < length) batch.append(additionalBlocks[i++])
618
+
619
+ return length < batch.length ? batch.restore(length) : batch
749
620
  }
750
621
 
751
622
  findingPeers () {
752
623
  this._findingPeers++
753
- if (this.replicator !== null && !this.closing) this.replicator.findingPeers++
624
+ if (this.core !== null && !this.closing) this.core.replicator.findingPeers++
754
625
 
755
626
  let once = true
756
627
 
@@ -758,8 +629,8 @@ module.exports = class Hypercore extends EventEmitter {
758
629
  if (this.closing || !once) return
759
630
  once = false
760
631
  this._findingPeers--
761
- if (this.replicator !== null && --this.replicator.findingPeers === 0) {
762
- this.replicator.updateAll()
632
+ if (this.core !== null && --this.core.replicator.findingPeers === 0) {
633
+ this.core.replicator.updateAll()
763
634
  }
764
635
  }
765
636
  }
@@ -773,42 +644,40 @@ module.exports = class Hypercore extends EventEmitter {
773
644
  async update (opts) {
774
645
  if (this.opened === false) await this.opening
775
646
  if (this.closing !== null) return false
647
+ if (this.snapshotted) return false
776
648
 
777
- if (this.writable && (!opts || opts.force !== true)) {
778
- if (!this.snapshotted) return false
779
- return this._updateSnapshot()
780
- }
649
+ if (this.writable && (!opts || opts.force !== true)) return false
781
650
 
782
- const remoteWait = this._shouldWait(opts, this.replicator.findingPeers > 0)
651
+ const remoteWait = this._shouldWait(opts, this.core.replicator.findingPeers > 0)
783
652
 
784
653
  let upgraded = false
785
654
 
786
- if (await this.replicator.applyPendingReorg()) {
655
+ if (await this.core.replicator.applyPendingReorg()) {
787
656
  upgraded = true
788
657
  }
789
658
 
790
659
  if (!upgraded && remoteWait) {
791
660
  const activeRequests = (opts && opts.activeRequests) || this.activeRequests
792
- const req = this.replicator.addUpgrade(activeRequests)
661
+ const req = this.core.replicator.addUpgrade(activeRequests)
793
662
 
794
- upgraded = await req.promise
663
+ try {
664
+ upgraded = await req.promise
665
+ } catch (err) {
666
+ if (isSessionMoved(err)) return this.update(opts)
667
+ throw err
668
+ }
795
669
  }
796
670
 
797
671
  if (!upgraded) return false
798
- if (this.snapshotted) return this._updateSnapshot()
799
672
  return true
800
673
  }
801
674
 
802
- batch ({ checkout = -1, autoClose = true, session = true, restore = false, clear = false } = {}) {
803
- return new Batch(session ? this.session() : this, checkout, autoClose, restore, clear)
804
- }
805
-
806
675
  async seek (bytes, opts) {
807
676
  if (this.opened === false) await this.opening
808
677
  if (!isValidIndex(bytes)) throw ASSERTION('seek is invalid')
809
678
 
810
- const tree = (opts && opts.tree) || this.core.tree
811
- const s = tree.seek(bytes, this.padding)
679
+ const tree = (opts && opts.tree) || this.state.core.tree
680
+ const s = tree.seek(this.state, bytes, this.padding)
812
681
 
813
682
  const offset = await s.update()
814
683
  if (offset) return offset
@@ -818,22 +687,47 @@ module.exports = class Hypercore extends EventEmitter {
818
687
  if (!this._shouldWait(opts, this.wait)) return null
819
688
 
820
689
  const activeRequests = (opts && opts.activeRequests) || this.activeRequests
821
- const req = this.replicator.addSeek(activeRequests, s)
690
+ const req = this.core.replicator.addSeek(activeRequests, s)
822
691
 
823
692
  const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
824
693
  if (timeout) req.context.setTimeout(req, timeout)
825
694
 
826
- return req.promise
695
+ try {
696
+ return await req.promise
697
+ } catch (err) {
698
+ if (isSessionMoved(err)) return this.seek(bytes, opts)
699
+ throw err
700
+ }
827
701
  }
828
702
 
829
703
  async has (start, end = start + 1) {
830
704
  if (this.opened === false) await this.opening
831
705
  if (!isValidIndex(start) || !isValidIndex(end)) throw ASSERTION('has range is invalid')
832
706
 
833
- if (end === start + 1) return this.core.bitfield.get(start)
707
+ if (this.state.isDefault()) {
708
+ if (end === start + 1) return this.core.bitfield.get(start)
709
+
710
+ const i = this.core.bitfield.firstUnset(start)
711
+ return i === -1 || i >= end
712
+ }
713
+
714
+ if (end === start + 1) {
715
+ const rx = this.state.storage.read()
716
+ const block = rx.getBlock(start)
717
+ rx.tryFlush()
718
+
719
+ return (await block) !== null
720
+ }
834
721
 
835
- const i = this.core.bitfield.firstUnset(start)
836
- return i === -1 || i >= end
722
+ let count = 0
723
+
724
+ const stream = this.state.storage.createBlockStream({ gte: start, lt: end })
725
+ for await (const block of stream) {
726
+ if (block === null) return false
727
+ count++
728
+ }
729
+
730
+ return count === (end - start)
837
731
  }
838
732
 
839
733
  async get (index, opts) {
@@ -841,12 +735,10 @@ module.exports = class Hypercore extends EventEmitter {
841
735
  if (!isValidIndex(index)) throw ASSERTION('block index is invalid')
842
736
 
843
737
  if (this.closing !== null) throw SESSION_CLOSED()
844
- if (this._snapshot !== null && index >= this._snapshot.compatLength) throw SNAPSHOT_NOT_AVAILABLE()
845
738
 
846
739
  const encoding = (opts && opts.valueEncoding && c.from(opts.valueEncoding)) || this.valueEncoding
847
740
 
848
- let req = this.cache && this.cache.get(index)
849
- if (!req) req = this._get(index, opts)
741
+ const req = this._get(index, opts)
850
742
 
851
743
  let block = await req
852
744
  if (!block) return null
@@ -857,6 +749,7 @@ module.exports = class Hypercore extends EventEmitter {
857
749
  // Copy the block as it might be shared with other sessions.
858
750
  block = b4a.from(block)
859
751
 
752
+ if (this.encryption.compat !== this.core.compat) this._updateEncryption()
860
753
  this.encryption.decrypt(index, block)
861
754
  }
862
755
 
@@ -879,7 +772,7 @@ module.exports = class Hypercore extends EventEmitter {
879
772
  if (start >= end) return cleared
880
773
  if (start >= this.length) return cleared
881
774
 
882
- await this.core.clear(start, end, cleared)
775
+ await this.state.clear(start, end, cleared)
883
776
 
884
777
  return cleared
885
778
  }
@@ -890,46 +783,55 @@ module.exports = class Hypercore extends EventEmitter {
890
783
  }
891
784
 
892
785
  async _get (index, opts) {
893
- let block
786
+ if (this.core.isFlushing) await this.core.flushed()
894
787
 
895
- if (this.core.bitfield.get(index)) {
896
- const tree = (opts && opts.tree) || this.core.tree
897
- block = this.core.blocks.get(index, tree)
788
+ const block = await readBlock(this.state.storage.read(), index)
898
789
 
899
- if (this.cache) this.cache.set(index, block)
900
- } else {
901
- if (!this._shouldWait(opts, this.wait)) return null
790
+ if (block !== null) return block
902
791
 
903
- if (opts && opts.onwait) opts.onwait(index, this)
904
- if (this.onwait) this.onwait(index, this)
905
-
906
- const activeRequests = (opts && opts.activeRequests) || this.activeRequests
792
+ if (this.closing !== null) throw SESSION_CLOSED()
907
793
 
908
- const req = this.replicator.addBlock(activeRequests, index)
909
- req.snapshot = index < this.length
794
+ // snapshot should check if core has block
795
+ if (this._snapshot !== null) {
796
+ checkSnapshot(this, index)
797
+ const coreBlock = await readBlock(this.core.state.storage.read(), index)
910
798
 
911
- const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
912
- if (timeout) req.context.setTimeout(req, timeout)
799
+ checkSnapshot(this, index)
800
+ if (coreBlock !== null) return coreBlock
801
+ }
913
802
 
914
- block = this._cacheOnResolve(index, req.promise, this.core.tree.fork)
803
+ // lets check the bitfield to see if we got it during the above async calls
804
+ // this is the last resort before replication, so always safe.
805
+ if (this.core.bitfield.get(index)) {
806
+ const coreBlock = await readBlock(this.state.storage.read(), index)
807
+ // TODO: this should not be needed, only needed atm in case we are doing a moveTo during this (we should fix)
808
+ if (coreBlock !== null) return coreBlock
915
809
  }
916
810
 
917
- return block
918
- }
811
+ if (!this._shouldWait(opts, this.wait)) return null
812
+
813
+ if (opts && opts.onwait) opts.onwait(index, this)
814
+ if (this.onwait) this.onwait(index, this)
919
815
 
920
- async _cacheOnResolve (index, req, fork) {
921
- const resolved = await req
816
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
922
817
 
923
- // Unslab only when it takes up less then half the slab
924
- const block = resolved !== null && 2 * resolved.byteLength < resolved.buffer.byteLength
925
- ? unslab(resolved)
926
- : resolved
818
+ const req = this.core.replicator.addBlock(activeRequests, index)
819
+ req.snapshot = index < this.length
927
820
 
928
- if (this.cache && fork === this.core.tree.fork) {
929
- this.cache.set(index, Promise.resolve(block))
821
+ const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
822
+ if (timeout) req.context.setTimeout(req, timeout)
823
+
824
+ let replicatedBlock = null
825
+
826
+ try {
827
+ replicatedBlock = await req.promise
828
+ } catch (err) {
829
+ if (isSessionMoved(err)) return this._get(index, opts)
830
+ throw err
930
831
  }
931
832
 
932
- return block
833
+ if (this._snapshot !== null) checkSnapshot(this, index)
834
+ return maybeUnslab(replicatedBlock)
933
835
  }
934
836
 
935
837
  _shouldWait (opts, defaultValue) {
@@ -953,19 +855,7 @@ module.exports = class Hypercore extends EventEmitter {
953
855
  }
954
856
 
955
857
  download (range) {
956
- const req = this._download(range)
957
-
958
- // do not crash in the background...
959
- req.catch(safetyCatch)
960
-
961
- return new Download(req)
962
- }
963
-
964
- async _download (range) {
965
- if (this.opened === false) await this.opening
966
-
967
- const activeRequests = (range && range.activeRequests) || this.activeRequests
968
- return this.replicator.addRange(activeRequests, range)
858
+ return new Download(this, range)
969
859
  }
970
860
 
971
861
  // TODO: get rid of this / deprecate it?
@@ -982,27 +872,31 @@ module.exports = class Hypercore extends EventEmitter {
982
872
  if (this.opened === false) await this.opening
983
873
 
984
874
  const {
985
- fork = this.core.tree.fork + 1,
875
+ fork = this.state.fork + 1,
986
876
  keyPair = this.keyPair,
987
877
  signature = null
988
878
  } = typeof opts === 'number' ? { fork: opts } : opts
989
879
 
880
+ const isDefault = this.state === this.core.state
990
881
  const writable = !this._readonly && !!(signature || (keyPair && keyPair.secretKey))
991
- if (writable === false && (newLength > 0 || fork !== this.core.tree.fork)) throw SESSION_NOT_WRITABLE()
882
+ if (isDefault && writable === false && (newLength > 0 || fork !== this.state.fork)) throw SESSION_NOT_WRITABLE()
992
883
 
993
- await this.core.truncate(newLength, fork, { keyPair, signature })
884
+ await this.state.truncate(newLength, fork, { keyPair, signature })
994
885
 
995
886
  // TODO: Should propagate from an event triggered by the oplog
996
- this.replicator.updateAll()
887
+ if (this.state === this.core.state) this.core.replicator.updateAll()
997
888
  }
998
889
 
999
890
  async append (blocks, opts = {}) {
1000
891
  if (this.opened === false) await this.opening
1001
892
 
1002
- const { keyPair = this.keyPair, signature = null } = opts
1003
- const writable = !this._readonly && !!(signature || (keyPair && keyPair.secretKey))
893
+ const isDefault = this.state === this.core.state
894
+ const defaultKeyPair = this.state.name === null ? this.keyPair : null
1004
895
 
1005
- if (writable === false) throw SESSION_NOT_WRITABLE()
896
+ const { keyPair = defaultKeyPair, signature = null } = opts
897
+ const writable = !!this.draft || !isDefault || !!signature || !!(keyPair && keyPair.secretKey)
898
+
899
+ if (this._readonly || writable === false) throw SESSION_NOT_WRITABLE()
1006
900
 
1007
901
  blocks = Array.isArray(blocks) ? blocks : [blocks]
1008
902
 
@@ -1021,17 +915,17 @@ module.exports = class Hypercore extends EventEmitter {
1021
915
  }
1022
916
  }
1023
917
 
1024
- return this.core.append(buffers, { keyPair, signature, preappend })
918
+ return this.state.append(buffers, { keyPair, signature, preappend })
1025
919
  }
1026
920
 
1027
921
  async treeHash (length) {
1028
922
  if (length === undefined) {
1029
923
  await this.ready()
1030
- length = this.core.tree.length
924
+ length = this.state.length
1031
925
  }
1032
926
 
1033
- const roots = await this.core.tree.getRoots(length)
1034
- return this.crypto.tree(roots)
927
+ const roots = await this.state.tree.getRoots(length)
928
+ return crypto.tree(roots)
1035
929
  }
1036
930
 
1037
931
  registerExtension (name, handlers = {}) {
@@ -1071,6 +965,10 @@ module.exports = class Hypercore extends EventEmitter {
1071
965
  }
1072
966
 
1073
967
  this.extensions.set(name, ext)
968
+
969
+ if (this.core === null) this._monitorIndex = -2
970
+ else this.core.addMonitor(this)
971
+
1074
972
  for (const peer of this.peers) {
1075
973
  peer.extensions.set(name, ext)
1076
974
  }
@@ -1109,41 +1007,87 @@ module.exports = class Hypercore extends EventEmitter {
1109
1007
  }
1110
1008
  return block
1111
1009
  }
1010
+
1011
+ _updateEncryption () {
1012
+ const e = this.encryption
1013
+ this.encryption = new BlockEncryption(e.key, this.key, { compat: this.core.compat, isBlockKey: e.isBlockKey })
1014
+ if (e === this.core.encryption) this.core.encryption = this.encryption
1015
+ }
1112
1016
  }
1113
1017
 
1018
+ module.exports = Hypercore
1019
+
1114
1020
  function isStream (s) {
1115
1021
  return typeof s === 'object' && s && typeof s.pipe === 'function'
1116
1022
  }
1117
1023
 
1118
- function isRandomAccessClass (fn) {
1119
- return !!(typeof fn === 'function' && fn.prototype && typeof fn.prototype.open === 'function')
1120
- }
1121
-
1122
1024
  function toHex (buf) {
1123
1025
  return buf && b4a.toString(buf, 'hex')
1124
1026
  }
1125
1027
 
1126
1028
  function preappend (blocks) {
1127
- const offset = this.core.tree.length
1128
- const fork = this.core.tree.fork
1029
+ const offset = this.state.length
1030
+ const fork = this.state.fork
1031
+
1032
+ if (this.encryption.compat !== this.core.compat) this._updateEncryption()
1129
1033
 
1130
1034
  for (let i = 0; i < blocks.length; i++) {
1131
1035
  this.encryption.encrypt(offset + i, blocks[i], fork)
1132
1036
  }
1133
1037
  }
1134
1038
 
1135
- function ensureEncryption (core, opts) {
1136
- if (!opts.encryptionKey) return
1137
- // Only override the block encryption if it's either not already set or if
1138
- // the caller provided a different key.
1139
- if (core.encryption && b4a.equals(core.encryption.key, opts.encryptionKey) && core.encryption.compat === core.core.compat) return
1140
- core.encryption = new BlockEncryption(opts.encryptionKey, core.key, { compat: core.core ? core.core.compat : true, isBlockKey: opts.isBlockKey })
1039
+ function isValidIndex (index) {
1040
+ return index === 0 || index > 0
1141
1041
  }
1142
1042
 
1143
- function createCache (cache) {
1144
- return cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (cache || null)
1043
+ function maybeUnslab (block) {
1044
+ // Unslab only when it takes up less then half the slab
1045
+ return block !== null && 2 * block.byteLength < block.buffer.byteLength ? unslab(block) : block
1145
1046
  }
1146
1047
 
1147
- function isValidIndex (index) {
1148
- return index === 0 || index > 0
1048
+ function checkSnapshot (snapshot, index) {
1049
+ if (index >= snapshot.state.snapshotCompatLength) throw SNAPSHOT_NOT_AVAILABLE()
1050
+ }
1051
+
1052
+ function readBlock (rx, index) {
1053
+ const promise = rx.getBlock(index)
1054
+ rx.tryFlush()
1055
+ return promise
1056
+ }
1057
+
1058
+ function initOnce (session, storage, key, opts) {
1059
+ if (storage === null) storage = opts.storage || null
1060
+ if (key === null) key = opts.key || null
1061
+
1062
+ session.core = new Core(Hypercore.defaultStorage(storage), {
1063
+ eagerUpgrade: true,
1064
+ notDownloadingLinger: opts.notDownloadingLinger,
1065
+ allowFork: opts.allowFork !== false,
1066
+ inflightRange: opts.inflightRange,
1067
+ compat: opts.compat === true,
1068
+ force: opts.force,
1069
+ createIfMissing: opts.createIfMissing,
1070
+ discoveryKey: opts.discoveryKey,
1071
+ overwrite: opts.overwrite,
1072
+ key,
1073
+ keyPair: opts.keyPair,
1074
+ legacy: opts.legacy,
1075
+ manifest: opts.manifest,
1076
+ globalCache: opts.globalCache || null // session is a temp option, not to be relied on unless you know what you are doing (no semver guarantees)
1077
+ })
1078
+ }
1079
+
1080
+ function maybeAddMonitor (name) {
1081
+ if (name === 'append' || name === 'truncate') return
1082
+ if (this._monitorIndex >= 0 || this.closing) return
1083
+
1084
+ if (this.core === null) {
1085
+ this._monitorIndex = -2
1086
+ } else {
1087
+ this.core.addMonitor(this)
1088
+ }
1089
+ }
1090
+
1091
+ function isSessionMoved (err) {
1092
+ return err.code === 'SESSION_MOVED'
1149
1093
  }