hypercore-rehoster
Version:
Help keep the hypercores of your choice available
118 lines (95 loc) • 3.02 kB
JavaScript
const idEnc = require('hypercore-id-encoding')
const safetyCatch = require('safety-catch')
const { REHOSTER_ENCODINGS } = require('./lib/encodings')
class RehosterDb {
constructor (bee) {
this.bee = bee
this._syncing = null
}
async add (key, value = {}) {
key = idEnc.decode(key)
const entry = {
description: value.description || null,
version: {
major: value.major || null,
minor: value.minor || null
}
}
await this.bee.put(key, entry, {
cas: (prev, next) => { // only if new or different value
if (!prev) return true
return prev.value.description !== next.value.description
},
...REHOSTER_ENCODINGS
})
}
async get (key) {
key = idEnc.decode(key)
return (await this.bee.get(key, REHOSTER_ENCODINGS))?.value || null
}
async has (key) {
key = idEnc.decode(key)
return await this.bee.get(key, REHOSTER_ENCODINGS) !== null
}
async delete (key) {
key = idEnc.decode(key)
await this.bee.del(key, REHOSTER_ENCODINGS)
}
// WARNING: The sync method should NOT be used in combination
// with the add and del methods: no care is taken to avoid
// race conditions against add and delete operations while
// a sync is running (only against other syncs)
async sync (desiredState) {
// We use obj reference for race-condition control
// so don't want to get thrown off if the caller
// re-uses an object across calls
desiredState = new Map(desiredState)
this._nextDesiredState = desiredState
let sanityCheck = 0
while (this._syncing !== null) {
try {
await this._syncing
} catch (e) {
safetyCatch(e) // we don't care about errors of other runs
}
if (this._nextDesiredState !== desiredState) {
// more recent desired state has arrived
return false
}
if (sanityCheck++ > 100_000) throw new Error('Almost certian logical bug in Rehoster.sync(...) code')
}
try {
this._syncing = this._sync(desiredState)
await this._syncing
} finally {
this._syncing = null
}
return true
}
async _sync (desiredState) {
const normKey = (key) => idEnc.normalize(key)
const normDesiredState = new Map()
for (const [key, value] of desiredState) {
normDesiredState.set(normKey(key), value)
}
const toDel = []
for await (const { key } of this.createReadStream()) {
if (normDesiredState.get(normKey(key)) === undefined) {
const delOp = this.delete(key)
delOp.catch(safetyCatch)
toDel.push(delOp)
}
}
const toAdd = []
for (const [key, entry] of normDesiredState) {
const addOp = this.add(key, entry) // duplicates are no-ops
addOp.catch(safetyCatch)
toAdd.push(addOp)
}
await Promise.all([...toAdd, ...toDel])
}
createReadStream (range = {}) {
return this.bee.createReadStream(range, REHOSTER_ENCODINGS)
}
}
module.exports = RehosterDb