// Spike 2 (partial, org #36) — can rocksdb-native open a real Corestore in a worklet-shaped thread?
//
// ⚠ HOW TO RUN. This needs `bare-worker`, which favela-web does NOT depend on — it is a probe, not a
// feature, and adding a dep to ship a probe would put it in every published tree. Run it from a tree
// that already has both a worker stack and corestore, e.g.:
//
// cp spike/worklet-storage-*.mjs /tmp/p/ && cd /tmp/p
// ln -s ../../hyper-fleeky/pear-git/node_modules node_modules
// ~/.local/share/store/apps/pgh/runtime/bare worklet-storage-probe.mjs
//
// ⛔ VERSION TRAP, and it cost real time. `bare-worker@4.4.0` + `bare-channel@5.2.4` (npm latest as of
// 2026-08-07) FATALS on both bare 1.26.0 and 1.28.7, before any user code runs:
// Fatal error in v8::SharedArrayBuffer::New
// Cannot construct SharedArrayBuffer with BackingStore of ArrayBuffer
// `bare-worker@4.1.6` + `bare-channel@5.2.2` (what pear-git ships) works. Downgrading those two in a
// fresh tree then pulled a `bare-module` prebuild that failed ABI against the runtime
// (`undefined symbol: js_get_function_id`) — so use a WHOLE tree that already works rather than
// resolving a new one. Recorded because "the worker stack fatals" reads like a worklet finding and is
// not one.
//
// Results 2026-08-07, on pear-git's tree (corestore 7.9.2, rocksdb-native 3.13.0, hypercore-storage
// 2.7.1 — OLDER than the fleet's current 3.17.2 / 3.2.0; the threading property is not version-specific
// but re-running on current versions is a cheap follow-up): 8/8.
//
// ⛔ WHAT THIS DOES AND DOES NOT PROVE. bare-kit's worklet is a Bare thread inside the app process with
// an IPC bridge; `bare-worker` is a Bare thread with the same IPC primitive. So this genuinely tests the
// THREADING half — which is the part most likely to break rocksdb, since rocksdb runs its own background
// threads and holds a process-wide exclusive lock per store.
//
// It does NOT test: the android-arm64 prebuild under Bionic, JNI, bare-kit's own worklet lifecycle, or
// Android's file-system semantics. Those need a device or an emulator (this host has no JDK, no Android
// SDK, and no /dev/kvm). celltron does NOT close the gap either — it runs the linux-arm64 GLIBC build
// under proot, never the android Bionic prebuild.
//
// Four questions, in the order they would bite a phone:
// 1. does a Corestore open at all off the main thread?
// 2. can the main thread hold its OWN store at the same time? (the real app shape: app + worklet)
// 3. does the data survive the thread exiting?
// 4. is the RocksDB lock RELEASED when the worklet dies — i.e. can it be reopened?
// (4) is the one that decides whether a phone survives Doze: spec §5 says the node will be killed and
// restarted constantly, and a lock that outlives the worklet makes every restart fail.
const Worker = require('bare-worker')
const Corestore = require('corestore')
const Hyperbee = require('hyperbee')
const fs = require('bare-fs')
const os = require('bare-os')
const path = require('bare-path')
const N = 2000
const root = path.join(os.tmpdir(), 'spike2-' + Date.now())
const workerDir = path.join(root, 'worklet-store')
const mainDir = path.join(root, 'main-store')
fs.mkdirSync(workerDir, { recursive: true })
fs.mkdirSync(mainDir, { recursive: true })
const results = []
const ok = (label, pass, detail = '') => { results.push([pass, label, detail]); console.log(` ${pass ? 'ok ' : 'FAIL'} - ${label}${detail ? ' ' + detail : ''}`) }
async function main () {
console.log('spike 2 — Corestore/rocksdb in a worklet-shaped thread\n')
console.log(' scratch:', root, '\n')
// --- 2. the MAIN thread holds its own store, concurrently ---
// The real app shape is not "a worklet alone": it is a host process that may also touch storage. Two
// rocksdb instances in one address space, on different threads, is the thing to prove.
const mainStore = new Corestore(mainDir)
await mainStore.ready()
const mainBee = new Hyperbee(mainStore.get({ name: 'main' }), { keyEncoding: 'utf-8', valueEncoding: 'json' })
await mainBee.ready()
await mainBee.put('hello', { from: 'main-thread' })
ok('main thread opened its OWN Corestore', true)
// --- 1. the worklet-shaped thread opens one too ---
const steps = []
let workerKey = null
const worker = new Worker(require.resolve('./worklet-storage-worker.mjs'), { workerData: { dir: workerDir, n: N } })
const done = new Promise((resolve, reject) => {
worker.on('message', (m) => {
steps.push(m)
if (m.step === 'ERROR') reject(new Error(m.error))
if (m.step === 'closed') { workerKey = m.key; resolve() }
})
worker.on('exit', (code) => { if (!workerKey) reject(new Error('worker exited (' + code + ') before closing its store')) })
})
await done
const by = (s) => steps.find((x) => x.step === s)
ok('worklet-thread opened a Corestore', !!by('corestore-ready'), by('corestore-ready') ? by('corestore-ready').ms + 'ms' : '')
ok('worklet-thread opened a Hyperbee', !!by('hyperbee-ready'))
ok(`worklet-thread wrote ${N} records`, by('wrote') && by('wrote').n === N, by('wrote') ? by('wrote').ms + 'ms' : '')
ok('range scan read them all back', by('read') && by('read').seen === N && !by('read').firstBad,
by('read') ? `seen=${by('read').seen}` + (by('read').firstBad ? ` BAD@${by('read').firstBad}` : '') : '')
// Main-thread store still usable while the worklet was hammering its own.
const still = await mainBee.get('hello')
ok('main-thread store still readable throughout', !!(still && still.value.from === 'main-thread'))
await worker.terminate()
// --- 3 + 4. data survived, and the lock was released ---
// Reopening from the main thread after the worklet is gone is exactly the Doze-restart case.
let reopened = null
let lockErr = null
try {
const s2 = new Corestore(workerDir)
await s2.ready()
const b2 = new Hyperbee(s2.get({ name: 'probe' }), { keyEncoding: 'utf-8', valueEncoding: 'json' })
await b2.ready()
let count = 0
for await (const _ of b2.createReadStream()) count++
reopened = count
await s2.close()
} catch (e) { lockErr = (e && e.message) || String(e) }
ok('store REOPENS after the worklet exits (the Doze-restart case)', lockErr === null, lockErr || '')
ok(`all ${N} records survived the thread`, reopened === N, `found ${reopened}`)
await mainStore.close()
const passed = results.filter(([p]) => p).length
console.log(`\n${passed}/${results.length} checks passed`)
console.log('\nNOT covered here: android-arm64 under Bionic, JNI, bare-kit worklet lifecycle, Android FS semantics.')
try { fs.rmSync(root, { recursive: true, force: true }) } catch {}
if (passed !== results.length) Bare.exitCode = 1
}
main().catch((e) => { console.error('\nSPIKE FAILED:', (e && e.stack) || e); Bare.exitCode = 1 })