fox-wamp
Version:
Web Application Message Router/Server WAMP/MQTT
187 lines (170 loc) • 5.59 kB
JavaScript
'use strict'
const { match, restoreUri, defaultParse } = require('../topic_pattern')
const { KeyValueStorageAbstract, isDataEmpty, deepDataMerge, unSerializeData, makeDataSerializable } = require('../realm')
const { KPQueue } = require('../allot/kpqueue')
class SqliteModKv {
constructor (db) {
this.db = db
this.pkq = new KPQueue()
}
async createTables () {
await this.db.run(
'CREATE TABLE IF NOT EXISTS kv (' +
'key TEXT not null,' +
'kv_realm TEXT not null,' +
'value TEXT not null,' +
'will_sid TEXT not null,' +
'opt TEXT not null,' +
'stamp TEXT,'+ // out message id
'PRIMARY KEY (kv_realm, key));'
)
await this.db.run(
'CREATE INDEX IF NOT EXISTS kv_sid on kv (will_sid);'
)
await this.db.run(
'CREATE TABLE IF NOT EXISTS set_value (' +
'key TEXT not null,' +
'kv_realm TEXT not null,' +
'msg_id TEXT not null,' +
'will_sid TEXT not null,' +
'set_when TEXT not null,' +
'PRIMARY KEY (kv_realm, key, msg_id));'
)
await this.db.run(
'CREATE INDEX IF NOT EXISTS set_value_sid on set_value (will_sid);'
)
await this.db.run(
'CREATE TABLE IF NOT EXISTS update_history (' +
'msg_id TEXT not null,' +
'msg_origin TEXT not null,' +
'msg_realm TEXT not null,' +
'msg_uri TEXT not null,' +
'msg_oldv TEXT,' +
'PRIMARY KEY (msg_id));'
)
}
async eraseSessionData (realmName, sessionId, runInboundEvent) {
const toRemove = []
await this.db.each(
'SELECT key, opt FROM kv WHERE kv_realm = ? AND will_sid = ?',
[realmName, sessionId],
(err, row) => {
toRemove.push({key: row.key, opt: JSON.parse(row.opt)})
}
)
for (let row of toRemove) {
if (row.opt.will) {
runInboundEvent(sessionId, defaultParse(row.key), row.opt.will)
} else {
runInboundEvent(sessionId, defaultParse(row.key), null)
}
}
// erase is expected to be done by incoming message
// await this.db.run(
// 'DELETE FROM kv WHERE kv_realm = ? AND will_sid = ?',
// [realmName, sessionId]
// )
await this.db.run(
'DELETE FROM set_value WHERE kv_realm = ? AND will_sid = ?',
[realmName, sessionId]
)
}
// @return promise
setKeyValue (realmName, suri, makeId, origin, data, opt, sid, pubOutEvent) {
return this.pkq.enQueue(realmName + '|' + suri, async () => {
const willSid = ('will' in opt) ? sid : 0
try {
const oldRow = await this.db.get(
'SELECT value, opt FROM kv WHERE kv_realm = ? AND key = ?',
[realmName, suri]
)
const oldData = oldRow && oldRow.value ? unSerializeData(JSON.parse(oldRow.value)) : null
const newData = deepDataMerge(oldData, data)
const updateHistoryId = makeId.makeIdStr()
if (isDataEmpty(newData)) {
await this.db.run('DELETE FROM kv WHERE kv_realm = ? AND key = ?', [realmName, suri])
} else {
await this.db.run(
'INSERT OR REPLACE INTO kv (kv_realm, key, value, will_sid, opt, stamp) VALUES (?, ?, ?, ?, ?, ?)',
[realmName, suri, JSON.stringify(makeDataSerializable(newData)), willSid, JSON.stringify(opt), updateHistoryId]
)
}
this.saveUpdateHistory(origin, updateHistoryId, realmName, suri, makeDataSerializable(oldData))
pubOutEvent('event', { sid, oldData, newData })
} catch (e) {
console.log('ERROR:', e.message)
}
})
}
async applySegment(segment, pubOutEvent) {
for (let inEvent of segment) {
console.log("MOD-KV", inEvent)
if (inEvent.opt.trace) {
await this.setKeyValue(
inEvent.realm,
restoreUri(inEvent.uri),
this.makeId(),
inEvent.qid,
inEvent.data,
inEvent.opt,
inEvent.sid,
pubOutEvent
)
}
}
}
getKey (realmName, uri, cbRow) {
const strUri = restoreUri(uri)
return this.pkq.enQueue(realmName + '|' + strUri, async () => {
// TODO: optimize search
await this.db.each(
'SELECT key, value, opt, stamp FROM kv WHERE kv_realm = ?',
[realmName],
(err, row) => {
const aKey = defaultParse(row.key)
if (match(aKey, uri)) {
const rowData = JSON.parse(row.value)
cbRow(aKey, unSerializeData(rowData), row.stamp)
}
}
)
})
}
saveUpdateHistory (id, origin, realmName, suri, oldv, newv) {
return this.db.run(
'INSERT INTO update_history VALUES (?,?,?,?,?);',
[id, origin, realmName, suri, JSON.stringify(oldv)]
)
}
}
class SqliteKv extends KeyValueStorageAbstract {
constructor (mod, makeId, realmName) {
super()
this.mod = mod
this.makeId = makeId
this.realmName = realmName
}
eraseSessionData (sessionId) {
return this.mod.eraseSessionData (this.realmName, sessionId, this.runInboundEvent.bind(this))
}
// @result promise
setKeyActor (actor) {
const suri = this.getStrUri(actor)
return this.mod.setKeyValue(
this.realmName,
suri,
this.makeId,
actor.getEventId(),
actor.getData(),
actor.getOpt(),
actor.getSid(),
(kind, outEvent) => actor.confirm(actor.msg)
)
}
// @return promise
getKey (uri, cbRow) {
return this.mod.getKey(this.realmName, uri, cbRow)
}
}
exports.SqliteKv = SqliteKv
exports.SqliteModKv = SqliteModKv