hypercore 10.38.1 → 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,64 +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
- get indexedLength () {
563
- return this.length
518
+ get signedLength () {
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()
522
+
523
+ return flushed === -1 ? this.state.length : flushed
564
524
  }
565
525
 
566
526
  /**
567
527
  * Deprecated. Use `const { byteLength } = await core.info()`.
568
528
  */
569
529
  get byteLength () {
530
+ if (this.opened === false) return 0
570
531
  if (this._snapshot) return this._snapshot.byteLength
571
- if (this.core === null) return 0
572
- if (!this.sparse) return this.contiguousByteLength
573
- return this.core.tree.byteLength - (this.core.tree.length * this.padding)
532
+ return this.state.byteLength - (this.state.length * this.padding)
574
533
  }
575
534
 
576
535
  get contiguousLength () {
577
- 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)
578
538
  }
579
539
 
580
540
  get contiguousByteLength () {
@@ -582,11 +542,12 @@ module.exports = class Hypercore extends EventEmitter {
582
542
  }
583
543
 
584
544
  get fork () {
585
- return this.core === null ? 0 : this.core.tree.fork
545
+ if (this.opened === false) return 0
546
+ return this.state.fork
586
547
  }
587
548
 
588
549
  get peers () {
589
- return this.replicator === null ? [] : this.replicator.peers
550
+ return this.opened === false ? [] : this.core.replicator.peers
590
551
  }
591
552
 
592
553
  get encryptionKey () {
@@ -598,155 +559,69 @@ module.exports = class Hypercore extends EventEmitter {
598
559
  }
599
560
 
600
561
  get globalCache () {
601
- return this.core && this.core.globalCache
562
+ return this.opened === false ? null : this.core.globalCache
602
563
  }
603
564
 
604
565
  ready () {
605
566
  return this.opening
606
567
  }
607
568
 
608
- _onupload (index, value, from) {
609
- const byteLength = value.byteLength - this.padding
610
-
611
- for (let i = 0; i < this.sessions.length; i++) {
612
- this.sessions[i].emit('upload', index, byteLength, from)
613
- }
614
- }
615
-
616
- _oninvalid (err, req, res, from) {
617
- for (let i = 0; i < this.sessions.length; i++) {
618
- this.sessions[i].emit('verification-error', err, req, res, from)
619
- }
620
- }
621
-
622
- async _oncoreconflict (proof, from) {
623
- await this.replicator.onconflict(from)
624
-
625
- for (const s of this.sessions) s.emit('conflict', proof.upgrade.length, proof.fork, proof)
626
-
627
- const err = new Error('Two conflicting signatures exist for length ' + proof.upgrade.length)
628
- await this._closeAllSessions(err)
569
+ async setUserData (key, value) {
570
+ if (this.opened === false) await this.opening
571
+ await this.state.setUserData(key, value)
629
572
  }
630
573
 
631
- async _closeAllSessions (err) {
632
- // this.sessions modifies itself when a session closes
633
- // This way we ensure we indeed iterate over all sessions
634
- const sessions = [...this.sessions]
635
-
636
- const all = []
637
- for (const s of sessions) all.push(s.close(err))
638
- 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
639
580
  }
640
581
 
641
- _oncoreupdate (status, bitfield, value, from) {
642
- if (status !== 0) {
643
- const truncatedNonSparse = (status & 0b1000) !== 0
644
- const appendedNonSparse = (status & 0b0100) !== 0
645
- const truncated = (status & 0b0010) !== 0
646
- const appended = (status & 0b0001) !== 0
582
+ transferSession (core) {
583
+ // todo: validate we can move
647
584
 
648
- if (truncated) {
649
- this.replicator.ontruncate(bitfield.start, bitfield.length)
650
- }
651
-
652
- if ((status & 0b10011) !== 0) {
653
- this.replicator.onupgrade()
654
- }
655
-
656
- if (status & 0b10000) {
657
- for (let i = 0; i < this.sessions.length; i++) {
658
- const s = this.sessions[i]
659
-
660
- if (s.encryption && s.encryption.compat !== this.core.compat) {
661
- s.encryption = new BlockEncryption(s.encryption.key, this.key, { compat: this.core.compat, isBlockKey: s.encryption.isBlockKey })
662
- }
663
- }
664
-
665
- for (let i = 0; i < this.sessions.length; i++) {
666
- this.sessions[i].emit('manifest')
667
- }
668
- }
669
-
670
- for (let i = 0; i < this.sessions.length; i++) {
671
- const s = this.sessions[i]
672
-
673
- if (truncated) {
674
- if (s.cache) s.cache.clear()
675
-
676
- // If snapshotted, make sure to update our compat so we can fail gets
677
- if (s._snapshot && bitfield.start < s._snapshot.compatLength) s._snapshot.compatLength = bitfield.start
678
- }
679
-
680
- if (s.sparse ? truncated : truncatedNonSparse) {
681
- s.emit('truncate', bitfield.start, this.core.tree.fork)
682
- }
683
-
684
- // For sparse sessions, immediately emit appends. If non-sparse, emit if contig length has updated
685
- if (s.sparse ? appended : appendedNonSparse) {
686
- s.emit('append')
687
- }
688
- }
689
-
690
- const contig = this.core.header.hints.contiguousLength
691
-
692
- // When the contig length catches up, broadcast the non-sparse length to peers
693
- if (appendedNonSparse && contig === this.core.tree.length) {
694
- for (const peer of this.peers) {
695
- if (peer.broadcastedNonSparse) continue
696
-
697
- peer.broadcastRange(0, contig)
698
- peer.broadcastedNonSparse = true
699
- }
700
- }
585
+ if (this.weak === false) {
586
+ this.core.activeSessions--
587
+ core.activeSessions++
701
588
  }
702
589
 
703
- if (bitfield) {
704
- this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
590
+ if (this._monitorIndex >= 0) {
591
+ this.core.removeMonitor(this)
592
+ core.addMonitor(this)
705
593
  }
706
594
 
707
- if (value) {
708
- const byteLength = value.byteLength - this.padding
709
-
710
- for (let i = 0; i < this.sessions.length; i++) {
711
- this.sessions[i].emit('download', bitfield.start, byteLength, from)
712
- }
713
- }
714
- }
595
+ const old = this.core
715
596
 
716
- _onpeerupdate (added, peer) {
717
- const name = added ? 'peer-add' : 'peer-remove'
597
+ this.core = core
718
598
 
719
- for (let i = 0; i < this.sessions.length; i++) {
720
- this.sessions[i].emit(name, peer)
599
+ old.replicator.clearRequests(this.activeRequests, SESSION_MOVED())
721
600
 
722
- if (added) {
723
- for (const ext of this.sessions[i].extensions.values()) {
724
- peer.extensions.set(ext.name, ext)
725
- }
726
- }
727
- }
601
+ this.emit('migrate', this.key)
728
602
  }
729
603
 
730
- async setUserData (key, value, { flush = false } = {}) {
731
- if (this.opened === false) await this.opening
732
- return this.core.userData(key, value, flush)
604
+ createTreeBatch () {
605
+ return this.state.createTreeBatch()
733
606
  }
734
607
 
735
- async getUserData (key) {
608
+ async restoreBatch (length, additionalBlocks = []) {
736
609
  if (this.opened === false) await this.opening
737
- for (const { key: savedKey, value } of this.core.header.userData) {
738
- 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')
739
614
  }
740
- return null
741
- }
742
615
 
743
- createTreeBatch () {
744
- 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
745
620
  }
746
621
 
747
622
  findingPeers () {
748
623
  this._findingPeers++
749
- if (this.replicator !== null && !this.closing) this.replicator.findingPeers++
624
+ if (this.core !== null && !this.closing) this.core.replicator.findingPeers++
750
625
 
751
626
  let once = true
752
627
 
@@ -754,8 +629,8 @@ module.exports = class Hypercore extends EventEmitter {
754
629
  if (this.closing || !once) return
755
630
  once = false
756
631
  this._findingPeers--
757
- if (this.replicator !== null && --this.replicator.findingPeers === 0) {
758
- this.replicator.updateAll()
632
+ if (this.core !== null && --this.core.replicator.findingPeers === 0) {
633
+ this.core.replicator.updateAll()
759
634
  }
760
635
  }
761
636
  }
@@ -769,42 +644,40 @@ module.exports = class Hypercore extends EventEmitter {
769
644
  async update (opts) {
770
645
  if (this.opened === false) await this.opening
771
646
  if (this.closing !== null) return false
647
+ if (this.snapshotted) return false
772
648
 
773
- if (this.writable && (!opts || opts.force !== true)) {
774
- if (!this.snapshotted) return false
775
- return this._updateSnapshot()
776
- }
649
+ if (this.writable && (!opts || opts.force !== true)) return false
777
650
 
778
- const remoteWait = this._shouldWait(opts, this.replicator.findingPeers > 0)
651
+ const remoteWait = this._shouldWait(opts, this.core.replicator.findingPeers > 0)
779
652
 
780
653
  let upgraded = false
781
654
 
782
- if (await this.replicator.applyPendingReorg()) {
655
+ if (await this.core.replicator.applyPendingReorg()) {
783
656
  upgraded = true
784
657
  }
785
658
 
786
659
  if (!upgraded && remoteWait) {
787
660
  const activeRequests = (opts && opts.activeRequests) || this.activeRequests
788
- const req = this.replicator.addUpgrade(activeRequests)
661
+ const req = this.core.replicator.addUpgrade(activeRequests)
789
662
 
790
- 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
+ }
791
669
  }
792
670
 
793
671
  if (!upgraded) return false
794
- if (this.snapshotted) return this._updateSnapshot()
795
672
  return true
796
673
  }
797
674
 
798
- batch ({ checkout = -1, autoClose = true, session = true, restore = false, clear = false } = {}) {
799
- return new Batch(session ? this.session() : this, checkout, autoClose, restore, clear)
800
- }
801
-
802
675
  async seek (bytes, opts) {
803
676
  if (this.opened === false) await this.opening
804
677
  if (!isValidIndex(bytes)) throw ASSERTION('seek is invalid')
805
678
 
806
- const tree = (opts && opts.tree) || this.core.tree
807
- 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)
808
681
 
809
682
  const offset = await s.update()
810
683
  if (offset) return offset
@@ -814,22 +687,47 @@ module.exports = class Hypercore extends EventEmitter {
814
687
  if (!this._shouldWait(opts, this.wait)) return null
815
688
 
816
689
  const activeRequests = (opts && opts.activeRequests) || this.activeRequests
817
- const req = this.replicator.addSeek(activeRequests, s)
690
+ const req = this.core.replicator.addSeek(activeRequests, s)
818
691
 
819
692
  const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
820
693
  if (timeout) req.context.setTimeout(req, timeout)
821
694
 
822
- 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
+ }
823
701
  }
824
702
 
825
703
  async has (start, end = start + 1) {
826
704
  if (this.opened === false) await this.opening
827
705
  if (!isValidIndex(start) || !isValidIndex(end)) throw ASSERTION('has range is invalid')
828
706
 
829
- 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
+ }
830
721
 
831
- const i = this.core.bitfield.firstUnset(start)
832
- 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)
833
731
  }
834
732
 
835
733
  async get (index, opts) {
@@ -837,12 +735,10 @@ module.exports = class Hypercore extends EventEmitter {
837
735
  if (!isValidIndex(index)) throw ASSERTION('block index is invalid')
838
736
 
839
737
  if (this.closing !== null) throw SESSION_CLOSED()
840
- if (this._snapshot !== null && index >= this._snapshot.compatLength) throw SNAPSHOT_NOT_AVAILABLE()
841
738
 
842
739
  const encoding = (opts && opts.valueEncoding && c.from(opts.valueEncoding)) || this.valueEncoding
843
740
 
844
- let req = this.cache && this.cache.get(index)
845
- if (!req) req = this._get(index, opts)
741
+ const req = this._get(index, opts)
846
742
 
847
743
  let block = await req
848
744
  if (!block) return null
@@ -853,6 +749,7 @@ module.exports = class Hypercore extends EventEmitter {
853
749
  // Copy the block as it might be shared with other sessions.
854
750
  block = b4a.from(block)
855
751
 
752
+ if (this.encryption.compat !== this.core.compat) this._updateEncryption()
856
753
  this.encryption.decrypt(index, block)
857
754
  }
858
755
 
@@ -875,7 +772,7 @@ module.exports = class Hypercore extends EventEmitter {
875
772
  if (start >= end) return cleared
876
773
  if (start >= this.length) return cleared
877
774
 
878
- await this.core.clear(start, end, cleared)
775
+ await this.state.clear(start, end, cleared)
879
776
 
880
777
  return cleared
881
778
  }
@@ -886,46 +783,55 @@ module.exports = class Hypercore extends EventEmitter {
886
783
  }
887
784
 
888
785
  async _get (index, opts) {
889
- let block
786
+ if (this.core.isFlushing) await this.core.flushed()
890
787
 
891
- if (this.core.bitfield.get(index)) {
892
- const tree = (opts && opts.tree) || this.core.tree
893
- block = this.core.blocks.get(index, tree)
788
+ const block = await readBlock(this.state.storage.read(), index)
894
789
 
895
- if (this.cache) this.cache.set(index, block)
896
- } else {
897
- if (!this._shouldWait(opts, this.wait)) return null
790
+ if (block !== null) return block
898
791
 
899
- if (opts && opts.onwait) opts.onwait(index, this)
900
- if (this.onwait) this.onwait(index, this)
901
-
902
- const activeRequests = (opts && opts.activeRequests) || this.activeRequests
792
+ if (this.closing !== null) throw SESSION_CLOSED()
903
793
 
904
- const req = this.replicator.addBlock(activeRequests, index)
905
- 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)
906
798
 
907
- const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
908
- if (timeout) req.context.setTimeout(req, timeout)
799
+ checkSnapshot(this, index)
800
+ if (coreBlock !== null) return coreBlock
801
+ }
909
802
 
910
- 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
911
809
  }
912
810
 
913
- return block
914
- }
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)
915
815
 
916
- async _cacheOnResolve (index, req, fork) {
917
- const resolved = await req
816
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
918
817
 
919
- // Unslab only when it takes up less then half the slab
920
- const block = resolved !== null && 2 * resolved.byteLength < resolved.buffer.byteLength
921
- ? unslab(resolved)
922
- : resolved
818
+ const req = this.core.replicator.addBlock(activeRequests, index)
819
+ req.snapshot = index < this.length
923
820
 
924
- if (this.cache && fork === this.core.tree.fork) {
925
- 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
926
831
  }
927
832
 
928
- return block
833
+ if (this._snapshot !== null) checkSnapshot(this, index)
834
+ return maybeUnslab(replicatedBlock)
929
835
  }
930
836
 
931
837
  _shouldWait (opts, defaultValue) {
@@ -949,19 +855,7 @@ module.exports = class Hypercore extends EventEmitter {
949
855
  }
950
856
 
951
857
  download (range) {
952
- const req = this._download(range)
953
-
954
- // do not crash in the background...
955
- req.catch(safetyCatch)
956
-
957
- return new Download(req)
958
- }
959
-
960
- async _download (range) {
961
- if (this.opened === false) await this.opening
962
-
963
- const activeRequests = (range && range.activeRequests) || this.activeRequests
964
- return this.replicator.addRange(activeRequests, range)
858
+ return new Download(this, range)
965
859
  }
966
860
 
967
861
  // TODO: get rid of this / deprecate it?
@@ -978,27 +872,31 @@ module.exports = class Hypercore extends EventEmitter {
978
872
  if (this.opened === false) await this.opening
979
873
 
980
874
  const {
981
- fork = this.core.tree.fork + 1,
875
+ fork = this.state.fork + 1,
982
876
  keyPair = this.keyPair,
983
877
  signature = null
984
878
  } = typeof opts === 'number' ? { fork: opts } : opts
985
879
 
880
+ const isDefault = this.state === this.core.state
986
881
  const writable = !this._readonly && !!(signature || (keyPair && keyPair.secretKey))
987
- 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()
988
883
 
989
- await this.core.truncate(newLength, fork, { keyPair, signature })
884
+ await this.state.truncate(newLength, fork, { keyPair, signature })
990
885
 
991
886
  // TODO: Should propagate from an event triggered by the oplog
992
- this.replicator.updateAll()
887
+ if (this.state === this.core.state) this.core.replicator.updateAll()
993
888
  }
994
889
 
995
890
  async append (blocks, opts = {}) {
996
891
  if (this.opened === false) await this.opening
997
892
 
998
- const { keyPair = this.keyPair, signature = null } = opts
999
- 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
1000
895
 
1001
- 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()
1002
900
 
1003
901
  blocks = Array.isArray(blocks) ? blocks : [blocks]
1004
902
 
@@ -1017,17 +915,17 @@ module.exports = class Hypercore extends EventEmitter {
1017
915
  }
1018
916
  }
1019
917
 
1020
- return this.core.append(buffers, { keyPair, signature, preappend })
918
+ return this.state.append(buffers, { keyPair, signature, preappend })
1021
919
  }
1022
920
 
1023
921
  async treeHash (length) {
1024
922
  if (length === undefined) {
1025
923
  await this.ready()
1026
- length = this.core.tree.length
924
+ length = this.state.length
1027
925
  }
1028
926
 
1029
- const roots = await this.core.tree.getRoots(length)
1030
- return this.crypto.tree(roots)
927
+ const roots = await this.state.tree.getRoots(length)
928
+ return crypto.tree(roots)
1031
929
  }
1032
930
 
1033
931
  registerExtension (name, handlers = {}) {
@@ -1067,6 +965,10 @@ module.exports = class Hypercore extends EventEmitter {
1067
965
  }
1068
966
 
1069
967
  this.extensions.set(name, ext)
968
+
969
+ if (this.core === null) this._monitorIndex = -2
970
+ else this.core.addMonitor(this)
971
+
1070
972
  for (const peer of this.peers) {
1071
973
  peer.extensions.set(name, ext)
1072
974
  }
@@ -1105,41 +1007,87 @@ module.exports = class Hypercore extends EventEmitter {
1105
1007
  }
1106
1008
  return block
1107
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
+ }
1108
1016
  }
1109
1017
 
1018
+ module.exports = Hypercore
1019
+
1110
1020
  function isStream (s) {
1111
1021
  return typeof s === 'object' && s && typeof s.pipe === 'function'
1112
1022
  }
1113
1023
 
1114
- function isRandomAccessClass (fn) {
1115
- return !!(typeof fn === 'function' && fn.prototype && typeof fn.prototype.open === 'function')
1116
- }
1117
-
1118
1024
  function toHex (buf) {
1119
1025
  return buf && b4a.toString(buf, 'hex')
1120
1026
  }
1121
1027
 
1122
1028
  function preappend (blocks) {
1123
- const offset = this.core.tree.length
1124
- 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()
1125
1033
 
1126
1034
  for (let i = 0; i < blocks.length; i++) {
1127
1035
  this.encryption.encrypt(offset + i, blocks[i], fork)
1128
1036
  }
1129
1037
  }
1130
1038
 
1131
- function ensureEncryption (core, opts) {
1132
- if (!opts.encryptionKey) return
1133
- // Only override the block encryption if it's either not already set or if
1134
- // the caller provided a different key.
1135
- if (core.encryption && b4a.equals(core.encryption.key, opts.encryptionKey) && core.encryption.compat === core.core.compat) return
1136
- 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
1137
1041
  }
1138
1042
 
1139
- function createCache (cache) {
1140
- 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
1141
1046
  }
1142
1047
 
1143
- function isValidIndex (index) {
1144
- 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'
1145
1093
  }