UNPKG

hypercore-storage

Version:

Storage engine for Hypercore

312 lines (247 loc) 8.05 kB
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() } 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))) } 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))) } 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]) } 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 }