const { Extension } = require('./messages')
// const MAX_ACTIVE = 32
const FLUSH_BATCH = 128
const MAX_PASSIVE_BATCH = 2048
const MAX_ACTIVE_BATCH = MAX_PASSIVE_BATCH + FLUSH_BATCH
class Batch {
constructor(outgoing, from) {
this.blocks = []
this.start = 0
this.end = 0
this.outgoing = outgoing
this.from = from
}
push(seq) {
const len = this.blocks.push(seq)
if (len === 1 || seq < this.start) this.start = seq
if (len === 1 || seq >= this.end) this.end = seq + 1
if (len >= FLUSH_BATCH) {
this.send()
this.clear()
}
}
send() {
if (!this.blocks.length) return
this.outgoing.send(
Extension.encode({
cache: { blocks: this.blocks, start: this.start, end: this.end }
}),
this.from
)
}
clear() {
this.start = this.end = 0
this.blocks = []
}
}
class HyperbeeExtension {
constructor(db) {
this.encoding = null
this.outgoing = null
this.db = db
this.active = 0
}
get(version, key) {
this.outgoing.broadcast(Extension.encode({ get: { version, key } }))
}
iterator(snapshot) {
if (snapshot.ended) return
if (snapshot.limit === 0) return
if (snapshot.limit === -1) snapshot.limit = 0
this.outgoing.broadcast(Extension.encode({ iterator: snapshot }))
}
onmessage(buf, from) {
// TODO: handle max active extension messages
// this.active++
const message = decode(buf)
if (!message) return
if (message.cache) this.oncache(message.cache, from)
if (message.get) this.onget(message.get, from)
if (message.iterator) this.oniterator(message.iterator, from)
}
oncache(message, from) {
if (!message.blocks.length) return
this.db.core.download(message)
}
onget(message, from) {
if (!message.version || message.version > this.db.version) return
const b = new Batch(this.outgoing, from)
const db = this.db.checkout(message.version)
db.get(message.key, {
extension: false,
wait: false,
update: false,
onseq
}).then(done, done)
function done() {
db.close().catch(noop)
b.send()
}
function onseq(seq) {
b.push(seq)
}
}
async oniterator(message, from) {
if (!message.version || message.version > this.db.version) return
const b = new Batch(this.outgoing, from)
const seqs = new Set()
let skip = message.checkpoint.length
let work = 0
const db = this.db.checkout(message.version)
const ite = db.createRangeIterator({
...message,
wait: false,
extension: false,
update: false,
limit: message.limit === 0 ? -1 : message.limit,
onseq(seq) {
if (skip && skip--) return
if (seqs.has(seq)) return
work++
seqs.add(seq)
b.push(seq)
}
})
try {
await ite.open()
// eslint-disable-next-line no-unmodified-loop-condition
while (work < MAX_ACTIVE_BATCH) {
if (!(await ite.next())) break
}
} catch (_) {
// do nothing
} finally {
ite.close().catch(noop)
db.close().catch(noop)
b.send()
}
}
static register(db) {
const e = new this(db)
e.outgoing = db.core.registerExtension('hyperbee', e)
return e
}
}
HyperbeeExtension.BATCH_SIZE = MAX_PASSIVE_BATCH
module.exports = HyperbeeExtension
function decode(buf) {
try {
return Extension.decode(buf)
} catch (err) {
return null
}
}
function noop() {}