@ocap/statedb-fs
Version:
OCAP statedb adapter that uses fs as backend
152 lines (119 loc) • 4.14 kB
JavaScript
/* eslint-disable no-underscore-dangle */
const path = require('path');
const omit = require('lodash/omit');
const { StateDBTable } = require('@ocap/statedb');
const Lokijs = require('lokijs');
const FsAdapter = require('lokijs/src/loki-fs-structured-adapter');
const debug = require('debug')(require('../../package.json').name);
class FsTable extends StateDBTable {
/**
* @param {Object} param
* @param {string} param.name db name
* @param {string|string[]} param.uniqIndex primary keys
* @param {string} param.dataDir data directory for db
* @param {boolean} param.syncBalance sync balance table when create / update / get
* @param {BalanceTable} param.balanceTable balance table
*/
constructor({ name, dataDir, uniqIndex, balanceTable, syncBalance = false }) {
super(uniqIndex);
this.name = name;
this.dataDir = dataDir;
this.uniqIndex = uniqIndex;
this.collection = null;
this.balanceTable = balanceTable;
this.syncBalance = syncBalance;
if (this.syncBalance && !this.balanceTable) {
throw new Error('balanceTable is required when syncBalance is true');
}
const adapter = new FsAdapter();
const db = new Lokijs(path.join(dataDir, `${name}.db`), {
adapter,
autoload: true,
autosave: true,
autosaveInterval: 1000,
autoloadCallback: () => {
this.collection = db.getCollection(name);
if (this.collection === null) {
this.collection = db.addCollection(name, { unique: [this.primaryKey], clone: true });
}
this.markReady();
},
});
}
async _create(key, attrs) {
const id = this.generatePrimaryKey(key);
// Don't call this._get here cause _get may be overwritten
const doc = await FsTable.prototype._get.call(this, id);
if (doc) {
throw new Error(`${this.name} already exists: ${key}`);
}
debug(`insert ${this.name}`, attrs);
const insertAttrs = { ...attrs };
// insert without tokens cause we've migrate token to balance table
if (this.syncBalance) {
delete insertAttrs.tokens;
}
const result = await this.collection.insert({ [this.primaryKey]: id, ...insertAttrs });
if (this.syncBalance && attrs.tokens) {
debug(`update balance for ${this.name}`, attrs);
result.tokens = await this.balanceTable.updateBalance({
address: attrs.address,
tokens: attrs.tokens,
context: attrs.context,
});
}
return result;
}
async _get(key) {
if (!key) return null;
const id = this.generatePrimaryKey(key);
const result = await this.collection.by(this.primaryKey, id);
if (result && this.syncBalance) {
const balance = await this.balanceTable.getBalance(result.address);
result.tokens = { ...(result.tokens || {}), ...balance };
}
return result;
}
_history() {
return [];
}
async _update(key, updates) {
const id = this.generatePrimaryKey(key);
// Don't call this._get here cause _get may be overwritten
const doc = await FsTable.prototype._get.call(this, id);
if (!doc) {
throw new Error(`${this.name} does not exists: ${key}`);
}
Object.assign(doc, updates);
const updateAttrs = { ...doc };
// insert without tokens cause we've migrate token to balance table
if (this.syncBalance) {
delete updateAttrs.tokens;
}
await this.collection.update(updateAttrs);
if (this.syncBalance && doc.tokens) {
debug(`update balance for ${this.name}`, doc);
doc.tokens = await this.balanceTable.updateBalance({
address: doc.address,
tokens: doc.tokens,
context: doc.context,
});
}
return doc;
}
updateOrCreate(exist, state, ctx) {
const id = this.generatePrimaryKey(state);
const attrs = omit(state, this.primaryKey);
if (!id) {
throw new Error('Cannot update or create without uniq index');
}
if (exist) {
return this.update(id, attrs, ctx);
}
return this.create(id, attrs, ctx);
}
_reset() {
this.collection.removeWhere({});
}
}
module.exports = FsTable;