objectstate
Version:
Lightweight, streaming state management.
162 lines (115 loc) • 2.83 kB
JavaScript
// Copyright 2015 Urban Airship and Contributors
var through = require('through')
var deepequal = require('deep-equal')
var prop = require('deep-property')
module.exports = objectState
function objectState (_initial) {
if (!isValidState(_initial)) {
throw new TypeError('invalid initial state: ' + _initial)
}
var state = deepcopy(_initial) || {}
var stream = through(write)
stream.get = get
stream.set = set
stream.remove = remove
stream.listen = listen
stream.listenOn = listenOn
stream.wait = wait
stream.state = copyState
stream.emitState = emitState
return stream
function write (data) {
if (!isValidState(data)) {
stream.emit('error', new TypeError('invalid state: ' + data))
return
}
if (typeof data === 'undefined' || equal(state, data)) {
return
}
state = deepcopy(data)
emitState()
}
function get (keypath) {
return deepcopy(prop.get(state, keypath))
}
function set (keypath, val) {
if (equal(get(keypath), val)) {
return
}
prop.set(state, keypath, deepcopy(val))
emitState()
}
function remove (keypath) {
var shouldEmit = prop.remove(state, keypath)
if (shouldEmit) {
emitState()
}
}
function listen (src, keypath) {
src.on('data', ondata)
src.once('end', function () {
src.removeListener('data', ondata)
})
return stream
function ondata (data) {
set(keypath, data)
}
}
function listenOn (ee, name, params) {
var paramsLength = params.length
ee.on(name, receiveEvent)
return stream
function receiveEvent () {
var args = [].slice.call(arguments)
wait(setParams)
function setParams () {
var param
var arg
for (var i = 0; i < paramsLength; ++i) {
param = params[i]
arg = args[i]
if (!param) {
continue
}
if (typeof arg === 'undefined') {
remove(param)
continue
}
set(param, arg)
}
}
}
}
function wait (fn) {
var original = deepcopy(state)
var queue = stream.queue
var shouldEmit = false
stream.queue = function () {
shouldEmit = true
}
try {
fn()
} finally {
stream.queue = queue
if (shouldEmit && !equal(state, original)) {
emitState()
}
}
}
function copyState () {
return deepcopy(state)
}
function emitState () {
stream.queue(deepcopy(state))
}
}
function deepcopy (obj) {
return typeof obj !== 'undefined' ? JSON.parse(JSON.stringify(obj)) : void 0
}
function equal (x, y) {
return deepequal(x, y, {strict: true})
}
function isValidState (x) {
var type = typeof x
return type === 'undefined' || type === 'object'
}