From f4fbad9a0db55d0c8299d954802bdc23ca34739f Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Thu, 22 Oct 2026 13:58:51 +0100 Subject: [PATCH] always store unmapped key in cores table --- lib/context.js | 4 +++- lib/mappings.js | 8 ++++++++ test/map-core.js | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index 98cf7be..dae278b 100644 --- a/lib/context.js +++ b/lib/context.js @@ -163,7 +163,7 @@ class CoreContext { getCoreOffsetLocal(context, core) { if (context === this) return core - const key = core === 0 ? context.core.key : context.cores[core - 1].key + const key = this.mappings.unresolve(core === 0 ? context.core.key : context.cores[core - 1].key) const offset = this.getCoreOffsetLocalByKey(key) if (offset > -1) return offset @@ -185,6 +185,8 @@ class CoreContext { async getCoreOffsetByKey(key, config) { await this.core.ready() + key = this.mappings.unresolve(key) + let offset = this.getCoreOffsetLocalByKey(key) if (offset > -1) return offset diff --git a/lib/mappings.js b/lib/mappings.js index 2552176..499f321 100644 --- a/lib/mappings.js +++ b/lib/mappings.js @@ -3,6 +3,7 @@ const b4a = require('b4a') class Mappings { constructor() { this.known = new Map() + this.reverse = new Map() this.dirty = false this._flushed = null } @@ -13,12 +14,19 @@ class Mappings { return known === undefined ? key : known } + unresolve(key) { + if (this.reverse.size === 0) return key + const from = this.reverse.get(b4a.toString(key, 'hex')) + return from === undefined ? key : from + } + add(mappings, { persisted = false } = {}) { for (const { from, to } of mappings) { if (b4a.equals(from, to)) continue const hex = b4a.toString(from, 'hex') if (this.known.has(hex)) continue this.known.set(hex, to) + this.reverse.set(b4a.toString(to, 'hex'), from) this._flushed = null if (!persisted) this.dirty = true } diff --git a/test/map-core.js b/test/map-core.js index 9831182..6e56c40 100644 --- a/test/map-core.js +++ b/test/map-core.js @@ -361,6 +361,44 @@ test('mappings - cores({ recursive: true }) walks the tables of every core reach await b.close() }) +test('mappings - the core table keeps the keys as they were before the mapping', async function (t) { + const store = new Corestore(await t.tmp()) + t.teardown(() => store.close()) + + const { a, w, copy, mappings } = await setup(store) + await w.write({ mappings }).flush() + + // the tree moves onto the original, so it now reads through the copy + w.move({ key: a.core.key, length: a.core.length }) + t.alike(w.core.key, copy.key, 'the tree sits on the copy') + + const batch = w.write() + batch.tryPut(key(60), value(60)) + await batch.flush() + + const table = (keys) => keys.map(hex) + const local = w.context.getLocalContext() + t.ok( + table(local.cores.map((c) => c.key)).includes(hex(a.core.key)), + 'the original is in the table' + ) + t.absent(table(local.cores.map((c) => c.key)).includes(hex(copy.key)), 'the copy never is') + await w.close() + + await a.core.clear(0, a.core.length) + + const r = new Bee(store.namespace('w'), { writable: false }) + await r.ready() + await r.cores() + t.absent( + table(r.context.cores.map((c) => c.key)).includes(hex(copy.key)), + 'nor in the checkpoint written' + ) + t.alike((await r.get(key(60))).value, value(60)) + await readAll(r, 50, t, 'and every entry of the original reads through the mapping') + await r.close() +}) + function replicateStores(a, b) { const s1 = a.replicate(true) const s2 = b.replicate(false)