238 lines
6.6 KiB
JavaScript
238 lines
6.6 KiB
JavaScript
var util = require('./util')
|
|
var slice = util.slice
|
|
var pluck = util.pluck
|
|
var each = util.each
|
|
var bind = util.bind
|
|
var create = util.create
|
|
var isList = util.isList
|
|
var isFunction = util.isFunction
|
|
var isObject = util.isObject
|
|
|
|
module.exports = {
|
|
createStore: createStore
|
|
}
|
|
|
|
var storeAPI = {
|
|
version: '2.0.12',
|
|
enabled: false,
|
|
|
|
// get returns the value of the given key. If that value
|
|
// is undefined, it returns optionalDefaultValue instead.
|
|
get: function(key, optionalDefaultValue) {
|
|
var data = this.storage.read(this._namespacePrefix + key)
|
|
return this._deserialize(data, optionalDefaultValue)
|
|
},
|
|
|
|
// set will store the given value at key and returns value.
|
|
// Calling set with value === undefined is equivalent to calling remove.
|
|
set: function(key, value) {
|
|
if (value === undefined) {
|
|
return this.remove(key)
|
|
}
|
|
this.storage.write(this._namespacePrefix + key, this._serialize(value))
|
|
return value
|
|
},
|
|
|
|
// remove deletes the key and value stored at the given key.
|
|
remove: function(key) {
|
|
this.storage.remove(this._namespacePrefix + key)
|
|
},
|
|
|
|
// each will call the given callback once for each key-value pair
|
|
// in this store.
|
|
each: function(callback) {
|
|
var self = this
|
|
this.storage.each(function(val, namespacedKey) {
|
|
callback.call(self, self._deserialize(val), (namespacedKey || '').replace(self._namespaceRegexp, ''))
|
|
})
|
|
},
|
|
|
|
// clearAll will remove all the stored key-value pairs in this store.
|
|
clearAll: function() {
|
|
this.storage.clearAll()
|
|
},
|
|
|
|
// additional functionality that can't live in plugins
|
|
// ---------------------------------------------------
|
|
|
|
// hasNamespace returns true if this store instance has the given namespace.
|
|
hasNamespace: function(namespace) {
|
|
return (this._namespacePrefix == '__storejs_'+namespace+'_')
|
|
},
|
|
|
|
// createStore creates a store.js instance with the first
|
|
// functioning storage in the list of storage candidates,
|
|
// and applies the the given mixins to the instance.
|
|
createStore: function() {
|
|
return createStore.apply(this, arguments)
|
|
},
|
|
|
|
addPlugin: function(plugin) {
|
|
this._addPlugin(plugin)
|
|
},
|
|
|
|
namespace: function(namespace) {
|
|
return createStore(this.storage, this.plugins, namespace)
|
|
}
|
|
}
|
|
|
|
function _warn() {
|
|
var _console = (typeof console == 'undefined' ? null : console)
|
|
if (!_console) { return }
|
|
var fn = (_console.warn ? _console.warn : _console.log)
|
|
fn.apply(_console, arguments)
|
|
}
|
|
|
|
function createStore(storages, plugins, namespace) {
|
|
if (!namespace) {
|
|
namespace = ''
|
|
}
|
|
if (storages && !isList(storages)) {
|
|
storages = [storages]
|
|
}
|
|
if (plugins && !isList(plugins)) {
|
|
plugins = [plugins]
|
|
}
|
|
|
|
var namespacePrefix = (namespace ? '__storejs_'+namespace+'_' : '')
|
|
var namespaceRegexp = (namespace ? new RegExp('^'+namespacePrefix) : null)
|
|
var legalNamespaces = /^[a-zA-Z0-9_\-]*$/ // alpha-numeric + underscore and dash
|
|
if (!legalNamespaces.test(namespace)) {
|
|
throw new Error('store.js namespaces can only have alphanumerics + underscores and dashes')
|
|
}
|
|
|
|
var _privateStoreProps = {
|
|
_namespacePrefix: namespacePrefix,
|
|
_namespaceRegexp: namespaceRegexp,
|
|
|
|
_testStorage: function(storage) {
|
|
try {
|
|
var testStr = '__storejs__test__'
|
|
storage.write(testStr, testStr)
|
|
var ok = (storage.read(testStr) === testStr)
|
|
storage.remove(testStr)
|
|
return ok
|
|
} catch(e) {
|
|
return false
|
|
}
|
|
},
|
|
|
|
_assignPluginFnProp: function(pluginFnProp, propName) {
|
|
var oldFn = this[propName]
|
|
this[propName] = function pluginFn() {
|
|
var args = slice(arguments, 0)
|
|
var self = this
|
|
|
|
// super_fn calls the old function which was overwritten by
|
|
// this mixin.
|
|
function super_fn() {
|
|
if (!oldFn) { return }
|
|
each(arguments, function(arg, i) {
|
|
args[i] = arg
|
|
})
|
|
return oldFn.apply(self, args)
|
|
}
|
|
|
|
// Give mixing function access to super_fn by prefixing all mixin function
|
|
// arguments with super_fn.
|
|
var newFnArgs = [super_fn].concat(args)
|
|
|
|
return pluginFnProp.apply(self, newFnArgs)
|
|
}
|
|
},
|
|
|
|
_serialize: function(obj) {
|
|
return JSON.stringify(obj)
|
|
},
|
|
|
|
_deserialize: function(strVal, defaultVal) {
|
|
if (!strVal) { return defaultVal }
|
|
// It is possible that a raw string value has been previously stored
|
|
// in a storage without using store.js, meaning it will be a raw
|
|
// string value instead of a JSON serialized string. By defaulting
|
|
// to the raw string value in case of a JSON parse error, we allow
|
|
// for past stored values to be forwards-compatible with store.js
|
|
var val = ''
|
|
try { val = JSON.parse(strVal) }
|
|
catch(e) { val = strVal }
|
|
|
|
return (val !== undefined ? val : defaultVal)
|
|
},
|
|
|
|
_addStorage: function(storage) {
|
|
if (this.enabled) { return }
|
|
if (this._testStorage(storage)) {
|
|
this.storage = storage
|
|
this.enabled = true
|
|
}
|
|
},
|
|
|
|
_addPlugin: function(plugin) {
|
|
var self = this
|
|
|
|
// If the plugin is an array, then add all plugins in the array.
|
|
// This allows for a plugin to depend on other plugins.
|
|
if (isList(plugin)) {
|
|
each(plugin, function(plugin) {
|
|
self._addPlugin(plugin)
|
|
})
|
|
return
|
|
}
|
|
|
|
// Keep track of all plugins we've seen so far, so that we
|
|
// don't add any of them twice.
|
|
var seenPlugin = pluck(this.plugins, function(seenPlugin) {
|
|
return (plugin === seenPlugin)
|
|
})
|
|
if (seenPlugin) {
|
|
return
|
|
}
|
|
this.plugins.push(plugin)
|
|
|
|
// Check that the plugin is properly formed
|
|
if (!isFunction(plugin)) {
|
|
throw new Error('Plugins must be function values that return objects')
|
|
}
|
|
|
|
var pluginProperties = plugin.call(this)
|
|
if (!isObject(pluginProperties)) {
|
|
throw new Error('Plugins must return an object of function properties')
|
|
}
|
|
|
|
// Add the plugin function properties to this store instance.
|
|
each(pluginProperties, function(pluginFnProp, propName) {
|
|
if (!isFunction(pluginFnProp)) {
|
|
throw new Error('Bad plugin property: '+propName+' from plugin '+plugin.name+'. Plugins should only return functions.')
|
|
}
|
|
self._assignPluginFnProp(pluginFnProp, propName)
|
|
})
|
|
},
|
|
|
|
// Put deprecated properties in the private API, so as to not expose it to accidential
|
|
// discovery through inspection of the store object.
|
|
|
|
// Deprecated: addStorage
|
|
addStorage: function(storage) {
|
|
_warn('store.addStorage(storage) is deprecated. Use createStore([storages])')
|
|
this._addStorage(storage)
|
|
}
|
|
}
|
|
|
|
var store = create(_privateStoreProps, storeAPI, {
|
|
plugins: []
|
|
})
|
|
store.raw = {}
|
|
each(store, function(prop, propName) {
|
|
if (isFunction(prop)) {
|
|
store.raw[propName] = bind(store, prop)
|
|
}
|
|
})
|
|
each(storages, function(storage) {
|
|
store._addStorage(storage)
|
|
})
|
|
each(plugins, function(plugin) {
|
|
store._addPlugin(plugin)
|
|
})
|
|
return store
|
|
}
|