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 }