const schema = require('../spec/hyperschema')
const { store, core } = require('./keys.js')
const View = require('./view.js')
const b4a = require('b4a')
const flat = require('flat-tree')
const CORESTORE_HEAD = schema.getEncoding('@corestore/head')
const CORESTORE_CORE = schema.getEncoding('@corestore/core')
const CORE_AUTH = schema.getEncoding('@core/auth')
const CORE_SESSIONS = schema.getEncoding('@core/sessions')
const CORE_HEAD = schema.getEncoding('@core/head')
const CORE_TREE_NODE = schema.getEncoding('@core/tree-node')
const CORE_DEPENDENCY = schema.getEncoding('@core/dependency')
const CORE_HINTS = schema.getEncoding('@core/hints')
class CoreTX {
constructor(core, db, view, changes) {
if (db.snapshotted) throw new Error('Cannot open core tx on snapshot')
this.core = core
this.db = db
this.view = view
this.changes = changes
}
setAuth(auth) {
this.changes.push([core.auth(this.core.corePointer), encode(CORE_AUTH, auth), null])
}
setSessions(sessions) {
this.changes.push([core.sessions(this.core.corePointer), encode(CORE_SESSIONS, sessions), null])
}
setHead(head) {
this.changes.push([core.head(this.core.dataPointer), encode(CORE_HEAD, head), null])
}
deleteHead() {
this.changes.push([core.head(this.core.dataPointer), null, null])
}
setDependency(dep) {
this.changes.push([core.dependency(this.core.dataPointer), encode(CORE_DEPENDENCY, dep), null])
}
setHints(hints) {
this.changes.push([core.hints(this.core.dataPointer), encode(CORE_HINTS, hints), null])
}
putBlock(index, data) {
this.changes.push([core.block(this.core.dataPointer, index), data, null])
}
deleteBlock(index) {
this.changes.push([core.block(this.core.dataPointer, index), null, null])
}
deleteBlockRange(start, end) {
this.changes.push([
core.block(this.core.dataPointer, start),
null,
core.block(this.core.dataPointer, end === -1 ? Infinity : end)
])
}
putBitfieldPage(index, data) {
this.changes.push([core.bitfield(this.core.dataPointer, index, 0), data, null])
}
deleteBitfieldPage(index) {
this.changes.push([core.bitfield(this.core.dataPointer, index, 0), null, null])
}
deleteBitfieldPageRange(start, end) {
this.changes.push([
core.bitfield(this.core.dataPointer, start, 0),
null,
core.bitfield(this.core.dataPointer, end === -1 ? Infinity : end, 0)
])
}
putTreeNode(node) {
this.changes.push([
core.tree(this.core.dataPointer, node.index),
encode(CORE_TREE_NODE, node),
null
])
}
deleteTreeNode(index) {
this.changes.push([core.tree(this.core.dataPointer, index), null, null])
}
deleteTreeNodeRange(start, end) {
this.changes.push([
core.tree(this.core.dataPointer, start),
null,
core.tree(this.core.dataPointer, end === -1 ? Infinity : end)
])
}
putUserData(key, value) {
const buffer = typeof value === 'string' ? b4a.from(value) : value
this.changes.push([core.userData(this.core.dataPointer, key), buffer, null])
}
deleteUserData(key) {
this.changes.push([core.userData(this.core.dataPointer, key), null, null])
}
putLocal(key, value) {
this.changes.push([core.local(this.core.dataPointer, key), value, null])
}
deleteLocal(key) {
this.changes.push([core.local(this.core.dataPointer, key), null, null])
}
deleteLocalRange(start, end) {
this.changes.push([
core.local(this.core.dataPointer, start),
null,
end === null ? core.localEnd(this.core.dataPointer) : core.local(this.core.dataPointer, end)
])
}
flush() {
const changes = this.changes
if (changes === null) return Promise.resolve(!this.view)
this.changes = null
if (this.view) {
this.view.apply(changes)
return Promise.resolve(false)
}
return View.flush(changes, this.db)
}
}
class CoreRX {
constructor(core, db, view) {
this.core = core
this.read = db.read({ autoDestroy: true })
this.view = view
view.readStart()
}
static async getAuth(db, c) {
return await decode(CORE_AUTH, await db.get(core.auth(c.corePointer)))
}
async getAuth() {
return await decode(CORE_AUTH, await this.view.get(this.read, core.auth(this.core.corePointer)))
}
async getSessions() {
return await decode(
CORE_SESSIONS,
await this.view.get(this.read, core.sessions(this.core.corePointer))
)
}
static async getHead(db, c) {
return await decode(CORE_HEAD, await db.get(core.head(c.dataPointer)))
}
async getHead() {
return await decode(CORE_HEAD, await this.view.get(this.read, core.head(this.core.dataPointer)))
}
async getDependency() {
return await decode(
CORE_DEPENDENCY,
await this.view.get(this.read, core.dependency(this.core.dataPointer))
)
}
static async getHints(db, c) {
return await decode(CORE_HINTS, await db.get(core.hints(c.dataPointer)))
}
async getHints() {
return await decode(
CORE_HINTS,
await this.view.get(this.read, core.hints(this.core.dataPointer))
)
}
getBlock(index) {
const dep = findBlockDependency(this.core.dependencies, index)
const data = dep === null ? this.core.dataPointer : dep.dataPointer
return this.view.get(this.read, core.block(data, index))
}
getBitfieldPage(index) {
return this.view.get(this.read, core.bitfield(this.core.dataPointer, index, 0))
}
async getTreeNode(index) {
const dep = findTreeDependency(this.core.dependencies, index)
const data = dep === null ? this.core.dataPointer : dep.dataPointer
return decode(CORE_TREE_NODE, await this.view.get(this.read, core.tree(data, index)))
}
async hasTreeNode(index) {
return (await this.getTreeNode(index)) !== null
}
getUserData(key) {
return this.view.get(this.read, core.userData(this.core.dataPointer, key))
}
getLocal(key) {
return this.view.get(this.read, core.local(this.core.dataPointer, key))
}
tryFlush() {
this.read.tryFlush()
this._free()
}
destroy() {
this.read.destroy()
this._free()
}
_free() {
if (this.view === null) return
this.view.readStop()
this.view = null
}
}
class CorestoreTX {
constructor(view) {
this.view = view
this.changes = []
}
setHead(head) {
this.changes.push([store.head(), encode(CORESTORE_HEAD, head), null])
}
setCoreHints(c, hints) {
this.changes.push([core.hints(c.dataPointer), encode(CORE_HINTS, hints), null])
}
putCore(discoveryKey, ptr) {
this.changes.push([store.core(discoveryKey), encode(CORESTORE_CORE, ptr), null])
}
putCoreByAlias(alias, discoveryKey) {
this.changes.push([store.coreByAlias(alias), discoveryKey, null])
}
clear() {
const [start, end] = store.clear()
this.changes.push([start, null, end])
}
apply() {
if (this.changes === null) return
this.view.apply(this.changes)
this.changes = null
}
}
class CorestoreRX {
constructor(db, view) {
this.read = db.read({ autoDestroy: true })
this.view = view
view.readStart()
}
async getHead() {
return decode(CORESTORE_HEAD, await this.view.get(this.read, store.head()))
}
async getCore(discoveryKey) {
return decode(CORESTORE_CORE, await this.view.get(this.read, store.core(discoveryKey)))
}
getCoreByAlias(alias) {
return this.view.get(this.read, store.coreByAlias(alias))
}
tryFlush() {
this.read.tryFlush()
this._free()
}
destroy() {
this.read.destroy()
this._free()
}
_free() {
if (this.view === null) return
this.view.readStop()
this.view = null
}
}
module.exports = { CorestoreTX, CorestoreRX, CoreTX, CoreRX }
function findBlockDependency(dependencies, index) {
for (let i = 0; i < dependencies.length; i++) {
const dep = dependencies[i]
if (index < dep.length) return dep
}
return null
}
function findTreeDependency(dependencies, index) {
for (let i = 0; i < dependencies.length; i++) {
const dep = dependencies[i]
if (flat.rightSpan(index) <= (dep.length - 1) * 2) return dep
}
return null
}
function decode(enc, buffer) {
if (buffer === null) return null
return enc.decode({ start: 0, end: buffer.byteLength, buffer })
}
function encode(enc, m) {
// TODO: use fancy slab for small messages
const state = { start: 0, end: 0, buffer: null }
enc.preencode(state, m)
state.buffer = b4a.allocUnsafe(state.end)
enc.encode(state, m)
return state.buffer
}