corestore 6.0.2 → 6.0.5

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 (3) hide show
  1. package/index.js +32 -13
  2. package/package.json +3 -3
  3. package/test/all.js +78 -1
package/index.js CHANGED
@@ -26,6 +26,8 @@ module.exports = class Corestore extends EventEmitter {
26
26
  this._keyStorage = null
27
27
  this._primaryKey = opts.primaryKey
28
28
  this._namespace = opts.namespace || DEFAULT_NAMESPACE
29
+
30
+ this._root = opts._root || this
29
31
  this._replicationStreams = opts._streams || []
30
32
  this._overwrite = opts.overwrite === true
31
33
 
@@ -37,6 +39,7 @@ module.exports = class Corestore extends EventEmitter {
37
39
 
38
40
  if (this._namespace.byteLength !== 32) throw new Error('Namespace must be a 32-byte Buffer or Uint8Array')
39
41
 
42
+ this._closing = null
40
43
  this._opening = opts._opening ? opts._opening.then(() => this._open()) : this._open()
41
44
  this._opening.catch(safetyCatch)
42
45
  this.ready = () => this._opening
@@ -178,7 +181,7 @@ module.exports = class Corestore extends EventEmitter {
178
181
  userData,
179
182
  auth,
180
183
  cache: opts.cache,
181
- createIfMissing: !opts._discoveryKey,
184
+ createIfMissing: opts.createIfMissing === false ? false : !opts._discoveryKey,
182
185
  keyPair: keyPair && keyPair.publicKey
183
186
  ? {
184
187
  publicKey: keyPair.publicKey,
@@ -187,6 +190,7 @@ module.exports = class Corestore extends EventEmitter {
187
190
  : null
188
191
  })
189
192
 
193
+ if (this._root._closing) throw new Error('The corestore is closed')
190
194
  this.cores.set(id, core)
191
195
  core.ready().then(() => {
192
196
  for (const { stream } of this._replicationStreams) {
@@ -226,6 +230,7 @@ module.exports = class Corestore extends EventEmitter {
226
230
  }
227
231
 
228
232
  get (opts = {}) {
233
+ if (this._root._closing) throw new Error('The corestore is closed')
229
234
  opts = validateGetOptions(opts)
230
235
 
231
236
  if (opts.cache !== false) {
@@ -288,6 +293,7 @@ module.exports = class Corestore extends EventEmitter {
288
293
  primaryKey: this._opening.then(() => this.primaryKey),
289
294
  namespace: generateNamespace(this._namespace, name),
290
295
  cache: this.cache,
296
+ _root: this._root,
291
297
  _opening: this._opening,
292
298
  _cores: this.cores,
293
299
  _streams: this._replicationStreams,
@@ -295,23 +301,25 @@ module.exports = class Corestore extends EventEmitter {
295
301
  })
296
302
  }
297
303
 
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
- }
304
+ _closeNamespace () {
305
305
  const closePromises = []
306
- for (const core of this.cores.values()) {
307
- closePromises.push(core.close())
306
+ for (const session of this._sessions) {
307
+ closePromises.push(session.close())
308
308
  }
309
- await Promise.allSettled(closePromises)
309
+ return Promise.allSettled(closePromises)
310
+ }
311
+
312
+ async _closePrimaryNamespace () {
313
+ const closePromises = []
314
+ // At this point, the primary namespace is closing.
310
315
  for (const { stream, isExternal } of this._replicationStreams) {
311
316
  // Only close streams that were created by the Corestore
312
317
  if (!isExternal) stream.destroy()
313
318
  }
314
- if (!this._keyStorage) return
319
+ for (const core of this.cores.values()) {
320
+ closePromises.push(forceClose(core))
321
+ }
322
+ await Promise.allSettled(closePromises)
315
323
  await new Promise((resolve, reject) => {
316
324
  this._keyStorage.close(err => {
317
325
  if (err) return reject(err)
@@ -320,10 +328,17 @@ module.exports = class Corestore extends EventEmitter {
320
328
  })
321
329
  }
322
330
 
331
+ async _close () {
332
+ await this._opening
333
+ await this._closeNamespace()
334
+ if (this._root === this) {
335
+ await this._closePrimaryNamespace()
336
+ }
337
+ }
338
+
323
339
  close () {
324
340
  if (this._closing) return this._closing
325
341
  this._closing = this._close()
326
- this._closing.catch(safetyCatch)
327
342
  return this._closing
328
343
  }
329
344
  }
@@ -371,3 +386,7 @@ function defaultCache () {
371
386
  function isStream (s) {
372
387
  return typeof s === 'object' && s && typeof s.pipe === 'function'
373
388
  }
389
+
390
+ function forceClose (core) {
391
+ return Promise.all(core.sessions.map(s => s.close()))
392
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "corestore",
3
- "version": "6.0.2",
3
+ "version": "6.0.5",
4
4
  "description": "A Hypercore factory that simplifies managing collections of cores.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -21,12 +21,12 @@
21
21
  "homepage": "https://github.com/hypercore-protocol/corestore#readme",
22
22
  "devDependencies": {
23
23
  "brittle": "^3.0.0",
24
- "random-access-memory": "^4.0.0",
24
+ "random-access-memory": "^5.0.1",
25
25
  "standardx": "^7.0.0"
26
26
  },
27
27
  "dependencies": {
28
28
  "b4a": "^1.3.1",
29
- "hypercore": "v10.0.0",
29
+ "hypercore": "^10.2.0",
30
30
  "hypercore-crypto": "^3.2.1",
31
31
  "safety-catch": "^1.0.1",
32
32
  "sodium-universal": "^3.0.4",
package/test/all.js CHANGED
@@ -40,6 +40,24 @@ test('basic get with custom keypair', async function (t) {
40
40
  t.ok(core2.writable)
41
41
  })
42
42
 
43
+ test('get with createIfMissing=false throws if new core', async function (t) {
44
+ const store = new Corestore(ram)
45
+ const core1a = store.get({ name: 'core-1', createIfMissing: false })
46
+
47
+ await t.exception(core1a.ready(), 'No Hypercore is stored here')
48
+ })
49
+
50
+ test('get with createIfMissing=false works if no new core', async function (t) {
51
+ const store = new Corestore(ram)
52
+ const name = 'core-1'
53
+
54
+ const core1 = store.get({ name })
55
+ await core1.ready()
56
+
57
+ const core1Too = store.get({ name, createIfMissing: false })
58
+ await t.execution(core1Too.ready())
59
+ })
60
+
43
61
  test('basic namespaces', async function (t) {
44
62
  const store = new Corestore(ram)
45
63
  const ns1 = store.namespace('ns1')
@@ -205,13 +223,17 @@ test('storage locking', async function (t) {
205
223
  }
206
224
  })
207
225
 
208
- test('closing a namespace does not close cores', async function (t) {
226
+ test('cores close when their last referencing namespace closes', async function (t) {
209
227
  const store = new Corestore(ram)
210
228
  const ns1 = store.namespace('ns1')
211
229
  const core1 = ns1.get({ name: 'core-1' })
212
230
  const core2 = ns1.get({ name: 'core-2' })
213
231
  await Promise.all([core1.ready(), core2.ready()])
214
232
 
233
+ const core3 = store.get(core1.key)
234
+ const core4 = store.get(core2.key)
235
+ await Promise.all([core3.ready(), core4.ready()])
236
+
215
237
  await ns1.close()
216
238
 
217
239
  t.is(store.cores.size, 2)
@@ -323,6 +345,61 @@ test('core caching after reopen regression', async function (t) {
323
345
  t.pass('did not infinite loop')
324
346
  })
325
347
 
348
+ test('closing a namespace does not close the root corestore', async function (t) {
349
+ const store = new Corestore(ram)
350
+ const core1 = store.get({ name: 'core-1' })
351
+ await core1.ready()
352
+
353
+ const ns = store.namespace('test-namespace')
354
+ const core2 = ns.get(core1.key)
355
+ await core2.ready()
356
+
357
+ await ns.close()
358
+ t.is(core1.closed, false)
359
+ t.is(core2.closed, true)
360
+ })
361
+
362
+ test('open a new session concurrently with a close should throw', async function (t) {
363
+ const store = new Corestore(ram)
364
+ const ns = store.namespace('test-namespace')
365
+
366
+ const core1 = ns.get({ name: 'core-1' })
367
+ store.close()
368
+
369
+ try {
370
+ await core1.ready()
371
+ t.fail('core1 should not have opened')
372
+ } catch {
373
+ t.pass('core1 did not open after corestore close')
374
+ }
375
+ })
376
+
377
+ test('closing the root corestore closes all sessions', async function (t) {
378
+ const store = new Corestore(ram)
379
+ const ns = store.namespace('test-namespace')
380
+
381
+ const core1 = store.get({ name: 'core-1' })
382
+ const core2 = store.get({ name: 'core-2' })
383
+ await Promise.all([core1.ready(), core2.ready()])
384
+
385
+ const core3 = ns.get(core1.key)
386
+ const core4 = ns.get(core2.key)
387
+ await Promise.all([core3.ready(), core4.ready()])
388
+
389
+ await store.close()
390
+
391
+ t.is(core1.closed, true)
392
+ t.is(core2.closed, true)
393
+
394
+ try {
395
+ const core5 = ns.get({ name: 'core-3' })
396
+ await core5.ready()
397
+ t.fail('core5 should not have opened after corestore close')
398
+ } catch (err) {
399
+ t.pass('core5 did not open after corestore close')
400
+ }
401
+ })
402
+
326
403
  function tmpdir () {
327
404
  return path.join(os.tmpdir(), 'corestore-' + Math.random().toString(16).slice(2))
328
405
  }