diff --git a/README.md b/README.md index c32000d..7f05300 100644 --- a/README.md +++ b/README.md @@ -21,26 +21,39 @@ Made fo for Node.JS and Ender.JS (browser-side). var localStorage = require('localStorage') , JsonStorage = require('json-storage') - , db = JsonStorage(localStorage, 'my-app-prefix') + , store = JsonStorage.create(localStorage, 'my-widget-namespace') , myValue = { foo: "bar" , baz: "quux" } ; - db.set('myKey', myValue); - myValue = db.get('myKey'); + store.set('myKey', myValue); + myValue = store.get('myKey'); API === - * JsonStorage(DOMStorage, 'application-prefix') // optional prefix - * get(key) - * set(key, value) - * remove(key) - * clear() - * keys() - * size() + * `create(DOMStorage, namespace)` + * `DOMStorage` should be globalStorage, sessionStorage, or localStorage + * `namespace` is optional string which allows multiple non-conflicting storage containers + * `get(key)` + * `set(key, value)` + * `remove(key)` + * `clear()` + * `keys()` + * `size()` + * `toJSON()` + * `JSON.stringify(store)` + +Upgrading from localStorage and 1.0.x to 1.1.x +=== + +1.1.x automatically attempts to upgrade your DOMStorage to use namespaces in backwards-compatible way. + +However, you can prevent this behaviour: + + localStorage.getItem('_json-storage-namespaced_', true); null vs undefined in JSON === @@ -52,22 +65,22 @@ they're simply to inform you of a few 'gotchas' inherent in JSON / DOMStorage co If they do, you're probably doing something wrong in the first place. -It is not valid to set `undefined` in JSON. So setting a key to `undefined` will remove it from the db. +It is not valid to set `undefined` in JSON. So setting a key to `undefined` will remove it from the store. -This means that `db.set('x')` is the same as `db.remove('x')`. +This means that `store.set('x')` is the same as `store.remove('x')`. To save `undefined`, use `null` instead. Note that both values that exist as `null` and values that don't exist at all will return `null`. - db.set('existing-key', null); - null === db.get('existing-key'); - null === db.get('non-existant-key'); + store.set('existing-key', null); + null === store.get('existing-key'); + null === store.get('non-existant-key'); The special case of `null` as `"null"`, aka `"\"null\""`: `null`, and `"null"` both parse as `null` the "object", instead of one being the string (which would be `"\"null\""`). -Objects containing `null`, however, parse as expected `{ "foo": null, "bar": "null" }` will parse as `foo` being `null` but `bar` being `"null"`, much unlike the value `"null"` being parsed on its own. \ No newline at end of file +Objects containing `null`, however, parse as expected `{ "foo": null, "bar": "null" }` will parse as `foo` being `null` but `bar` being `"null"`, much unlike the value `"null"` being parsed on its own. diff --git a/lib/index.js b/lib/index.js index 308e735..6b8dc21 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,9 @@ (function () { "use strict"; - var db; + var Store + , delim = ':' + ; function Stringify(obj) { var str; @@ -23,57 +25,122 @@ return obj; } - function JsonStorage(w3cStorage) { - this.db = w3cStorage; - this.keysAreDirty = true; + function escapeRegExp(str) { + return str.replace(/[-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); } - db = JsonStorage; + + function upgradeStorage(jss, w3cs) { + var i + , key + , val + , json = {} + ; + + if (jss._store.getItem('_json-storage-namespaced_', true)) { + return; + } + + // we can't modify the db while were reading or + // the keys will shift all over the place + for (i = 0; i < w3cs.length; i += 1) { + key = w3cs.key(i); + val = w3cs.getItem(key); + json[key] = val; + } + w3cs.clear(); + + Object.keys(json).forEach(function (key) { + jss.set(key, json[key]); + }); + + jss._store.setItem('_json-storage-namespaced_', true); + } + + function JsonStorage(w3cStorage, namespace) { + // called without new or create + // global will be undefined + if (!this) { + return new JsonStorage(w3cStorage, namespace); + } + + // if we didn't always add at least the delimeter + // then if a keyname with the delim, it would be more + // complicated to figure it out + this._namespace = delim; + this._namespace += (namespace || 'jss'); + + this._store = w3cStorage; + this._keysAreDirty = true; + if (!this._store.getItem('_json-storage-namespaced_')) { + upgradeStorage(this, w3cStorage); + } + } + Store = JsonStorage; - db.prototype.clear = function () { - this.keysAreDirty = true; - this.keys().forEach(function (uuid) { - this.remove(uuid); + Store.prototype.clear = function () { + this._keysAreDirty = true; + this.keys().forEach(function (key) { + this.remove(key); }, this); }; - db.prototype.remove = function (uuid) { - this.keysAreDirty = true; - this.db.removeItem(uuid); + Store.prototype.remove = function (key) { + this._keysAreDirty = true; + this._store.removeItem(key + this._namespace); }; - db.prototype.get = function (uuid) { - return Parse(this.db.getItem(uuid)); + Store.prototype.get = function (key) { + return Parse(this._store.getItem(key + this._namespace)); }; - db.prototype.set = function (uuid, val) { - this.keysAreDirty = true; - return this.db.setItem(uuid, Stringify(val)); + Store.prototype.set = function (key, val) { + this._keysAreDirty = true; + return this._store.setItem(key + this._namespace, Stringify(val)); }; - db.prototype.keys = function () { + Store.prototype.keys = function () { var i + , key + , delimAt ; - if (!this.keysAreDirty) { - return this.__keys.concat([]); + if (!this._keysAreDirty) { + return this._keys.concat([]); } - this.__keys = []; - for (i = 0; i < this.db.length; i += 1) { - this.__keys.push(this.db.key(i)); + this._keys = []; + for (i = 0; i < this._store.length; i += 1) { + key = this._store.key(i) || ''; + + delimAt = key.lastIndexOf(this._namespace); + // test if this key belongs to this widget + if (-1 !== delimAt) { + this._keys.push(key.substr(0, delimAt)); + } } - this.keysAreDirty = false; + this._keysAreDirty = false; - return this.__keys.concat([]); + return this._keys.concat([]); }; - db.prototype.size = function () { - return this.db.length; + Store.prototype.size = function () { + return this._store.length; }; - function create(w3cStorage) { - return new JsonStorage(w3cStorage); + Store.prototype.toJSON = function () { + var json = {} + ; + + this._keys.forEach(function (key) { + json[key] = this.get(key); + }, this); + + return json; + }; + + Store.create = function (w3cStorage, namespace) { + return new JsonStorage(w3cStorage, namespace); } - module.exports = create; + module.exports = Store; }()); diff --git a/lib/package.json b/lib/package.json index b0bb4f1..86a7578 100644 --- a/lib/package.json +++ b/lib/package.json @@ -3,7 +3,7 @@ "name": "json-storage", "description": "A wrapper for storage engines which use the W3C Storage API", "keywords": ["ender", "localStorage", "sessionStorage", "globalStorage", "Storage"], - "version": "1.0.1", + "version": "1.1.0", "repository": { "type": "git", "url": "git://github.com/coolaj86/json-storage-js.git" @@ -12,6 +12,7 @@ "node": ">= v0.2.0" }, "main": "index", + "browserDependencies": {}, "dependencies": {}, "devDependencies": {} } diff --git a/test/test.js b/test/test.js index c6313a1..e871a76 100644 --- a/test/test.js +++ b/test/test.js @@ -8,7 +8,7 @@ ; assert.equal(null, db.get('x')); - assert.deepEqual([], db.keys()); + assert.deepEqual([], db.keys(), 'found keys in empty db: ' + JSON.stringify(db.keys())); db.clear(); assert.equal(null, db.get('x')); @@ -23,5 +23,9 @@ db.clear(); assert.deepEqual([], db.keys()); + db.set('a', 'b'); + assert.deepEqual(['a'], db.keys()); + assert.deepEqual({ 'a': 'b' }, db.toJSON()); + console.log("Done! All tests pass."); }()); diff --git a/test/two-stores-can-have-same-keys.js b/test/two-stores-can-have-same-keys.js new file mode 100644 index 0000000..7d8d1bf --- /dev/null +++ b/test/two-stores-can-have-same-keys.js @@ -0,0 +1,28 @@ +(function () { + "use strict"; + + var localStorage = require('localStorage') + , jsonStorage = require('../lib/') + , assert = require('assert') + , store0 = jsonStorage.create(localStorage, '0') + , store1 = jsonStorage.create(localStorage, '1') + ; + + store0.set('foo', 'bar'); + store1.set('foo', 'baz'); + + assert.strictEqual('bar', store0.get('foo')); + assert.strictEqual('baz', store1.get('foo')); + + store0.remove('foo'); + + assert.strictEqual(null, store0.get('foo')); + assert.strictEqual('baz', store1.get('foo'), 'after removing a value from store1, store0 lost it as well'); + + store0.clear(); + + assert.strictEqual(null, store0.get('foo')); + assert.strictEqual('baz', store1.get('foo'), 'after clearing store0, store1 lost values'); + + console.log('[PASS] no assertions failed'); +}());