corestore 6.18.4 → 7.0.1

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.
Files changed (4) hide show
  1. package/README.md +12 -38
  2. package/index.js +310 -457
  3. package/package.json +29 -30
  4. package/LICENSE +0 -21
package/README.md CHANGED
@@ -13,6 +13,11 @@ Corestore provides:
13
13
  ### Installation
14
14
  `npm install corestore`
15
15
 
16
+ > [!NOTE]
17
+ > This readme reflects Corestore 7, our latest major version that is backed by RocksDB for storage and atomicity.
18
+ > Whilst we are fully validating that, the npm dist-tag for latest is set to latest version of Corestore 7, the previous major, to avoid too much disruption.
19
+ > It will be updated to 11 in a few weeks.
20
+
16
21
  ### Usage
17
22
  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:
18
23
  ```js
@@ -24,27 +29,17 @@ const core2 = store.get({ name: 'core-2' })
24
29
  ```
25
30
 
26
31
  ### API
27
- #### `const store = new Corestore(storage, opts = {})`
32
+ #### `const store = new Corestore(storage)`
28
33
  Create a new Corestore instance.
29
34
 
30
35
  `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.
31
36
 
32
- Opts include:
33
- ```js
34
- {
35
- inflightRange: null // Advanced option. Forwarded to the Hypercores created by corestore.get(...)
36
- }
37
- ```
38
-
39
- #### `const core = store.get(key | { name: 'a-name', exclusive, ...hypercoreOpts})`
37
+ #### `const core = store.get(key | { name: 'a-name', ...hypercoreOpts})`
40
38
  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 String with hex/z32 key, or if the `key` options is set).
41
39
 
42
40
  If that Hypercore has previously been loaded, subsequent calls to `get` will return a new Hypercore session on the existing core.
43
41
 
44
- If you set the `exclusive` option and you are opening a writable session it will wait for all other exclusive writable to close before
45
- opening the Hypercore effectively meaning any op on the core will wait until its exclusive.
46
-
47
- All other options besides `name` and `key` and `exclusive` will be forwarded to the Hypercore constructor.
42
+ All other options besides `name` and `key` will be forwarded to the Hypercore constructor.
48
43
 
49
44
  #### `const stream = store.replicate(optsOrStream)`
50
45
  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.
@@ -67,8 +62,11 @@ swarm.join(...)
67
62
  swarm.on('connection', (connection) => store.replicate(connection))
68
63
  ```
69
64
 
65
+ #### `const storeB = storeA.session()`
66
+ Create a new Corestore session. Closing a session will close all cores made from this session.
67
+
70
68
  #### `const store = store.namespace(name)`
71
- 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.
69
+ Create a new namespaced Corestore session. Namespacing is useful if you're going to be sharing a single Corestore instance between many applications or components, as it prevents name collisions.
72
70
 
73
71
  Namespaces can be chained:
74
72
  ```js
@@ -78,32 +76,8 @@ const core1 = ns1.get({ name: 'main' }) // These will load different Hypercores
78
76
  const core2 = ns2.get({ name: 'main' })
79
77
  ```
80
78
 
81
- #### `const storeB = storeA.session(opts)`
82
- Create a new Corestore that shares resources with the original, like cache, cores, replication streams, and storage, while optionally resetting the namespace, overriding `primaryKey`.
83
- Useful when an application wants to accept an optional Corestore, but needs to maintain a predictable key derivation.
84
-
85
- `opts` are the same as the constructor options:
86
-
87
- ```js
88
- {
89
- primaryKey, // Overrides the primaryKey for this session
90
- namespace, // If set to null it will reset to the DEFAULT_NAMESPACE
91
- detach: true, // By disabling this, closing the session will also close the store that created the session
92
- inflightRange: null // Advanced option. Forwarded to the Hypercores created by `session.get(...)
93
- }
94
- ```
95
-
96
79
  #### `await store.close()`
97
80
  Fully close this Corestore instance.
98
81
 
99
- #### `store.on('core-open', core)`
100
- Emitted when the first session for a core is opened.
101
-
102
- *Note: This core may close at any time, so treat it as a weak reference*
103
-
104
- #### `store.on('core-close', core)`
105
- Emitted when the last session for a core is closed.
106
-
107
82
  ### License
108
83
  MIT
109
-
package/index.js CHANGED
@@ -1,562 +1,415 @@
1
- const safetyCatch = require('safety-catch')
2
- const crypto = require('hypercore-crypto')
3
- const sodium = require('sodium-universal')
4
- const Hypercore = require('hypercore')
5
- const hypercoreId = require('hypercore-id-encoding')
6
- const Xache = require('xache')
7
1
  const b4a = require('b4a')
2
+ const Hypercore = require('hypercore')
8
3
  const ReadyResource = require('ready-resource')
9
- const RW = require('read-write-mutexify')
4
+ const EventEmitter = require('events')
5
+ const sodium = require('sodium-universal')
6
+ const crypto = require('hypercore-crypto')
7
+ const ID = require('hypercore-id-encoding')
10
8
 
11
9
  const [NS] = crypto.namespace('corestore', 1)
12
10
  const DEFAULT_NAMESPACE = b4a.alloc(32) // This is meant to be 32 0-bytes
13
11
 
14
- const CORES_DIR = 'cores'
15
- const PRIMARY_KEY_FILE_NAME = 'primary-key'
16
- const USERDATA_NAME_KEY = 'corestore/name'
17
- const USERDATA_NAMESPACE_KEY = 'corestore/namespace'
18
- const POOL_SIZE = 512 // how many open fds to aim for before cycling them
19
- const DEFAULT_MANIFEST = 0 // bump to 1 when this is more widely deployed
20
- const DEFAULT_COMPAT = true
21
-
22
- module.exports = class Corestore extends ReadyResource {
23
- constructor (storage, opts = {}) {
24
- super()
25
-
26
- const root = opts._root
27
-
28
- this.storage = Hypercore.defaultStorage(storage, { lock: PRIMARY_KEY_FILE_NAME, poolSize: opts.poolSize || POOL_SIZE, rmdir: true })
29
- this.cores = root ? root.cores : new Map()
30
- this.cache = !!opts.cache
31
- this.primaryKey = opts.primaryKey || null
32
- this.passive = !!opts.passive
33
- this.manifestVersion = typeof opts.manifestVersion === 'number' ? opts.manifestVersion : (root ? root.manifestVersion : DEFAULT_MANIFEST)
34
- this.compat = typeof opts.compat === 'boolean' ? opts.compat : (root ? root.compat : DEFAULT_COMPAT)
35
- this.inflightRange = opts.inflightRange || null
36
- this.globalCache = opts.globalCache || null
37
-
38
- this._keyStorage = null
39
- this._bootstrap = opts._bootstrap || null
40
- this._namespace = opts.namespace || DEFAULT_NAMESPACE
41
- this._noCoreCache = root ? root._noCoreCache : new Xache({ maxSize: 65536 })
42
-
43
- this._root = root || this
44
- this._replicationStreams = root ? root._replicationStreams : []
45
- this._overwrite = opts.overwrite === true
46
- this._readonly = opts.writable === false
47
- this._attached = opts._attached || null
48
- this._notDownloadingLinger = opts.notDownloadingLinger
49
-
50
- this._sessions = new Set() // sessions for THIS namespace
51
- this._rootStoreSessions = new Set()
52
- this._locks = root ? root._locks : new Map()
12
+ class StreamTracker {
13
+ constructor () {
14
+ this.records = []
15
+ }
53
16
 
54
- this._findingPeersCount = 0
55
- this._findingPeers = []
56
- this._isCorestore = true
17
+ add (stream, isExternal) {
18
+ const record = { index: 0, stream, isExternal }
19
+ this.records.push(record)
20
+ return record
21
+ }
57
22
 
58
- if (this._namespace.byteLength !== 32) throw new Error('Namespace must be a 32-byte Buffer or Uint8Array')
59
- this.ready().catch(safetyCatch)
23
+ remove (record) {
24
+ const popped = this.records.pop()
25
+ if (popped === record) return
26
+ this.records[popped.index = record.index] = popped
60
27
  }
61
28
 
62
- static isCorestore (obj) {
63
- return !!(typeof obj === 'object' && obj && obj._isCorestore)
29
+ attachAll (core) {
30
+ for (let i = 0; i < this.records.length; i++) {
31
+ const record = this.records[i]
32
+ const muxer = record.stream.noiseStream.userData
33
+ if (!core.replicator.attached(muxer)) core.replicator.attachTo(muxer)
34
+ }
64
35
  }
65
36
 
66
- static from (storage, opts) {
67
- return this.isCorestore(storage) ? storage : new this(storage, opts)
37
+ destroy () {
38
+ // reverse is safer cause we delete mb
39
+ for (let i = this.records.length - 1; i >= 0; i--) {
40
+ const record = this.records[i]
41
+ if (!record.isExternal) record.stream.destroy()
42
+ }
68
43
  }
44
+ }
69
45
 
70
- // for now just release the lock...
71
- async suspend () {
72
- if (this._root !== this) return this._root.suspend()
46
+ class SessionTracker {
47
+ constructor () {
48
+ this.map = new Map()
49
+ }
73
50
 
74
- await this.ready()
51
+ get size () {
52
+ return this.map.size
53
+ }
75
54
 
76
- if (this._keyStorage !== null) {
77
- await new Promise((resolve, reject) => {
78
- this._keyStorage.suspend((err) => {
79
- if (err) return reject(err)
80
- resolve()
81
- })
82
- })
83
- }
55
+ get (id) {
56
+ const existing = this.map.get(id)
57
+ if (existing !== undefined) return existing
58
+ const fresh = []
59
+ this.map.set(id, fresh)
60
+ return fresh
84
61
  }
85
62
 
86
- async resume () {
87
- if (this._root !== this) return this._root.resume()
63
+ gc (id) {
64
+ this.map.delete(id)
65
+ }
88
66
 
89
- await this.ready()
67
+ list (id) {
68
+ return id ? (this.map.get(id) || []) : [...this]
69
+ }
90
70
 
91
- if (this._keyStorage !== null) {
92
- await new Promise((resolve, reject) => {
93
- this._keyStorage.open((err) => {
94
- if (err) return reject(err)
95
- resolve()
96
- })
97
- })
71
+ * [Symbol.iterator] () {
72
+ for (const sessions of this.map.values()) {
73
+ yield * sessions[Symbol.iterator]()
98
74
  }
99
75
  }
76
+ }
100
77
 
101
- findingPeers () {
102
- let done = false
103
- this._incFindingPeers()
78
+ class CoreTracker extends EventEmitter {
79
+ constructor () {
80
+ super()
81
+ this.map = new Map()
82
+ this.watching = []
83
+ }
104
84
 
105
- return () => {
106
- if (done) return
107
- done = true
108
- this._decFindingPeers()
109
- }
85
+ get size () {
86
+ return this.map.size
110
87
  }
111
88
 
112
- _emitCore (name, core) {
113
- this.emit(name, core)
114
- for (const session of this._root._rootStoreSessions) {
115
- if (session !== this) {
116
- session.emit(name, core)
117
- }
118
- }
119
- if (this !== this._root) this._root.emit(name, core)
89
+ watch (store) {
90
+ if (store.watchIndex !== -1) return
91
+ store.watchIndex = this.watching.push(store) - 1
120
92
  }
121
93
 
122
- _incFindingPeers () {
123
- if (++this._findingPeersCount !== 1) return
94
+ unwatch (store) {
95
+ if (store.watchIndex === -1) return
96
+ const head = this.watching.pop()
97
+ if (head !== store) this.watching[(head.watchIndex = store.watchIndex)] = head
98
+ store.watchIndex = -1
99
+ }
124
100
 
125
- for (const core of this._sessions) {
126
- this._findingPeers.push(core.findingPeers())
127
- }
101
+ get (id) {
102
+ // we allow you do call this from the outside, so support normal buffers also
103
+ if (b4a.isBuffer(id)) id = b4a.toString(id, 'hex')
104
+ return this.map.get(id) || null
128
105
  }
129
106
 
130
- _decFindingPeers () {
131
- if (--this._findingPeersCount !== 0) return
107
+ set (id, core) {
108
+ this.map.set(id, core)
109
+ this.emit('add', core) // TODO: will be removed
110
+ if (this.watching.length > 0) this._emit(core)
111
+ }
132
112
 
133
- while (this._findingPeers.length > 0) {
134
- this._findingPeers.pop()()
113
+ _emit (core) {
114
+ for (let i = this.watching.length - 1; i >= 0; i--) {
115
+ const store = this.watching[i]
116
+ for (const fn of store.watchers) fn(core)
135
117
  }
136
118
  }
137
119
 
138
- async _openNamespaceFromBootstrap () {
139
- const ns = await this._bootstrap.getUserData(USERDATA_NAMESPACE_KEY)
140
- if (ns) {
141
- this._namespace = ns
142
- }
120
+ delete (id, core) {
121
+ this.map.delete(id)
122
+ this.emit('remove', core) // TODO: will be removed
143
123
  }
144
124
 
145
- async _open () {
146
- if (this._root !== this) {
147
- await this._root.ready()
148
- if (!this.primaryKey) this.primaryKey = this._root.primaryKey
149
- if (this._bootstrap) await this._openNamespaceFromBootstrap()
150
- return
151
- }
125
+ [Symbol.iterator] () {
126
+ return this.map.values()
127
+ }
128
+ }
152
129
 
153
- this._keyStorage = this.storage(PRIMARY_KEY_FILE_NAME)
154
-
155
- this.primaryKey = await new Promise((resolve, reject) => {
156
- this._keyStorage.stat((err, st) => {
157
- if (err && err.code !== 'ENOENT') return reject(err)
158
- if (err || st.size < 32 || this._overwrite) {
159
- const key = this.primaryKey || crypto.randomBytes(32)
160
- return this._keyStorage.write(0, key, err => {
161
- if (err) return reject(err)
162
- return resolve(key)
163
- })
164
- }
165
- this._keyStorage.read(0, 32, (err, key) => {
166
- if (err) return reject(err)
167
- if (this.primaryKey) return resolve(this.primaryKey)
168
- return resolve(key)
169
- })
170
- })
171
- })
130
+ class Corestore extends ReadyResource {
131
+ constructor (storage, opts = {}) {
132
+ super()
172
133
 
173
- if (this._bootstrap) await this._openNamespaceFromBootstrap()
174
- }
134
+ this.root = opts.root || null
135
+ this.storage = this.root ? this.root.storage : Hypercore.defaultStorage(storage)
136
+ this.streamTracker = this.root ? this.root.streamTracker : new StreamTracker()
137
+ this.cores = this.root ? this.root.cores : new CoreTracker()
138
+ this.sessions = new SessionTracker()
139
+ this.globalCache = this.root ? this.root.globalCache : (opts.globalCache || null)
140
+ this.primaryKey = this.root ? this.root.primaryKey : (opts.primaryKey || null)
141
+ this.ns = opts.namespace || DEFAULT_NAMESPACE
175
142
 
176
- async _exists (discoveryKey) {
177
- const id = b4a.toString(discoveryKey, 'hex')
178
- const storageRoot = getStorageRoot(id)
143
+ this.watchers = null
144
+ this.watchIndex = -1
179
145
 
180
- const st = this.storage(storageRoot + '/oplog')
146
+ this.manifestVersion = 1 // just compat
181
147
 
182
- const exists = await new Promise((resolve) => st.stat((err, st) => resolve(!err && st.size > 0)))
183
- await new Promise(resolve => st.close(resolve))
148
+ this._ongcBound = this._ongc.bind(this)
184
149
 
185
- return exists
150
+ this.ready().catch(noop)
186
151
  }
187
152
 
188
- async _generateKeys (opts) {
189
- if (opts._discoveryKey) {
190
- return {
191
- manifest: null,
192
- keyPair: null,
193
- key: null,
194
- discoveryKey: opts._discoveryKey
195
- }
153
+ watch (fn) {
154
+ if (this.watchers === null) {
155
+ this.watchers = new Set()
156
+ this.cores.watch(this)
196
157
  }
197
158
 
198
- const keyPair = opts.name
199
- ? await this.createKeyPair(opts.name)
200
- : (opts.secretKey)
201
- ? { secretKey: opts.secretKey, publicKey: opts.publicKey }
202
- : null
159
+ this.watchers.add(fn)
160
+ }
203
161
 
204
- if (opts.manifest) {
205
- const key = Hypercore.key(opts.manifest)
162
+ unwatch (fn) {
163
+ if (this.watchers === null) return
206
164
 
207
- return {
208
- manifest: opts.manifest,
209
- keyPair,
210
- key,
211
- discoveryKey: crypto.discoveryKey(key)
212
- }
213
- }
165
+ this.watchers.delete(fn)
214
166
 
215
- if (opts.key) {
216
- return {
217
- manifest: null,
218
- keyPair,
219
- key: opts.key,
220
- discoveryKey: crypto.discoveryKey(opts.key)
221
- }
167
+ if (this.watchers.size === 0) {
168
+ this.watchers = null
169
+ this.cores.unwatch(this)
222
170
  }
171
+ }
223
172
 
224
- const publicKey = opts.publicKey || keyPair.publicKey
173
+ session (opts) {
174
+ const root = this.root || this
175
+ return new Corestore(null, { ...opts, root })
176
+ }
225
177
 
226
- if (opts.compat === false || (opts.compat !== true && !this.compat)) {
227
- let manifest = { version: this.manifestVersion, signers: [{ publicKey }] } // default manifest
228
- let key = Hypercore.key(manifest)
229
- let discoveryKey = crypto.discoveryKey(key)
178
+ namespace (name, opts) {
179
+ return this.session({ ...opts, namespace: generateNamespace(this.ns, name) })
180
+ }
230
181
 
231
- if (!(await this._exists(discoveryKey)) && manifest.version !== 0) {
232
- const manifestV0 = { version: 0, signers: [{ publicKey }] }
233
- const keyV0 = Hypercore.key(manifestV0)
234
- const discoveryKeyV0 = crypto.discoveryKey(keyV0)
182
+ _ongc (session) {
183
+ if (session.sessions.length === 0) this.sessions.gc(session.id)
184
+ }
235
185
 
236
- if (await this._exists(discoveryKeyV0)) {
237
- manifest = manifestV0
238
- key = keyV0
239
- discoveryKey = discoveryKeyV0
240
- }
241
- }
186
+ async _getOrSetSeed () {
187
+ const seed = await this.storage.getSeed()
188
+ if (seed !== null) return seed
189
+ return await this.storage.setSeed(this.primaryKey || crypto.randomBytes(32))
190
+ }
242
191
 
243
- return {
244
- manifest,
245
- keyPair,
246
- key,
247
- discoveryKey
248
- }
192
+ async _open () {
193
+ if (this.root !== null) {
194
+ if (this.root.opened === false) await this.root.ready()
195
+ this.primaryKey = this.root.primaryKey
196
+ return
249
197
  }
250
198
 
251
- return {
252
- manifest: null,
253
- keyPair,
254
- key: publicKey,
255
- discoveryKey: crypto.discoveryKey(publicKey)
199
+ const primaryKey = await this._getOrSetSeed()
200
+
201
+ if (this.primaryKey === null) {
202
+ this.primaryKey = primaryKey
203
+ return
256
204
  }
257
- }
258
205
 
259
- _getPrereadyUserData (core, key) {
260
- // Need to manually read the header values before the Hypercore is ready, hence the ugliness.
261
- for (const { key: savedKey, value } of core.core.header.userData) {
262
- if (key === savedKey) return value
206
+ if (!b4a.equals(primaryKey, this.primaryKey)) {
207
+ throw new Error('Another corestore is stored here')
263
208
  }
264
- return null
265
209
  }
266
210
 
267
- async _preready (core) {
268
- const name = this._getPrereadyUserData(core, USERDATA_NAME_KEY)
269
- if (!name) return
211
+ async _close () {
212
+ const sessions = []
213
+ const hanging = [...this.sessions]
214
+ for (const sess of hanging) sessions.push(sess.close())
270
215
 
271
- const namespace = this._getPrereadyUserData(core, USERDATA_NAMESPACE_KEY)
272
- const keyPair = await this.createKeyPair(b4a.toString(name), namespace)
273
- core.setKeyPair(keyPair)
274
- }
216
+ if (this.watchers !== null) this.cores.unwatch(this)
275
217
 
276
- _getLock (id) {
277
- let rw = this._locks.get(id)
218
+ await Promise.all(sessions)
219
+ if (this.root !== null) return
278
220
 
279
- if (!rw) {
280
- rw = new RW()
281
- this._locks.set(id, rw)
282
- }
283
-
284
- return rw
221
+ const cores = []
222
+ for (const core of this.cores) cores.push(core.close())
223
+ await Promise.all(cores)
224
+ await this.storage.close()
285
225
  }
286
226
 
287
- async _preload (id, keys, opts) {
288
- const { manifest, keyPair, key } = keys
227
+ async _attachMaybe (muxer, discoveryKey) {
228
+ if (this.opened === false) await this.ready()
229
+ if (this.cores.get(toHex(discoveryKey)) === null && !(await this.storage.has(discoveryKey))) return
289
230
 
290
- while (this.cores.has(id)) {
291
- const existing = this.cores.get(id)
292
- if (existing.opened && !existing.closing) return { from: existing, keyPair, manifest, cache: !!opts.cache }
293
- if (existing.closing) {
294
- await existing.close()
295
- } else {
296
- await existing.ready().catch(safetyCatch)
297
- }
298
- }
231
+ const core = this._getCore(discoveryKey, { createIfMissing: false })
299
232
 
300
- const hasKeyPair = !!(keyPair && keyPair.secretKey)
301
- const userData = {}
302
- if (opts.name) {
303
- userData[USERDATA_NAME_KEY] = b4a.from(opts.name)
304
- userData[USERDATA_NAMESPACE_KEY] = this._namespace
305
- }
233
+ if (!core) return
234
+ if (!core.opened) await core.ready()
306
235
 
307
- // No more async ticks allowed after this point -- necessary for caching
308
-
309
- const storageRoot = getStorageRoot(id)
310
- const core = new Hypercore(p => this.storage(storageRoot + '/' + p), {
311
- _preready: this._preready.bind(this),
312
- notDownloadingLinger: this._notDownloadingLinger,
313
- inflightRange: this.inflightRange,
314
- autoClose: true,
315
- active: false,
316
- encryptionKey: opts.encryptionKey || null,
317
- isBlockKey: !!opts.isBlockKey,
318
- userData,
319
- manifest,
320
- key,
321
- compat: opts.compat,
322
- cache: opts.cache,
323
- globalCache: this.globalCache,
324
- createIfMissing: opts.createIfMissing === false ? false : !opts._discoveryKey,
325
- keyPair: hasKeyPair ? keyPair : null
326
- })
327
-
328
- if (this._root.closing) {
329
- try {
330
- await core.close()
331
- } catch {}
332
- throw new Error('The corestore is closed')
236
+ if (!core.replicator.attached(muxer)) {
237
+ core.replicator.attachTo(muxer)
333
238
  }
239
+ }
334
240
 
335
- this.cores.set(id, core)
336
- this._noCoreCache.delete(id)
337
- core.ready().then(() => {
338
- if (core.closing) return // extra safety here as ready is a tick after open
339
- if (hasKeyPair) core.setKeyPair(keyPair)
340
- this._emitCore('core-open', core)
341
- if (this.passive) return
342
-
343
- const ondownloading = () => {
344
- for (const { stream } of this._replicationStreams) {
345
- core.replicate(stream, { session: true })
346
- }
241
+ replicate (isInitiator, opts) {
242
+ const isExternal = isStream(isInitiator)
243
+ const stream = Hypercore.createProtocolStream(isInitiator, {
244
+ ...opts,
245
+ ondiscoverykey: discoveryKey => {
246
+ if (this.closing) return
247
+ const muxer = stream.noiseStream.userData
248
+ return this._attachMaybe(muxer, discoveryKey)
347
249
  }
348
- // when the replicator says we are downloading, answer the call
349
- core.replicator.ondownloading = ondownloading
350
- // trigger once if the condition is already true
351
- if (core.replicator.downloading) ondownloading()
352
- }, () => {
353
- this._noCoreCache.set(id, true)
354
- this.cores.delete(id)
355
- })
356
- core.once('close', () => {
357
- this._emitCore('core-close', core)
358
- this.cores.delete(id)
359
- })
360
- core.on('conflict', (len, fork, proof) => {
361
- this.emit('conflict', core, len, fork, proof)
362
250
  })
363
251
 
364
- return { from: core, keyPair, manifest, cache: !!opts.cache }
365
- }
252
+ if (this.cores.size > 0) {
253
+ const muxer = stream.noiseStream.userData
254
+ const uncork = muxer.uncork.bind(muxer)
255
+ muxer.cork()
366
256
 
367
- async createKeyPair (name, namespace = this._namespace) {
368
- if (!this.opened) await this.ready()
257
+ for (const core of this.cores) {
258
+ if (!core.replicator.downloading || core.replicator.attached(muxer) || !core.opened) continue
259
+ core.replicator.attachTo(muxer)
260
+ }
369
261
 
370
- const keyPair = {
371
- publicKey: b4a.allocUnsafeSlow(sodium.crypto_sign_PUBLICKEYBYTES),
372
- secretKey: b4a.alloc(sodium.crypto_sign_SECRETKEYBYTES)
262
+ stream.noiseStream.opened.then(uncork)
373
263
  }
374
264
 
375
- const seed = deriveSeed(this.primaryKey, namespace, name)
376
- sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
377
-
378
- return keyPair
265
+ const record = this.streamTracker.add(stream, isExternal)
266
+ stream.once('close', () => this.streamTracker.remove(record))
267
+ return stream
379
268
  }
380
269
 
381
- get (opts = {}) {
382
- if (this.closing || this._root.closing) throw new Error('The corestore is closed')
383
- opts = validateGetOptions(opts)
384
-
385
- if (opts.cache !== false) {
386
- opts.cache = opts.cache === true || (this.cache && !opts.cache) ? defaultCache() : opts.cache
387
- }
388
- if (this._readonly && opts.writable !== false) {
389
- opts.writable = false
270
+ get (opts) {
271
+ if (b4a.isBuffer(opts) || typeof opts === 'string') opts = { key: opts }
272
+ if (!opts) opts = {}
273
+
274
+ const conf = {
275
+ preload: null,
276
+ parent: opts.parent || null,
277
+ sessions: null,
278
+ ongc: null,
279
+ core: null,
280
+ active: opts.active !== false,
281
+ encryptionKey: opts.encryptionKey || null,
282
+ isBlockKey: !!opts.isBlockKey,
283
+ valueEncoding: opts.valueEncoding || null,
284
+ exclusive: !!opts.exclusive,
285
+ manifest: opts.manifest || null,
286
+ keyPair: opts.keyPair || null,
287
+ onwait: opts.onwait || null,
288
+ wait: opts.wait !== false,
289
+ timeout: opts.timeout || 0,
290
+ draft: !!opts.draft,
291
+ writable: opts.writable
390
292
  }
391
293
 
392
- let rw = null
393
- let id = null
394
-
395
- const core = new Hypercore(null, {
396
- ...opts,
397
- globalCache: this.globalCache,
398
- name: null,
399
- preload: async () => {
400
- if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
401
- if (!this.opened) await this.ready()
402
-
403
- const keys = await this._generateKeys(opts)
404
-
405
- id = b4a.toString(keys.discoveryKey, 'hex')
406
- rw = (opts.exclusive && opts.writable !== false) ? this._getLock(id) : null
407
-
408
- if (rw) await rw.write.lock()
409
- return await this._preload(id, keys, opts)
410
- }
411
- })
412
-
413
- this._sessions.add(core)
414
- if (this._findingPeersCount > 0) {
415
- this._findingPeers.push(core.findingPeers())
294
+ // name requires us to rt to storage + ready, so needs preload
295
+ // same goes if user has defined async preload obvs
296
+ if (opts.name || opts.preload) {
297
+ conf.preload = this._preload(opts)
298
+ return new Hypercore(null, null, conf)
416
299
  }
417
300
 
418
- const gc = () => {
419
- // technically better to also clear _findingPeers if we added it,
420
- // but the lifecycle for those are pretty short so prob not worth the complexity
421
- // as _decFindingPeers clear them all.
422
- this._sessions.delete(core)
301
+ // if not not we can sync create it, which just is easier for the
302
+ // upstream user in terms of guarantees (key is there etc etc)
303
+ const core = this._getCore(null, opts)
423
304
 
424
- if (!rw) return
425
- rw.write.unlock()
426
- if (!rw.write.locked) this._locks.delete(id)
427
- }
305
+ conf.core = core
306
+ conf.sessions = this.sessions.get(core.id)
307
+ conf.ongc = this._ongcBound
428
308
 
429
- core.ready().catch(gc)
430
- core.once('close', gc)
309
+ return new Hypercore(null, null, conf)
310
+ }
431
311
 
432
- return core
312
+ async createKeyPair (name, ns = this.ns) {
313
+ if (this.opened === false) await this.ready()
314
+ return createKeyPair(this.primaryKey, ns, name)
433
315
  }
434
316
 
435
- replicate (isInitiator, opts) {
436
- const isExternal = isStream(isInitiator) || !!(opts && opts.stream)
437
- const stream = Hypercore.createProtocolStream(isInitiator, {
438
- ...opts,
439
- ondiscoverykey: async discoveryKey => {
440
- if (this.closing) return
317
+ async _preload (opts) {
318
+ if (opts.preload) opts = { ...opts, ...(await opts.preload) }
319
+ if (this.opened === false) await this.ready()
441
320
 
442
- const id = b4a.toString(discoveryKey, 'hex')
443
- if (this._noCoreCache.get(id)) return
321
+ const discoveryKey = opts.name ? await this.storage.getAlias({ name: opts.name, namespace: this.ns }) : null
322
+ const core = this._getCore(discoveryKey, opts)
444
323
 
445
- const core = this.get({ _discoveryKey: discoveryKey, active: false })
324
+ return {
325
+ parent: opts.parent || null,
326
+ core,
327
+ sessions: this.sessions.get(core.id),
328
+ ongc: this._ongcBound,
329
+ encryptionKey: opts.encryptionKey || null,
330
+ isBlockKey: !!opts.isBlockKey
331
+ }
332
+ }
446
333
 
447
- try {
448
- await core.ready()
449
- } catch {
450
- return
451
- }
334
+ _auth (discoveryKey, opts) {
335
+ const result = {
336
+ keyPair: null,
337
+ key: null,
338
+ discoveryKey,
339
+ manifest: null
340
+ }
452
341
 
453
- // remote is asking for the core so we HAVE to answer even if not downloading
454
- if (!core.closing) core.replicate(stream, { session: true })
455
- await core.close()
456
- }
457
- })
342
+ if (opts.name) {
343
+ result.keyPair = createKeyPair(this.primaryKey, this.ns, opts.name)
344
+ } else if (opts.keyPair) {
345
+ result.keyPair = opts.keyPair
346
+ }
458
347
 
459
- if (!this.passive) {
460
- const muxer = stream.noiseStream.userData
461
- muxer.cork()
462
- for (const core of this.cores.values()) {
463
- // If the core is not opened, it will be replicated in preload.
464
- if (!core.opened || core.closing || !core.replicator.downloading) continue
465
- core.replicate(stream, { session: true })
466
- }
467
- stream.noiseStream.opened.then(() => muxer.uncork())
348
+ if (opts.manifest) {
349
+ result.manifest = opts.manifest
350
+ } else if (result.keyPair && !result.discoveryKey) {
351
+ result.manifest = { version: 1, signers: [{ publicKey: result.keyPair.publicKey }] }
468
352
  }
469
353
 
470
- const streamRecord = { stream, isExternal }
471
- this._replicationStreams.push(streamRecord)
354
+ if (opts.key) result.key = ID.decode(opts.key)
355
+ else if (result.manifest) result.key = Hypercore.key(result.manifest)
472
356
 
473
- stream.once('close', () => {
474
- this._replicationStreams.splice(this._replicationStreams.indexOf(streamRecord), 1)
475
- })
357
+ if (result.discoveryKey) return result
476
358
 
477
- return stream
359
+ if (opts.discoveryKey) result.discoveryKey = ID.decode(opts.discoveryKey)
360
+ else if (result.key) result.discoveryKey = crypto.discoveryKey(result.key)
361
+ else throw new Error('Could not derive discovery from input')
362
+
363
+ return result
478
364
  }
479
365
 
480
- namespace (name, opts) {
481
- if (name instanceof Hypercore) {
482
- return this.session({ ...opts, _bootstrap: name })
483
- }
484
- return this.session({ ...opts, namespace: generateNamespace(this._namespace, name) })
366
+ _hasCore (discoveryKey) {
367
+ return this.cores.get(toHex(discoveryKey)) !== null
485
368
  }
486
369
 
487
- session (opts) {
488
- const session = new Corestore(this.storage, {
489
- namespace: this._namespace,
490
- cache: this.cache,
491
- writable: !this._readonly,
492
- _attached: opts && opts.detach === false ? this : null,
493
- _root: this._root,
494
- inflightRange: this.inflightRange,
495
- globalCache: this.globalCache,
496
- ...opts
370
+ _getCore (discoveryKey, opts) {
371
+ const auth = this._auth(discoveryKey, opts)
372
+
373
+ const id = toHex(auth.discoveryKey)
374
+ const existing = this.cores.get(id)
375
+ if (existing) return existing
376
+
377
+ const core = Hypercore.createCore(this.storage, {
378
+ eagerUpgrade: true,
379
+ notDownloadingLinger: opts.notDownloadingLinger,
380
+ allowFork: opts.allowFork !== false,
381
+ inflightRange: opts.inflightRange,
382
+ compat: false, // no compat for now :)
383
+ force: opts.force,
384
+ createIfMissing: opts.createIfMissing,
385
+ discoveryKey: auth.discoveryKey,
386
+ overwrite: opts.overwrite,
387
+ key: auth.key,
388
+ keyPair: auth.keyPair,
389
+ legacy: opts.legacy,
390
+ manifest: auth.manifest,
391
+ globalCache: opts.globalCache || this.globalCache || null,
392
+ alias: opts.name ? { name: opts.name, namespace: this.ns } : null
497
393
  })
498
- if (this === this._root) this._rootStoreSessions.add(session)
499
- return session
500
- }
501
394
 
502
- _closeNamespace () {
503
- const closePromises = []
504
- for (const session of this._sessions) {
505
- closePromises.push(session.close())
395
+ core.onidle = () => {
396
+ core.destroy()
397
+ this.cores.delete(id, core)
506
398
  }
507
- return Promise.allSettled(closePromises)
508
- }
509
399
 
510
- async _closePrimaryNamespace () {
511
- const closePromises = []
512
- // At this point, the primary namespace is closing.
513
- for (const { stream, isExternal } of this._replicationStreams) {
514
- // Only close streams that were created by the Corestore
515
- if (!isExternal) stream.destroy()
400
+ core.replicator.ondownloading = () => {
401
+ this.streamTracker.attachAll(core)
516
402
  }
517
- for (const core of this.cores.values()) {
518
- closePromises.push(forceClose(core))
519
- }
520
- await Promise.allSettled(closePromises)
521
- await new Promise((resolve, reject) => {
522
- this._keyStorage.close(err => {
523
- if (err) return reject(err)
524
- return resolve(null)
525
- })
526
- })
527
- }
528
-
529
- async _close () {
530
- this._root._rootStoreSessions.delete(this)
531
403
 
532
- await this._closeNamespace()
533
-
534
- if (this._root === this) {
535
- await this._closePrimaryNamespace()
536
- } else if (this._attached) {
537
- await this._attached.close()
538
- }
404
+ this.cores.set(id, core)
405
+ return core
539
406
  }
540
407
  }
541
408
 
542
- function validateGetOptions (opts) {
543
- const key = (b4a.isBuffer(opts) || typeof opts === 'string') ? hypercoreId.decode(opts) : null
544
- if (key) return { key }
545
-
546
- if (opts.key) {
547
- opts.key = hypercoreId.decode(opts.key)
548
- }
549
- if (opts.keyPair) {
550
- opts.publicKey = opts.keyPair.publicKey
551
- opts.secretKey = opts.keyPair.secretKey
552
- }
409
+ module.exports = Corestore
553
410
 
554
- if (opts.name && typeof opts.name !== 'string') throw new Error('name option must be a String')
555
- if (opts.name && opts.secretKey) throw new Error('Cannot provide both a name and a secret key')
556
- if (opts.publicKey && !b4a.isBuffer(opts.publicKey)) throw new Error('publicKey option must be a Buffer or Uint8Array')
557
- if (opts.secretKey && !b4a.isBuffer(opts.secretKey)) throw new Error('secretKey option must be a Buffer or Uint8Array')
558
- if (!opts._discoveryKey && (!opts.name && !opts.publicKey && !opts.manifest && !opts.key && !opts.preload)) throw new Error('Must provide either a name or a publicKey')
559
- return opts
411
+ function isStream (s) {
412
+ return typeof s === 'object' && s && typeof s.pipe === 'function'
560
413
  }
561
414
 
562
415
  function generateNamespace (namespace, name) {
@@ -573,19 +426,19 @@ function deriveSeed (primaryKey, namespace, name) {
573
426
  return out
574
427
  }
575
428
 
576
- function defaultCache () {
577
- return new Xache({ maxSize: 65536, maxAge: 0 })
578
- }
579
-
580
- function isStream (s) {
581
- return typeof s === 'object' && s && typeof s.pipe === 'function'
429
+ function createKeyPair (primaryKey, namespace, name) {
430
+ const seed = deriveSeed(primaryKey, namespace, name)
431
+ const buf = b4a.alloc(sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES)
432
+ const keyPair = {
433
+ publicKey: buf.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES),
434
+ secretKey: buf.subarray(sodium.crypto_sign_PUBLICKEYBYTES)
435
+ }
436
+ sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
437
+ return keyPair
582
438
  }
583
439
 
584
- async function forceClose (core) {
585
- await core.ready()
586
- return Promise.all(core.sessions.map(s => s.close()))
587
- }
440
+ function noop () {}
588
441
 
589
- function getStorageRoot (id) {
590
- return CORES_DIR + '/' + id.slice(0, 2) + '/' + id.slice(2, 4) + '/' + id
442
+ function toHex (discoveryKey) {
443
+ return b4a.toString(discoveryKey, 'hex')
591
444
  }
package/package.json CHANGED
@@ -1,44 +1,43 @@
1
1
  {
2
2
  "name": "corestore",
3
- "version": "6.18.4",
3
+ "version": "7.0.1",
4
4
  "description": "A Hypercore factory that simplifies managing collections of cores.",
5
5
  "main": "index.js",
6
+ "files": [
7
+ "index.js"
8
+ ],
9
+ "dependencies": {
10
+ "b4a": "^1.6.7",
11
+ "bare-events": "^2.5.0",
12
+ "hypercore": "^11.0.0",
13
+ "hypercore-crypto": "^3.4.2",
14
+ "hypercore-id-encoding": "^1.3.0",
15
+ "ready-resource": "^1.1.1",
16
+ "sodium-universal": "^4.0.1"
17
+ },
18
+ "devDependencies": {
19
+ "brittle": "^3.7.0",
20
+ "rache": "^1.0.0",
21
+ "standard": "^17.1.2",
22
+ "test-tmp": "^1.3.0"
23
+ },
6
24
  "scripts": {
7
25
  "test": "standard && brittle test/*.js"
8
26
  },
27
+ "imports": {
28
+ "events": {
29
+ "bare": "bare-events",
30
+ "default": "events"
31
+ }
32
+ },
9
33
  "repository": {
10
34
  "type": "git",
11
- "url": "git+https://github.com/holepunchto/corestore.git"
35
+ "url": "https://github.com/holepunchto/corestore2.git"
12
36
  },
13
- "keywords": [
14
- "corestore"
15
- ],
16
- "author": "Andrew Osheroff <andrewosh@gmail.com>",
37
+ "author": "Holepunch Inc",
17
38
  "license": "MIT",
18
39
  "bugs": {
19
- "url": "https://github.com/holepunchto/corestore/issues"
40
+ "url": "https://github.com/holepunchto/corestore2/issues"
20
41
  },
21
- "homepage": "https://github.com/holepunchto/corestore#readme",
22
- "files": [
23
- "index.js",
24
- "lib/**.js"
25
- ],
26
- "devDependencies": {
27
- "brittle": "^3.2.2",
28
- "rache": "^1.0.0",
29
- "random-access-memory": "^6.2.0",
30
- "standard": "^17.1.0",
31
- "test-tmp": "^1.0.2"
32
- },
33
- "dependencies": {
34
- "b4a": "^1.6.4",
35
- "hypercore": "^10.37.10",
36
- "hypercore-crypto": "^3.4.0",
37
- "hypercore-id-encoding": "^1.2.0",
38
- "read-write-mutexify": "^2.1.0",
39
- "ready-resource": "^1.0.0",
40
- "safety-catch": "^1.0.1",
41
- "sodium-universal": "^4.0.0",
42
- "xache": "^1.1.0"
43
- }
42
+ "homepage": "https://github.com/holepunchto/corestore2"
44
43
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2023 Holepunch Inc
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.