From fa27db996fbd6f7a5528fbfaa0ec8a9e72a800e1 Mon Sep 17 00:00:00 2001 From: HDegroote <75906619+HDegroote@users.noreply.github.com> Date: Tue, 3 Jun 2025 14:19:49 +0200 Subject: [PATCH 1/3] Support length opt for update --- README.md | 3 ++- index.js | 5 ++++- test/replicate.js | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 37c9395fd..fab1aa404 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,8 @@ console.log('core was updated?', updated, 'length is', core.length) ``` js { - wait: false + wait: false, + length: 0 // minimum length (will hang until core.length is greater than or equal) } ``` diff --git a/index.js b/index.js index b5da832ad..97864f629 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -const { EventEmitter } = require('events') +const { EventEmitter, once } = require('events') const isOptions = require('is-options') const crypto = require('hypercore-crypto') const CoreStorage = require('hypercore-storage') @@ -668,6 +668,7 @@ class Hypercore extends EventEmitter { if (this.writable && (!opts || opts.force !== true)) return false + const minLength = opts?.length || 0 const remoteWait = this._shouldWait(opts, this.core.replicator.findingPeers > 0) let upgraded = false @@ -688,6 +689,8 @@ class Hypercore extends EventEmitter { } } + while (this.length < minLength) await once(this, 'append') + if (!upgraded) return false return true } diff --git a/test/replicate.js b/test/replicate.js index 298bc0f23..c73de705d 100644 --- a/test/replicate.js +++ b/test/replicate.js @@ -373,6 +373,29 @@ test('update with zero length', async function (t) { t.is(b.length, 0) }) +test('update with min length', async function (t) { + const a = await create(t) + const b = await create(t, a.key) + + replicate(a, b, t) + + let updateResolved = false + b.update({ length: 3 }) + .then(() => { + updateResolved = true + t.is(b.length, 3, 'update resolved at length 3') + }) + .catch((e) => { + console.error(e) + t.fail('unexpected error') + }) + + await a.append('block0') + await a.append('block1') + t.is(updateResolved, false, 'sanity check') + await a.append('block2') +}) + test('basic multiplexing', async function (t) { const a1 = await create(t) const a2 = await create(t) From 585b2d14b94bf32e3e72eb0ffd0b5f53772300ac Mon Sep 17 00:00:00 2001 From: HDegroote <75906619+HDegroote@users.noreply.github.com> Date: Tue, 3 Jun 2025 14:31:42 +0200 Subject: [PATCH 2/3] Fix test --- test/replicate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/replicate.js b/test/replicate.js index c73de705d..8b3d445aa 100644 --- a/test/replicate.js +++ b/test/replicate.js @@ -374,6 +374,7 @@ test('update with zero length', async function (t) { }) test('update with min length', async function (t) { + t.plan(2) const a = await create(t) const b = await create(t, a.key) From 89ee8096044691529f77b7862e6791c60255543d Mon Sep 17 00:00:00 2001 From: HDegroote <75906619+HDegroote@users.noreply.github.com> Date: Tue, 3 Jun 2025 15:04:26 +0200 Subject: [PATCH 3/3] Clean solution --- index.js | 22 ++++++++++++++++++++-- package.json | 1 + test/replicate.js | 20 ++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 97864f629..a26de0721 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -const { EventEmitter, once } = require('events') +const { EventEmitter } = require('events') const isOptions = require('is-options') const crypto = require('hypercore-crypto') const CoreStorage = require('hypercore-storage') @@ -9,6 +9,7 @@ const Protomux = require('protomux') const id = require('hypercore-id-encoding') const safetyCatch = require('safety-catch') const unslab = require('unslab') +const rrp = require('resolve-reject-promise') const Core = require('./lib/core') const Info = require('./lib/info') @@ -689,7 +690,24 @@ class Hypercore extends EventEmitter { } } - while (this.length < minLength) await once(this, 'append') + if (this.length < minLength) { + const { promise, resolve, reject } = rrp() + const onclose = () => { + reject(SESSION_CLOSED('Hypercore closed while waiting for length update')) + } + const onappend = () => { + if (this.length >= minLength) resolve() + } + this.on('close', onclose) + this.on('append', onappend) + try { + await promise + } finally { + this.off('close', onclose) + this.off('append', onappend) + } + upgraded = true + } if (!upgraded) return false return true diff --git a/package.json b/package.json index f4a82fe6c..bb0f598d1 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "protomux": "^3.5.0", "quickbit-universal": "^2.2.0", "random-array-iterator": "^1.0.0", + "resolve-reject-promise": "^1.1.0", "safety-catch": "^1.0.1", "sodium-universal": "^5.0.1", "streamx": "^2.12.4", diff --git a/test/replicate.js b/test/replicate.js index 8b3d445aa..66710ac23 100644 --- a/test/replicate.js +++ b/test/replicate.js @@ -397,6 +397,26 @@ test('update with min length', async function (t) { await a.append('block2') }) +test('update with min length throws if the core closes before reaching the length', async function (t) { + t.plan(1) + const a = await create(t) + const b = await create(t, a.key) + + replicate(a, b, t) + + b.update({ length: 3 }) + .then(() => { + t.fail('should not trigger') + }) + .catch((e) => { + t.is(e.code, 'SESSION_CLOSED') + }) + + await a.append('block0') + await a.append('block1') + await b.close() +}) + test('basic multiplexing', async function (t) { const a1 = await create(t) const a2 = await create(t)