/**
* serveOnConnection must not rebuild the overlay lookup maps per connection.
*
* It built contentHashPaths + filePaths from scratch on EVERY inbound
* connection. Measured on tabertron's tree store (39,484 files / 2,977 trees)
* that is 3.8 MB of garbage and ~79,000 map insertions per connection, for a
* result identical every time until the manifest changes.
*
* It is also largely redundant: _scanIncremental already pushes new entries
* into the live maps so peers connected BEFORE a rescan can find new bytes
* without reconnecting. The full rebuild only has to happen when the manifest
* object is REPLACED (_scan / _scanBoot), not when it is appended to.
*
* Hence the cache key is manifest IDENTITY:
* - _scan / _scanBoot assign a new manifest object -> rebuild
* - _scanIncremental pushes into the existing array -> reuse, and its own
* .set() calls keep the cached maps current
*/
import test from 'brittle'
import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
import crypto from 'hypercore-crypto'
import SecretStream from '@hyperswarm/secret-stream'
import { DeployServer } from '../index.js'
function mktemp (prefix) {
const dir = path.join(os.tmpdir(), 'hyper-deploy-mapcache-' + prefix + '-' + crypto.randomBytes(6).toString('hex'))
fs.mkdirSync(dir, { recursive: true })
return dir
}
function pair () {
const a = new SecretStream(true)
const b = new SecretStream(false)
a.rawStream.pipe(b.rawStream).pipe(a.rawStream)
return { a, b }
}
test('overlay lookup maps are reused across connections, rebuilt on rescan', async (t) => {
t.timeout(30000)
const dir = mktemp('cache')
fs.writeFileSync(path.join(dir, 'a.txt'), 'hello')
const server = new DeployServer(dir)
await server.ready()
t.teardown(async () => {
try { await server.close() } catch {}
try { fs.rmSync(dir, { recursive: true, force: true }) } catch {}
})
const proto = server._overlayProtocol
t.ok(proto, 'overlay protocol enabled')
const c1 = pair()
server._onConnection(c1.a)
const maps1 = { hashes: proto._contentHashPaths, paths: proto._filePaths }
t.ok(maps1.hashes instanceof Map, 'first connection populates the maps')
t.ok(maps1.hashes.size > 0, 'maps are non-empty')
const c2 = pair()
server._onConnection(c2.a)
t.is(proto._contentHashPaths, maps1.hashes,
'second connection REUSES the same contentHashPaths instance — no rebuild')
t.is(proto._filePaths, maps1.paths,
'second connection REUSES the same filePaths instance — no rebuild')
// A full rescan replaces the manifest object, so the cache must drop.
fs.writeFileSync(path.join(dir, 'b.txt'), 'second file')
await server.rescan()
const c3 = pair()
server._onConnection(c3.a)
t.not(proto._contentHashPaths, maps1.hashes,
'a full rescan (new manifest object) forces a rebuild')
t.is(proto._filePaths.size, server.manifest.files.length,
'rebuilt maps cover every manifest entry')
for (const c of [c1, c2, c3]) { c.a.destroy(); c.b.destroy() }
})