This commit is contained in:
Raven Scott
2026-05-20 21:02:45 -04:00
parent 1f3f4b24a2
commit 14d0980b4f
734 changed files with 682 additions and 746 deletions
@@ -0,0 +1,153 @@
const test = require('brittle')
const { HyperP2PReactiveState } = require('../index.js')
const b4a = require('b4a')
const path = require('bare-path')
const fs = require('bare-fs/promises')
const process = require('bare-process')
test('hyper-p2p-reactive-state basic lifecycle and LWW CRDT', async (t) => {
const cwd = process.cwd()
const storageDir = path.join(cwd, 'test-reactive-storage-' + Date.now())
const topic = 'test-reactive-topic-' + Date.now()
const state = new HyperP2PReactiveState({
topic,
storageDir,
syncInterval: 2000,
expiry: 10000,
metadata: { test: true }
})
let readyFired = false
state.on('ready', () => { readyFired = true })
await state.ready()
t.ok(readyFired, 'ready event fired')
// Test set
const res1 = await state.set('user:alice', { name: 'Alice', score: 100 })
t.ok(res1.timestamp, 'set returns timestamp')
t.is(state.get('user:alice').value.name, 'Alice')
// Test update (should win by timestamp)
await state.set('user:alice', { name: 'Alice Updated', score: 150 })
t.is(state.get('user:alice').value.score, 150)
// Test query
const results = state.query({ keyPrefix: 'user:' })
t.ok(results.length >= 1, 'query returns results')
// Test delete
await state.delete('user:alice')
t.ok(state.get('user:alice').deleted, 'delete marks as deleted')
// Test subscribe
let changeFired = false
const unsub = state.subscribe('config:theme', (change) => {
changeFired = true
t.is(change.key, 'config:theme')
t.is(change.value, 'dark')
})
await state.set('config:theme', 'dark')
t.ok(changeFired, 'subscribe callback fired')
unsub()
// Test toJSON
const json = state.toJSON()
t.ok(json['config:theme'] === 'dark' || json['user:alice'] === null, 'toJSON works')
await state.close()
t.pass('closed cleanly')
// Cleanup storage
try {
await fs.rm(storageDir, { recursive: true, force: true })
} catch (e) {}
})
test('hyper-p2p-reactive-state persistence across instances', async (t) => {
const cwd = process.cwd()
const storageDir = path.join(cwd, 'test-reactive-persist-' + Date.now())
const topic = 'test-persist-topic-' + Date.now()
// First instance writes
const state1 = new HyperP2PReactiveState({
topic,
storageDir,
metadata: { instance: 1 }
})
await state1.ready()
await state1.set('shared:key', 'hello-persist')
await state1.close()
// Second instance loads from same storage
const state2 = new HyperP2PReactiveState({
topic,
storageDir,
metadata: { instance: 2 }
})
await state2.ready()
const loaded = state2.get('shared:key')
t.ok(loaded, 'persisted value loaded')
t.is(loaded.value, 'hello-persist')
await state2.close()
try {
await fs.rm(storageDir, { recursive: true, force: true })
} catch (e) {}
})
test('hyper-p2p-reactive-state LWW conflict resolution', async (t) => {
const cwd = process.cwd()
const storageDir = path.join(cwd, 'test-lww-' + Date.now())
const topic = 'test-lww-topic'
const state = new HyperP2PReactiveState({ topic, storageDir })
await state.ready()
const now = Date.now()
// Simulate concurrent sets with different timestamps
await state.set('conflict:key', 'first', { timestamp: now })
await state.set('conflict:key', 'second', { timestamp: now + 100 })
t.is(state.get('conflict:key').value, 'second', 'later timestamp wins')
// Same timestamp, peerId tiebreak
const peerA = 'aaa111'
const peerB = 'bbb222'
await state.set('tie:key', 'fromA', { timestamp: now, peerId: peerA })
await state.set('tie:key', 'fromB', { timestamp: now, peerId: peerB })
t.is(state.get('tie:key').value, 'fromB', 'higher peerId wins tie')
await state.close()
try {
await fs.rm(storageDir, { recursive: true, force: true })
} catch (e) {}
})
test('hyper-p2p-reactive-state: close without leak', async (t) => {
const m = new HyperP2PReactiveState()
await m.close()
t.pass()
})
test('hyper-p2p-reactive-state: validation rejects invalid input', async (t) => {
const m = new HyperP2PReactiveState()
try {
if (typeof m.addNeighbor === 'function') m.addNeighbor(null)
else if (typeof m.buildCircuit === 'function') m.buildCircuit([])
else if (typeof m.grant === 'function') m.grant(null, -1)
else if (typeof m.enqueue === 'function') m.enqueue('bad', null)
else if (typeof m.reportSample === 'function') m.reportSample(null, -1, -1)
else if (typeof m.fanout === 'function') m.fanout(null, 0)
else if (typeof m.probe === 'function') m.probe(null)
else if (typeof m.resolve === 'function') m.resolve(null)
else if (typeof m.acquire === 'function') m.acquire(null)
else throw new Error('no validation hook')
t.fail('expected throw')
} catch (err) {
t.ok(err instanceof Error)
}
await m.close()
})