corestore 6.0.0-beta2 → 6.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.
@@ -1,5 +1,4 @@
1
- name: Test on Node.js
2
-
1
+ name: Build Status
3
2
  on:
4
3
  push:
5
4
  branches:
@@ -11,13 +10,13 @@ jobs:
11
10
  build:
12
11
  strategy:
13
12
  matrix:
14
- node-version: [12.x, 14.x]
15
- os: [ubuntu-16.04, macos-latest, windows-latest]
13
+ node-version: [lts/*]
14
+ os: [ubuntu-latest, macos-latest, windows-latest]
16
15
  runs-on: ${{ matrix.os }}
17
16
  steps:
18
17
  - uses: actions/checkout@v2
19
18
  - name: Use Node.js ${{ matrix.node-version }}
20
- uses: actions/setup-node@v1
19
+ uses: actions/setup-node@v2
21
20
  with:
22
21
  node-version: ${{ matrix.node-version }}
23
22
  - run: npm install
package/README.md CHANGED
@@ -1,2 +1,59 @@
1
- # neocorestore
2
- A new take on Corestore
1
+ # Corestore
2
+
3
+ Corestore is a Hypercore factory that makes it easier to manage large collections of named Hypercores.
4
+
5
+ Corestore provides:
6
+ 1. __Key Derivation__ - All writable Hypercore keys are derived from a single master key and a user-provided name.
7
+ 2. __Session Handling__ - If a single Hypercore is loaded multiple times through the `get` method, the underlying resources will only be opened once (using Hypercore 10's new session feature). Once all sessions are closed, the resources will be released.
8
+ 3. __Storage Management__ - Hypercores can be stored in any random-access-storage instance, where they will be keyed by their discovery keys.
9
+ 4. __Namespacing__ - You can share a single Corestore instance between multiple applications or components without worrying about naming collisions by creating "namespaces" (e.g. `corestore.namespace('my-app').get({ name: 'main' })
10
+
11
+ ### Installation
12
+ `npm install corestore`
13
+
14
+ ### Usage
15
+ A corestore instance can be constructed with a random-access-storage module, a function that returns a random-access-storage module given a path, or a string. If a string is specified, it will be assumed to be a path to a local storage directory:
16
+ ```js
17
+ const Corestore = require('corestore')
18
+
19
+ const store = new Corestore('./my-storage')
20
+ const core1 = store.get({ name: 'core-1' })
21
+ const core2 = store.get({ name: 'core-2' })
22
+ ```
23
+
24
+ ### API
25
+ #### `const store = new Corestore(storage)`
26
+ Create a new Corestore instance.
27
+
28
+ `storage` can be either a random-access-storage module, a string, or a function that takes a path and returns an random-access-storage instance.
29
+
30
+ #### `const core = store.get(key | { name: 'a-name', ...hypercoreOpts})`
31
+ Loads a Hypercore, either by name (if the `name` option is provided), or from the provided key (if the first argument is a Buffer, or if the `key` options is set).
32
+
33
+ If that Hypercore has previously been loaded, subsequent calls to `get` will return a new Hypercore session on the existing core.
34
+
35
+ All other options besides `name` and `key` will be forwarded to the Hypercore constructor.
36
+
37
+ #### `const stream = store.replicate(opts)`
38
+ Creates a replication stream that's capable of replicating all Hypercores that are managed by the Corestore, assuming the remote peer has the correct capabilities.
39
+
40
+ `opts` will be forwarded to Hypercore's `replicate` function.
41
+
42
+ Corestore replicates in an "all-to-all" fashion, meaning that when replication begins, it will attempt to replicate every Hypercore that's currently loaded and in memory. These attempts will fail if the remote side doesn't have a Hypercore's capability -- Corestore replication does not exchange Hypercore keys.
43
+
44
+ If the remote side dynamically adds a new Hypercore to the replication stream, Corestore will load and replicatethat core if possible.
45
+
46
+ #### `const store = store.namespace(name)`
47
+ Create a new namespaced Corestore. Namespacing is useful if you're going to be sharing a single Corestore instance between many applications or components, as it prevents name collisions.
48
+
49
+ Namespaces can be chained:
50
+ ```js
51
+ const ns1 = store.namespace('a')
52
+ const ns2 = ns1.namespace('b')
53
+ const core1 = ns1.get({ name: 'main' }) // These will load different Hypercores
54
+ const core2 = ns2.get({ name: 'main' })
55
+ ```
56
+
57
+ ### License
58
+ MIT
59
+
package/index.js CHANGED
@@ -1,122 +1,373 @@
1
- const { NanoresourcePromise: Nanoresource } = require('nanoresource-promise/emitter')
2
- const raf = require('random-access-file')
1
+ const { EventEmitter } = require('events')
2
+ const safetyCatch = require('safety-catch')
3
+ const crypto = require('hypercore-crypto')
4
+ const sodium = require('sodium-universal')
5
+ const Hypercore = require('hypercore')
6
+ const Xache = require('xache')
7
+ const b4a = require('b4a')
3
8
 
4
- const Replicator = require('./lib/replicator')
5
- const Index = require('./lib/db')
6
- const Loader = require('./lib/loader')
7
- const errors = require('./lib/errors')
9
+ const [NS] = crypto.namespace('corestore', 1)
10
+ const DEFAULT_NAMESPACE = b4a.alloc(32) // This is meant to be 32 0-bytes
8
11
 
9
- module.exports = class Corestore extends Nanoresource {
12
+ const CORES_DIR = 'cores'
13
+ const PRIMARY_KEY_FILE_NAME = 'primary-key'
14
+ const USERDATA_NAME_KEY = 'corestore/name'
15
+ const USERDATA_NAMESPACE_KEY = 'corestore/namespace'
16
+
17
+ module.exports = class Corestore extends EventEmitter {
10
18
  constructor (storage, opts = {}) {
11
19
  super()
12
20
 
13
- if (typeof storage === 'string') storage = defaultStorage(storage)
14
- if (typeof storage !== 'function') throw new errors.InvalidStorageError()
15
- this.storage = storage
21
+ this.storage = Hypercore.defaultStorage(storage, { lock: PRIMARY_KEY_FILE_NAME })
22
+ this.cores = opts._cores || new Map()
23
+ this.primaryKey = null
24
+ this.cache = !!opts.cache
25
+
26
+ this._keyStorage = null
27
+ this._primaryKey = opts.primaryKey
28
+ this._namespace = opts.namespace || DEFAULT_NAMESPACE
29
+ this._replicationStreams = opts._streams || []
30
+ this._overwrite = opts.overwrite === true
31
+
32
+ this._streamSessions = opts._streamSessions || new Map()
33
+ this._sessions = new Set() // sessions for THIS namespace
16
34
 
17
- this._namespace = opts.namespace || []
18
- this._db = opts._db || new Index(this.storage, opts)
19
- this._loader = opts._loader || new Loader(this.storage, this._db, opts)
20
- this._replicator = opts._replicator || new Replicator(this._loader, opts)
35
+ this._findingPeersCount = 0
36
+ this._findingPeers = []
21
37
 
22
- this._db.on('error', err => this.emit('db-error', err))
23
- this._loader.on('error', err => this.emit('error', err))
24
- this._loader.on('core', (core, opts) => this.emit('core', core, opts))
25
- this._loader.on('core', (core, opts) => this.emit('feed', core, opts))
38
+ if (this._namespace.byteLength !== 32) throw new Error('Namespace must be a 32-byte Buffer or Uint8Array')
26
39
 
27
- this.ready = this.open.bind(this)
40
+ this._opening = opts._opening ? opts._opening.then(() => this._open()) : this._open()
41
+ this._opening.catch(safetyCatch)
42
+ this.ready = () => this._opening
28
43
  }
29
44
 
30
- get cache () {
31
- return this._loader.cache
45
+ findingPeers () {
46
+ let done = false
47
+ this._incFindingPeers()
48
+
49
+ return () => {
50
+ if (done) return
51
+ done = true
52
+ this._decFindingPeers()
53
+ }
32
54
  }
33
55
 
34
- // Nanoresource Methods
56
+ _incFindingPeers () {
57
+ if (++this._findingPeersCount !== 1) return
58
+
59
+ for (const core of this._sessions) {
60
+ this._findingPeers.push(core.findingPeers())
61
+ }
62
+ }
63
+
64
+ _decFindingPeers () {
65
+ if (--this._findingPeersCount !== 0) return
66
+
67
+ while (this._findingPeers.length > 0) {
68
+ this._findingPeers.pop()()
69
+ }
70
+ }
35
71
 
36
72
  async _open () {
37
- await this._db.open()
38
- return this._loader.open()
73
+ if (this._primaryKey) {
74
+ this.primaryKey = await this._primaryKey
75
+ return this.primaryKey
76
+ }
77
+ this._keyStorage = this.storage(PRIMARY_KEY_FILE_NAME)
78
+ this.primaryKey = await new Promise((resolve, reject) => {
79
+ this._keyStorage.stat((err, st) => {
80
+ if (err && err.code !== 'ENOENT') return reject(err)
81
+ if (err || st.size < 32 || this._overwrite) {
82
+ const key = crypto.randomBytes(32)
83
+ return this._keyStorage.write(0, key, err => {
84
+ if (err) return reject(err)
85
+ return resolve(key)
86
+ })
87
+ }
88
+ this._keyStorage.read(0, 32, (err, key) => {
89
+ if (err) return reject(err)
90
+ return resolve(key)
91
+ })
92
+ })
93
+ })
94
+ return this.primaryKey
39
95
  }
40
96
 
41
- async _close () {
42
- await this._replicator.close()
43
- await this._loader.close()
44
- return this._db.close()
97
+ async _generateKeys (opts) {
98
+ if (opts._discoveryKey) {
99
+ return {
100
+ keyPair: null,
101
+ auth: null,
102
+ discoveryKey: opts._discoveryKey
103
+ }
104
+ }
105
+ if (!opts.name) {
106
+ return {
107
+ keyPair: {
108
+ publicKey: opts.publicKey,
109
+ secretKey: opts.secretKey
110
+ },
111
+ sign: opts.sign,
112
+ auth: opts.auth,
113
+ discoveryKey: crypto.discoveryKey(opts.publicKey)
114
+ }
115
+ }
116
+ const { publicKey, auth } = await this.createKeyPair(opts.name)
117
+ return {
118
+ keyPair: {
119
+ publicKey,
120
+ secretKey: null
121
+ },
122
+ auth,
123
+ discoveryKey: crypto.discoveryKey(publicKey)
124
+ }
45
125
  }
46
126
 
47
- // Private Methods
127
+ _getPrereadyUserData (core, key) {
128
+ // Need to manually read the header values before the Hypercore is ready, hence the ugliness.
129
+ for (const { key: savedKey, value } of core.core.header.userData) {
130
+ if (key === savedKey) return value
131
+ }
132
+ return null
133
+ }
134
+
135
+ async _preready (core) {
136
+ const name = this._getPrereadyUserData(core, USERDATA_NAME_KEY)
137
+ if (!name) return
138
+
139
+ const namespace = this._getPrereadyUserData(core, USERDATA_NAMESPACE_KEY)
140
+ const { publicKey, auth } = await this.createKeyPair(b4a.toString(name), namespace)
141
+ if (!b4a.equals(publicKey, core.key)) throw new Error('Stored core key does not match the provided name')
142
+
143
+ // TODO: Should Hypercore expose a helper for this, or should preready return keypair/auth?
144
+ core.auth = auth
145
+ core.key = publicKey
146
+ core.writable = true
147
+ }
48
148
 
49
- _validateGetOptions (opts) {
50
- if (typeof opts === 'object') {
51
- if (!Buffer.isBuffer(opts)) {
52
- if (!opts.key && !opts.name && !opts.keyPair) throw new errors.InvalidOptionsError()
149
+ async _preload (opts) {
150
+ await this.ready()
151
+
152
+ const { discoveryKey, keyPair, auth } = await this._generateKeys(opts)
153
+ const id = b4a.toString(discoveryKey, 'hex')
154
+
155
+ while (this.cores.has(id)) {
156
+ const existing = this.cores.get(id)
157
+ if (existing.opened && !existing.closing) return { from: existing, keyPair, auth }
158
+ if (existing.closing) {
159
+ await existing.close()
53
160
  } else {
54
- opts = { key: opts }
161
+ await existing.ready().catch(safetyCatch)
55
162
  }
56
- } else {
57
- opts = { key: Buffer.from(opts, 'hex') }
58
163
  }
59
- if (opts.key && typeof opts.key === 'string') opts.key = Buffer.from(opts.key, 'hex')
60
- if (opts.key && opts.key.length !== 32) throw new errors.InvalidKeyError()
61
- if (opts.name && !Array.isArray(opts.name)) opts.name = [opts.name]
62
- return opts
164
+
165
+ const userData = {}
166
+ if (opts.name) {
167
+ userData[USERDATA_NAME_KEY] = b4a.from(opts.name)
168
+ userData[USERDATA_NAMESPACE_KEY] = this._namespace
169
+ }
170
+
171
+ // No more async ticks allowed after this point -- necessary for caching
172
+
173
+ const storageRoot = [CORES_DIR, id.slice(0, 2), id.slice(2, 4), id].join('/')
174
+ const core = new Hypercore(p => this.storage(storageRoot + '/' + p), {
175
+ _preready: this._preready.bind(this),
176
+ autoClose: true,
177
+ encryptionKey: opts.encryptionKey || null,
178
+ userData,
179
+ auth,
180
+ cache: opts.cache,
181
+ createIfMissing: !opts._discoveryKey,
182
+ keyPair: keyPair && keyPair.publicKey
183
+ ? {
184
+ publicKey: keyPair.publicKey,
185
+ secretKey: null
186
+ }
187
+ : null
188
+ })
189
+
190
+ this.cores.set(id, core)
191
+ core.ready().then(() => {
192
+ for (const { stream } of this._replicationStreams) {
193
+ const sessions = this._streamSessions.get(stream)
194
+ const session = core.session()
195
+ sessions.push(session)
196
+ core.replicate(stream)
197
+ }
198
+ }, () => {
199
+ this.cores.delete(id)
200
+ })
201
+ core.once('close', () => {
202
+ this.cores.delete(id)
203
+ })
204
+
205
+ return { from: core, keyPair, auth }
63
206
  }
64
207
 
65
- // Public Methods
208
+ async createKeyPair (name, namespace = this._namespace) {
209
+ if (!this.primaryKey) await this._opening
66
210
 
67
- get (opts = {}) {
68
- opts = this._validateGetOptions(opts)
69
- if (!this.opened) this.open().catch(err => this.emit('error', err))
70
- return this._loader.get(this._namespace, opts)
211
+ const keyPair = {
212
+ publicKey: b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES),
213
+ secretKey: b4a.alloc(sodium.crypto_sign_SECRETKEYBYTES),
214
+ auth: {
215
+ sign: (msg) => sign(keyPair, msg),
216
+ verify: (signable, signature) => {
217
+ return crypto.verify(signable, signature, keyPair.publicKey)
218
+ }
219
+ }
220
+ }
221
+
222
+ const seed = deriveSeed(this.primaryKey, namespace, name)
223
+ sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
224
+
225
+ return keyPair
71
226
  }
72
227
 
73
- namespace (name) {
74
- if (!name) throw new Error('A name must be provided as the first argument.')
75
- if (Buffer.isBuffer(name)) name = name.toString('hex')
76
- return new Corestore(this.storage, {
77
- namespace: [...this._namespace, name],
78
- _db: this._db,
79
- _loader: this._loader,
80
- _replicator: this._replicator
228
+ get (opts = {}) {
229
+ opts = validateGetOptions(opts)
230
+
231
+ if (opts.cache !== false) {
232
+ opts.cache = opts.cache === true || (this.cache && !opts.cache) ? defaultCache() : opts.cache
233
+ }
234
+
235
+ const core = new Hypercore(null, {
236
+ ...opts,
237
+ name: null,
238
+ preload: () => this._preload(opts)
81
239
  })
82
- }
83
240
 
84
- replicate (isInitiator, opts) {
85
- return this._replicator.replicate(isInitiator, opts)
241
+ this._sessions.add(core)
242
+ if (this._findingPeersCount > 0) {
243
+ this._findingPeers.push(core.findingPeers())
244
+ }
245
+
246
+ core.once('close', () => {
247
+ // technically better to also clear _findingPeers if we added it,
248
+ // but the lifecycle for those are pretty short so prob not worth the complexity
249
+ // as _decFindingPeers clear them all.
250
+ this._sessions.delete(core)
251
+ })
252
+
253
+ return core
86
254
  }
87
255
 
88
- // Backup/Restore
256
+ replicate (isInitiator, opts) {
257
+ const isExternal = isStream(isInitiator) || !!(opts && opts.stream)
258
+ const stream = Hypercore.createProtocolStream(isInitiator, {
259
+ ...opts,
260
+ ondiscoverykey: discoveryKey => {
261
+ const core = this.get({ _discoveryKey: discoveryKey })
262
+ return core.ready().catch(safetyCatch)
263
+ }
264
+ })
89
265
 
90
- async backup () {
91
- if (!this.opened) await this.open()
92
- const allCores = await this._db.getAllCores()
93
- return {
94
- masterKey: this._loader.masterKey.toString('hex'),
95
- cores: allCores
266
+ const sessions = []
267
+ for (const core of this.cores.values()) {
268
+ if (!core.opened) continue // If the core is not opened, it will be replicated in preload.
269
+ const session = core.session()
270
+ sessions.push(session)
271
+ core.replicate(stream)
96
272
  }
273
+
274
+ const streamRecord = { stream, isExternal }
275
+ this._replicationStreams.push(streamRecord)
276
+ this._streamSessions.set(stream, sessions)
277
+
278
+ stream.once('close', () => {
279
+ this._replicationStreams.splice(this._replicationStreams.indexOf(streamRecord), 1)
280
+ this._streamSessions.delete(stream)
281
+ Promise.all(sessions.map(s => s.close())).catch(safetyCatch)
282
+ })
283
+ return stream
97
284
  }
98
285
 
99
- restore (manifest) {
100
- return this._db.restore(manifest)
286
+ namespace (name) {
287
+ return new Corestore(this.storage, {
288
+ primaryKey: this._opening.then(() => this.primaryKey),
289
+ namespace: generateNamespace(this._namespace, name),
290
+ cache: this.cache,
291
+ _opening: this._opening,
292
+ _cores: this.cores,
293
+ _streams: this._replicationStreams,
294
+ _streamSessions: this._streamSessions
295
+ })
101
296
  }
102
297
 
103
- static async restore (manifest, target) {
104
- if (!manifest || !manifest.masterKey) throw new Error('Malformed manifest.')
105
- const store = new this(target, {
106
- overwriteMasterKey: true,
107
- masterKey: Buffer.from(manifest.masterKey, 'hex')
298
+ async _close () {
299
+ await this._opening
300
+ if (!b4a.equals(this._namespace, DEFAULT_NAMESPACE)) {
301
+ // namespaces should not release resources on close
302
+ // TODO: Refactor the namespace close logic to actually close sessions with ref counting
303
+ return
304
+ }
305
+ const closePromises = []
306
+ for (const core of this.cores.values()) {
307
+ closePromises.push(core.close())
308
+ }
309
+ await Promise.allSettled(closePromises)
310
+ for (const { stream, isExternal } of this._replicationStreams) {
311
+ // Only close streams that were created by the Corestore
312
+ if (!isExternal) stream.destroy()
313
+ }
314
+ if (!this._keyStorage) return
315
+ await new Promise((resolve, reject) => {
316
+ this._keyStorage.close(err => {
317
+ if (err) return reject(err)
318
+ return resolve(null)
319
+ })
108
320
  })
109
- await store.restore(manifest)
110
- return store
111
321
  }
322
+
323
+ close () {
324
+ if (this._closing) return this._closing
325
+ this._closing = this._close()
326
+ this._closing.catch(safetyCatch)
327
+ return this._closing
328
+ }
329
+ }
330
+
331
+ function sign (keyPair, message) {
332
+ if (!keyPair.secretKey) throw new Error('Invalid key pair')
333
+ return crypto.sign(message, keyPair.secretKey)
112
334
  }
113
335
 
114
- function defaultStorage (dir) {
115
- return function (name) {
116
- let lock = null
117
- try {
118
- lock = name.endsWith('/bitfield') ? require('fd-lock') : null
119
- } catch (err) {}
120
- return raf(name, { directory: dir, lock: lock })
336
+ function validateGetOptions (opts) {
337
+ if (b4a.isBuffer(opts)) return { key: opts, publicKey: opts }
338
+ if (opts.key) {
339
+ opts.publicKey = opts.key
340
+ }
341
+ if (opts.keyPair) {
342
+ opts.publicKey = opts.keyPair.publicKey
343
+ opts.secretKey = opts.keyPair.secretKey
121
344
  }
345
+ if (opts.name && typeof opts.name !== 'string') throw new Error('name option must be a String')
346
+ if (opts.name && opts.secretKey) throw new Error('Cannot provide both a name and a secret key')
347
+ if (opts.publicKey && !b4a.isBuffer(opts.publicKey)) throw new Error('publicKey option must be a Buffer or Uint8Array')
348
+ if (opts.secretKey && !b4a.isBuffer(opts.secretKey)) throw new Error('secretKey option must be a Buffer or Uint8Array')
349
+ if (!opts._discoveryKey && (!opts.name && !opts.publicKey)) throw new Error('Must provide either a name or a publicKey')
350
+ return opts
351
+ }
352
+
353
+ function generateNamespace (namespace, name) {
354
+ if (!b4a.isBuffer(name)) name = b4a.from(name)
355
+ const out = b4a.allocUnsafe(32)
356
+ sodium.crypto_generichash_batch(out, [namespace, name])
357
+ return out
358
+ }
359
+
360
+ function deriveSeed (primaryKey, namespace, name) {
361
+ if (!b4a.isBuffer(name)) name = b4a.from(name)
362
+ const out = b4a.alloc(32)
363
+ sodium.crypto_generichash_batch(out, [NS, namespace, name], primaryKey)
364
+ return out
365
+ }
366
+
367
+ function defaultCache () {
368
+ return new Xache({ maxSize: 65536, maxAge: 0 })
369
+ }
370
+
371
+ function isStream (s) {
372
+ return typeof s === 'object' && s && typeof s.pipe === 'function'
122
373
  }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "corestore",
3
- "version": "6.0.0-beta2",
4
- "description": "A Hypercore factory that stores and replicates linked cores.",
3
+ "version": "6.0.0",
4
+ "description": "A Hypercore factory that simplifies managing collections of cores.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "standard && tape test/*.js"
7
+ "test": "standard && brittle test/*.js"
8
8
  },
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/andrewosh/corestore.git"
11
+ "url": "git+https://github.com/hypercore-protocol/corestore.git"
12
12
  },
13
13
  "keywords": [
14
14
  "corestore"
@@ -16,30 +16,20 @@
16
16
  "author": "Andrew Osheroff <andrewosh@gmail.com>",
17
17
  "license": "MIT",
18
18
  "bugs": {
19
- "url": "https://github.com/andrewosh/corestore/issues"
19
+ "url": "https://github.com/hypercore-protocol/corestore/issues"
20
20
  },
21
- "homepage": "https://github.com/andrewosh/corestore#readme",
21
+ "homepage": "https://github.com/hypercore-protocol/corestore#readme",
22
22
  "devDependencies": {
23
- "@andrewosh/corestore-migration": "^5.8.2",
24
- "hypercore-promisifier": "^1.0.3",
25
- "random-access-memory": "^3.1.1",
26
- "standard": "^16.0.3",
27
- "tape": "^5.0.1"
23
+ "brittle": "^3.0.0",
24
+ "random-access-memory": "^4.0.0",
25
+ "standardx": "^7.0.0"
28
26
  },
29
27
  "dependencies": {
30
- "@jsdevtools/readdir-enhanced": "^6.0.4",
31
- "custom-error-class": "^1.0.0",
32
- "derive-key": "^1.0.1",
33
- "end-of-stream": "^1.4.4",
34
- "fd-lock": "^1.1.1",
35
- "hyperbee": "^1.1.1",
36
- "hypercore": "^9.6.0",
37
- "hypercore-crypto": "^2.2.0",
38
- "hypercore-protocol": "^8.0.7",
39
- "nanoresource-promise": "^2.0.0",
40
- "random-access-file": "^2.1.4",
41
- "random-access-storage": "^1.4.1",
42
- "refpool": "^1.2.2",
43
- "varint": "^6.0.0"
28
+ "b4a": "^1.3.1",
29
+ "hypercore": "v10.0.0",
30
+ "hypercore-crypto": "^3.2.1",
31
+ "safety-catch": "^1.0.1",
32
+ "sodium-universal": "^3.0.4",
33
+ "xache": "^1.1.0"
44
34
  }
45
35
  }