Compare commits

...

14 Commits

Author SHA1 Message Date
2e4745b189 update null examples 2018-07-31 16:58:36 +00:00
21098984f1 v2.1.2: update urls 2018-07-31 10:49:03 -06:00
b2fdf0aba2 check false on the right variable 2014-02-24 05:27:01 -07:00
7b459f1f87 allow namespaces to be turned off 2014-02-24 05:23:47 -07:00
3a2ada1ce0 Update README.md 2014-02-03 15:34:54 -06:00
a881915cfe Update README.md 2014-02-03 15:33:28 -06:00
5e9e9d8a41 Update README.md 2014-02-03 15:13:55 -06:00
780b7583a1 Update README.md 2014-02-03 15:13:19 -06:00
c4efbba4da Update README.md 2014-02-03 15:08:54 -06:00
6f7b83ea15 Update README.md 2014-02-03 15:06:26 -06:00
1a9a10c24e Update README.md 2014-02-03 15:05:16 -06:00
9cdcdf98d9 updated package meta data 2014-02-03 14:00:29 -07:00
a430f94fce made more browser-friendly and added stringify option 2014-01-26 14:39:47 -07:00
699f673b79 made more browser-friendly 2014-01-26 14:25:42 -07:00
14 changed files with 309 additions and 1005 deletions

119
README.md Normal file
View File

@ -0,0 +1,119 @@
JsonStorage
====
A light, sensible abstraction for DOMStorage (such as localStorage).
Installation
===
Bower (Browser)
```bash
bower install json-storage
# or
wget https://git.coolaj86.com/coolaj86/json-storage.js/raw/branch/master/json-storage.js
```
Node.JS (Server)
```bash
npm install -S localStorage json-storage
```
Usage
===
Made for Node.js and Bower (browser-side).
```javascript
var localStorage = require('localStorage')
, JsonStorage = require('json-storage').JsonStorage
, store = JsonStorage.create(localStorage, 'my-widget-namespace', { stringify: true })
, myValue = {
foo: "bar"
, baz: "quux"
}
;
store.set('myKey', myValue);
myValue = store.get('myKey');
```
NOTE: When using with Node and the `localStorage` module,
you may wish to pass the `{ stringify: false }` option to prevent double stringification.
API
===
* `JsonStorage.create(DOMStorage, namespace, opts)`
* `DOMStorage` should be globalStorage, sessionStorage, or localStorage. Defaults to window.localStorage if set to `null`.
* `namespace` is optional string which allows multiple non-conflicting storage containers. For example you could pass two widgets different storage containers and not worry about naming conflicts:
* `Gizmos.create(JsonStorage.create(null, 'my-gizmos'))`
* `Gadgets.create(JsonStorage.create(null, 'my-gadgets'))`
* Namespacing can be turned off by explicitly setting `false`
* `Gadgets.create(JsonStorage.create(null, false))`
* `opts`
* `stringify` set to `false` in `node` to avoid double stringifying
* `store.get(key)`
* `store.set(key, value)`
* `store.remove(key)`
* `store.clear()`
* `store.keys()`
* `store.size()`
* `store.toJSON()`
* `JSON.stringify(store)`
**NOTE**: You cannot omit optional parameters. Use `null` if you want accepts the defaults for some things and provide a values for others. For example: `JsonStorage.create(null, null, { stringify: false })`
JSON / DOMStorage Conversion Gotchas
===
These notes do not reflect a bugs or defects in this library,
they're simply to inform you of a few 'gotchas' inherent in JSON / DOMStorage conversion.
99.999% of the time these gotchas shouldn't effect you in any way.
If they do, you're probably doing something wrong in the first place.
### `undefined` vs `null`
It is not valid to set `undefined` in JSON. So setting a key to `undefined` will remove it from the store.
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`.
```javascript
store.set('existing-key', null);
null === store.get('existing-key');
null === store.get('non-existant-key');
```
### `null` vs `"null"`
The special case of `null` as `"null"`, aka `"\"null\""`:
```
typeof null // object
typeof "null" // string
typeof "\"null\"" // string
```
`null`, and `"null"` both parse as `null` the "object", instead of one being the string (which would be `"\"null\""`).
```
JSON.parse(null) // null (object)
JSON.parse("null") // null (object)
JSON.parse("\"null\"") // 'null' (string)
```
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.
```
JSON.parse('{ "foo": null }') // { foo: null }
JSON.parse('{ "foo": "null" }') // { foo: 'null' }
```

28
bower.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "json-storage",
"version": "2.0.1",
"homepage": "https://github.com/coolaj86/json-storage-js",
"authors": [
"AJ ONeal <coolaj86@gmail.com>"
],
"description": "A wrapper for storage engines which use the W3C Storage API",
"main": "json-storage.js",
"keywords": [
"dom",
"storage",
"json",
"w3c",
"localStorage",
"sessionStorage",
"globalStorage",
"Storage"
],
"license": "Apache2",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

View File

@ -1,282 +0,0 @@
(function () {
"use strict";
var localStorage = require('localStorage')
, JsonStorage = require('json-storage')
, jsonStorage = JsonStorage(localStorage)
, db = jsonStorage
, uuidgen = require('node-uuid')
, schemamap = {}
;
function create(schema) {
var models = {};
function createModel(lsmodel, prefix, submodels, uuid) {
//
// New Model
//
function LModel(obj) {
var lmodel = this;
// Add any already-appended submodels
// TODO DRY this up
submodels.forEach(function (subname) {
var sub_ids = '_' + subname + '_ids'
, subs = obj[subname]
, suuid = schemamap[subname].uuid || 'uuid';
// Just in case the object is already created
if ('function' === typeof obj.__lookupGetter__(subname)) {
return;
}
if (false === Array.isArray(subs)) {
return;
}
obj[sub_ids] = [];
lmodel[sub_ids] = obj[sub_ids];
subs.forEach(function (sub) {
if (!sub[suuid]) {
sub[suuid] = uuidgen();
}
models[subname].set(sub[suuid], sub);
lmodel[sub_ids].push(sub[suuid]);
});
obj[subname] = undefined;
delete obj[subname];
});
// Copy object to this
Object.keys(obj).forEach(function (k) {
lmodel[k] = obj[k];
});
// create uuid if it has none
if (undefined === obj[uuid]) {
obj[uuid] = uuidgen();
lmodel[uuid] = obj[uuid];
}
}
// A simple save method
LModel.prototype.save = function () {
lsmodel.set(this[uuid], this);
};
// getters for submodels
submodels.forEach(function (subname) {
var sub_ids = '_' + subname + '_ids';
LModel.prototype.__defineGetter__(subname, function () {
// not all submodels exist at "compile-time"
var submodel = models[subname]
, lmodel = this
, subs
, suuid = schemamap[subname].uuid || 'uuid';
lmodel[sub_ids] = lmodel[sub_ids] || [];
subs = submodel.some(lmodel[sub_ids]);
// modify collection, but leave as array with forEach
// XXX maybe create prototype with forEach
subs.add = function (obj) {
subs.push(obj);
if (undefined === obj[suuid]) {
submodel.add(obj);
} else {
submodel.set(obj[suuid], obj);
}
lmodel[sub_ids].push(obj[suuid]);
lmodel.save();
};
return subs;
});
});
return LModel;
}
// A database abstraction
function Model(model) {
var lsmodel = this
, prefix = model.name
, submodels = model.has_many || []
, uuid = model.uuid || 'uuid'
, LModel = createModel(lsmodel, prefix, submodels, uuid);
lsmodel.create = function (a, b, c) {
return new LModel(a, b, c)
};
// clear
lsmodel.clear = function () {
return db.set(prefix + '-lsm', []);
};
// all keys
lsmodel.keys = function () {
return db.get(prefix + '-lsm');
};
// all items
lsmodel.all = function () {
var items = [];
lsmodel.keys().forEach(function (uuid) {
var item = lsmodel.get(uuid)
// TODO this should be a useless check
if (null === item) {
lsmodel.remove(uuid);
return;
}
items.push(item);
});
return items;
};
// TODO query accepts objects
lsmodel.query = function (obj) {
//
return null;
};
// pass in your filter function
lsmodel.filter = function (test) {
var all = lsmodel.all(),
results = [];
all.forEach(function (one) {
if (!test(one)) {
return;
}
results.push(one);
});
return results;
}
lsmodel.some = function (uuids) {
var results = [];
uuids.forEach(function (uuid) {
var one = lsmodel.get(uuid);
// push null elements to keep array indices
results.push(one);
});
return results;
}
// TODO query object
lsmodel.get = function (uuid) {
var item = db.get(uuid);
if (null === item) {
return null;
}
return new LModel(item);
};
lsmodel.set = function (uuid, val) {
var keys;
if (null === val || undefined === val) {
return db.remove(uuid);
}
keys = db.get(prefix + '-lsm') || [];
// add the key if it didn't exist
if (-1 === keys.indexOf(uuid)) {
keys.push(uuid);
db.set(prefix + '-lsm', keys);
}
submodels.forEach(function (mod) {
var children = val[mod] || [];
// TODO decouple or create new objects
children.forEach(function (child) {
var e;
if (null === child) {
return;
}
if ('string' === typeof child) {
return;
}
if ('string' !== typeof child.uuid) {
return;
}
// TODO other[mod].set(uuid, child);
});
});
/*
console.log('\n\n val');
console.log(val);
console.log('\n\n');
*/
db.set(uuid, val)
};
// Remove an item from the table list and the db
lsmodel.remove = function (uuid) {
var keys = db.get(prefix + '-lsm')
, i;
keys.forEach(function (key, j) {
if (key === uuid) {
i = j;
}
});
if (undefined === i) {
return;
}
db.remove(uuid);
keys.splice(i,1);
db.set(prefix + '-lsm', keys);
};
// Remove all objects from the table list and db
lsmodel.clear = function () {
lsmodel.keys().forEach(function (uuid) {
db.remove(uuid);
});
db.set(prefix + '-lsm', []);
};
lsmodel.add = function (val) {
var obj = lsmodel.create(val);
obj.save();
return obj;
};
}
// TODO compare versions of schema
db.set('schema-lsm', schema);
schema.forEach(function (scheme) {
var table = scheme.name
, x = db.get(table + '-lsm')
;
// Pre-create the "models" tableshould always have
schemamap[scheme.name] = scheme;
if (!Array.isArray(x)) {
db.set(table + '-lsm', []);
}
models[scheme.name] = new Model(scheme);
});
return models;
}
module.exports = create;
}());

View File

@ -1,20 +0,0 @@
{
"author": "AJ ONeal <coolaj86@gmail.com> (http://coolaj86.info)",
"name": "json-storage-model",
"description": "An abstraction for models to be stored in json-storage",
"keywords": ["ender", "model", "json-storage", "localStorage", "sessionStorage", "globalStorage", "Storage"],
"version": "0.9.1",
"repository": {
"type": "git",
"url": "git://github.com/coolaj86/json-storage-js.git"
},
"engines": {
"node": ">= v0.2.0"
},
"main": "index",
"dependencies": {
"json-storage": ">= 1.0.0"
, "node-uuid": ">= 0.0.0"
},
"devDependencies": {}
}

View File

@ -1,247 +0,0 @@
(function () {
"use strict";
var Model = require('json-storage-model')
, assert = require('assert')
, schema
, lsmAll = {}
, Returns
, Batches;
schema = [
{
name: "returns",
has_many: ['batches'],
uuid: "key"
},
{
name: "batches",
has_many: ['products']
},
{
name: "products"
}
];
function setup() {
lsmAll = new Model(schema);
//console.log(lsmAll);
Returns = lsmAll.returns;
Batches = lsmAll.batches;
}
function empty() {
assert.deepEqual([], Returns.keys());
assert.deepEqual([], Returns.all());
assert.deepEqual([], Returns.some([]));
assert.deepEqual([null], Returns.some(['x']));
assert.deepEqual([null, null], Returns.some(['x','y']));
assert.deepEqual(null, Returns.get('nada'));
Returns.clear();
assert.deepEqual([], Returns.keys());
assert.deepEqual([], Returns.all());
assert.deepEqual([], Returns.some([]));
assert.deepEqual([null], Returns.some(['x']));
assert.deepEqual([null, null], Returns.some(['x','y']));
assert.deepEqual(null, Returns.get('nada'));
}
function errors() {
var errcount = 0;
// TODO make throw
try {
Returns.get();
} catch(e) {
errcount += 1;
}
//assert.equal(1, errcount);
console.log('skipped `throw error on get(undefined)`');
try {
Returns.some();
} catch(e) {
errcount += 1;
}
assert.equal(1, errcount);
Returns.some([]);
}
function createWithoutKey() {
var ret = Returns.create({
memo: "031811-IHC",
})
, ret2;
assert.ok(ret.key);
assert.equal("031811-IHC", ret.memo);
ret2 = Returns.get(ret.key);
assert.equal(null, ret2);
ret.test1 = true;
ret.save();
ret2 = Returns.get(ret.key);
assert.equal("031811-IHC", ret2.memo);
}
function createWithKey() {
var ret = Returns.create({
key: "ajs-test",
memo: "031811-IHC",
})
, ret2;
ret2 = Returns.get(ret.key);
assert.ok(!ret2);
assert.equal("ajs-test", ret.key);
ret.save();
ret2 = Returns.get(ret.key);
assert.ok(ret2.memo);
}
function insertRemove() {
var keys = []
, all = []
, ids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
, k;
Returns.clear();
// add
function populate() {
all = [];
keys = [];
ids.forEach(function (id, i) {
// this is private logic
all.push({ key: id, _batches_ids: [] });
keys.push(all[i].key);
Returns.create(all[i]).save();
});
assert.deepEqual(all, Returns.all());
assert.deepEqual(keys, Returns.keys());
}
// Remove from first
populate();
for (k = 0; k < ids.length; k += 1) {
Returns.remove(ids[k]);
}
assert.deepEqual([], Returns.all());
assert.deepEqual([], Returns.keys());
// Remove from last
populate();
for (k = ids.length - 1; k >= 0; k -= 1) {
Returns.remove(ids[k]);
}
assert.deepEqual([], Returns.all());
assert.deepEqual([], Returns.keys());
// Remove from middle
populate();
ids.sort(function () { return 0.5 - Math.random(); });
for (k = ids.length - 1; k >= 0; k -= 1) {
Returns.remove(ids[k]);
}
assert.deepEqual([], Returns.all());
assert.deepEqual([], Returns.keys());
}
// testing all, keys, some, filter
function all() {
var keys = [],
all = [],
some,
ids = [];
Returns.clear();
assert.deepEqual(all, Returns.all());
assert.deepEqual(keys, Returns.keys());
all.push({ key: "one", memo: "3131-mtn", _batches_ids: [] });
keys.push(all[0].key);
Returns.create(all[0]).save();
all.push({ key: "two", memo: "3232-hlt", _batches_ids: [] });
keys.push(all[1].key);
Returns.create(all[1]).save();
all.push({ key: "three", memo: "4123-hbc", _batches_ids: [] });
keys.push(all[2].key);
Returns.create(all[2]).save();
all.push({ key: "four", memo: "4441-dtf", _batches_ids: [] });
keys.push(all[3].key);
Returns.create(all[3]).save();
all.push({ key: "five", memo: "4412-abn", _batches_ids: [] });
keys.push(all[4].key);
Returns.create(all[4]).save();
some = Returns.filter(function (ret) {
return /^4\d/.test(ret.memo);
});
assert.equal(3, some.length);
some.forEach(function (one) {
ids.push(one.key);
});
assert.deepEqual(some, Returns.some(ids));
assert.deepEqual(all, Returns.all());
assert.deepEqual(keys, Returns.keys());
assert.deepEqual(all.slice(1,3), Returns.some(["two", "three"]));
assert.deepEqual(keys, Returns.keys());
console.log('skipping not-implemented `query`');
}
function relate() {
var batches = [
{
uuid: "a",
name: "chuck",
_products_ids: []
},
{
name: "daryl",
_products_ids: []
}
]
, ret = Returns.create({})
, batch;
// Add relation
ret.save();
ret = Returns.get(ret.key);
ret.batches.add(batches[0]);
assert.deepEqual(Batches.all()[0], batches[0]);
assert.deepEqual(ret.batches[0], batches[0]);
ret.save();
// create with an existing relation
ret = Returns.create({ batches: batches });
batches[1].uuid = ret.batches[1].uuid;
console.log('skipping assert which requires visual inspection');
//assert.deepEqual(batches, ret.batches);
//console.log(Batches.all());
}
setup();
empty();
errors();
createWithoutKey();
createWithKey();
insertRemove();
all();
relate();
console.log("All tests passed");
}());

140
json-storage.js Normal file
View File

@ -0,0 +1,140 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
var proto
, delim = ':'
;
function stringify(obj) {
var str;
try {
str = JSON.stringify(obj);
} catch(e) {
str = "";
}
return str;
}
function parse(str) {
var obj = null;
try {
obj = JSON.parse(str);
} catch(e) {}
return obj;
}
function JsonStorage(w3cStorage, namespace, opts) {
var me = this
;
if (!(this instanceof JsonStorage)) {
return new JsonStorage(w3cStorage, namespace, opts);
}
if (!w3cStorage) {
w3cStorage = window.localStorage;
} else if ('function' !== typeof w3cStorage.getItem) {
throw new Error('You must supply a W3C DOM Storage mechanism such as window.localStorage or window.sessionStorage');
}
me._opts = opts || {};
if (false === me._opts.stringify) {
me._stringify = false;
} else {
me._stringify = true;
}
// 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');
if (false === namespace) {
this._namespace = '';
}
this._store = w3cStorage;
this._keysAreDirty = true;
this._keys = [];
}
proto = JsonStorage.prototype;
proto.clear = function () {
this._keysAreDirty = true;
this.keys().forEach(function (key) {
this.remove(key);
}, this);
};
proto.remove = function (key) {
this._keysAreDirty = true;
this._store.removeItem(key + this._namespace);
};
proto.get = function (key) {
var item = this._store.getItem(key + this._namespace)
;
if ('undefined' === typeof item) {
item = null;
}
if (this._stringify) {
item = parse(item);
}
return item;
};
proto.set = function (key, val) {
this._keysAreDirty = true;
return this._store.setItem(key + this._namespace, this._stringify && stringify(val) || val);
};
proto.keys = function () {
var i
, key
, delimAt
;
if (!this._keysAreDirty) {
return this._keys.concat([]);
}
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 (!this._namespace || (-1 !== delimAt)) {
this._keys.push(key.substr(0, delimAt));
}
}
this._keysAreDirty = false;
return this._keys.concat([]);
};
proto.size = function () {
return this._store.length;
};
proto.toJSON = function () {
var json = {}
;
this.keys().forEach(function (key) {
json[key] = this.get(key);
}, this);
return json;
};
JsonStorage.create = JsonStorage;
exports.JsonStorage = JsonStorage;
}('undefined' !== typeof exports && exports || new Function('return this')()));

View File

@ -1,176 +0,0 @@
(function () {
"use strict";
var Future = require('future')
, __id_ = 'id'
, nameToString = function () {
return this.name;
};
function RowType(table) {
var name = table.name,
columns = table.columns,
klass;
klass = function (id, arr) {
/*
// Extending Array doesn't work very well
var self = this;
arr.forEach(function (item, i) {
self[i] = item;
});
*/
if (!(__id_ in this)) {
this[__id_] = id;
}
this.arr = arr;
};
// TODO make more directive-like
// TODO create a delegates_to directive
klass.prototype.toString = table.toString || nameToString;
columns.forEach(function (column, i) {
klass.prototype.__defineGetter__(column, function () {
return this.arr[i];
//return this[i];
});
});
return klass;
}
function createIndexes(tables) {
Object.keys(tables).forEach(function (name) {
var table = tables[name],
index,
krow;
if (!table.columns) {
console.log("missing columns");
console.log(table);
}
table.klass = RowType(table);
index = table.indexed = {};
table.rows.forEach(function (row, z) {
// TODO no need to index
var krow = new table.klass(z, row);
//krow.id = z;
index[krow[__id_]] = krow;
table.rows[z] = krow;
});
});
}
function createRelationships(tables) {
Object.keys(tables).forEach(function (name) {
var table = tables[name],
relationships,
relHandlers = {};
// TODO create a delegates_to directive
relHandlers.belongs_to = function (relname) {
// TODO abstract name mutation
var relation = tables[relname + 's'],
relname_s = relname + '_id';
table.klass.prototype.__defineGetter__(relname, function () {
var fid = this[relname_s];
return relation.indexed[fid];
});
}
//relationships = table.relationships && table.relationships.belongs_to;
//relationships = relationships || [];
relationships = table.relationships || {};
Object.keys(relationships).forEach(function (relType) {
if (!relHandlers[relType]) {
console.log('relationship type "' + relType + '" is not supported');
return;
}
relationships[relType].forEach(relHandlers[relType]);
});
});
}
function createRelate(tables) {
var future = Future()
;
createIndexes(tables);
createRelationships(tables);
//testRelationships(tables);
//console.log(Object.keys(data));
future.fulfill(null, tables);
return future.passable();
}
var Future = require('future')
, Join = require('Join')
;
function createGet(pathname, tables) {
var future = Future()
, join = Join()
, request = require('ahr2')
, db = {}
, errs = []
;
tables.forEach(function (tablename) {
var resource = pathname + '/' + tablename + ".json"
, req = request.get(resource)
;
req.when(function (err, xhr, data) {
if (err) {
console.log('error tables-get');
console.log(err);
console.log(err.message);
return;
}
if (global.Buffer && data instanceof global.Buffer) {
data = data.toString();
}
// TODO need to refactor AHR and fix JSON / text handling
try {
data = JSON.parse(data.toString());
} catch (e) {
// ignore, it was probably already parsed
}
// follow convention
tablename = tablename.replace('-','_');
db[tablename] = data;
});
join.add(req);
});
join.when(function () {
// TODO add timeout as error
if (0 == errs.length) {
errs = null;
}
future.fulfill(errs, db);
});
return future.passable();
}
module.exports = {
get: createGet
, relate: createRelate
};
}());

View File

@ -1,21 +0,0 @@
{
"author": "AJ ONeal <coolaj86@gmail.com> (http://coolaj86.info)"
, "name": "json-tables"
, "description": "An abstraction for models to be stored in json-storage"
, "keywords": ["ender", "orm", "sql", "model", "json-storage", "localStorage", "sessionStorage", "globalStorage", "Storage"]
, "version": "0.7.1"
, "repository": {
"type": "git"
, "url": "git://github.com/coolaj86/json-storage-js.git"
}
, "engines": {
"node": ">= v0.2.0"
}
, "main": "index"
, "dependencies": {
"json-storage-model": ">= 0.9.0"
, "json-storage": ">= 1.0.0"
, "node-uuid": ">= 0.0.0"
}
, "devDependencies": {}
}

View File

@ -1,86 +0,0 @@
JsonStorage
====
A light, sensible abstraction for DOMStorage (such as localStorage).
Installation
===
Ender.JS (Browser)
ender build json-storage
Node.JS (Server)
npm install localStorage json-storage
Usage
===
Made fo for Node.JS and Ender.JS (browser-side).
var localStorage = require('localStorage')
, JsonStorage = require('json-storage')
, store = JsonStorage.create(localStorage, 'my-widget-namespace')
, myValue = {
foo: "bar"
, baz: "quux"
}
;
store.set('myKey', myValue);
myValue = store.get('myKey');
API
===
* `JsonStorage.create(DOMStorage, namespace)`
* `DOMStorage` should be globalStorage, sessionStorage, or localStorage
* `namespace` is optional string which allows multiple non-conflicting storage containers
* `store.get(key)`
* `store.set(key, value)`
* `store.remove(key)`
* `store.clear()`
* `store.keys()`
* `store.size()`
* `store.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
===
These notes do not reflect a bugs or defects in this library,
they're simply to inform you of a few 'gotchas' inherent in JSON / DOMStorage conversion.
99.999% of the time these gotchas shouldn't effect you in any way.
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 store.
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`.
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.

View File

@ -1,151 +0,0 @@
(function () {
"use strict";
var Store
, delim = ':'
;
function Stringify(obj) {
var str;
try {
str = JSON.stringify(obj);
} catch(e) {
str = "";
}
return str;
}
function Parse(str) {
var obj = null;
try {
obj = JSON.parse(str);
} catch(e) {}
return obj;
}
function escapeRegExp(str) {
return str.replace(/[-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
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);
try {
val = JSON.parse(w3cs.getItem(key));
} catch(e) {
return;
}
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;
this._keys = [];
if (!this._store.getItem('_json-storage-namespaced_')) {
upgradeStorage(this, w3cStorage);
}
}
Store = JsonStorage;
Store.prototype.clear = function () {
this._keysAreDirty = true;
this.keys().forEach(function (key) {
this.remove(key);
}, this);
};
Store.prototype.remove = function (key) {
this._keysAreDirty = true;
this._store.removeItem(key + this._namespace);
};
Store.prototype.get = function (key) {
return Parse(this._store.getItem(key + this._namespace));
};
Store.prototype.set = function (key, val) {
this._keysAreDirty = true;
return this._store.setItem(key + this._namespace, Stringify(val));
};
Store.prototype.keys = function () {
var i
, key
, delimAt
;
if (!this._keysAreDirty) {
return this._keys.concat([]);
}
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;
return this._keys.concat([]);
};
Store.prototype.size = function () {
return this._store.length;
};
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 = Store;
}());

View File

@ -1,18 +0,0 @@
{
"author": "AJ ONeal <coolaj86@gmail.com> (http://coolaj86.info)",
"name": "json-storage",
"description": "A wrapper for storage engines which use the W3C Storage API",
"keywords": ["localStorage", "sessionStorage", "globalStorage", "Storage"],
"version": "1.1.3",
"repository": {
"type": "git",
"url": "git://github.com/coolaj86/json-storage-js.git"
},
"engines": {
"node": ">= v0.2.0"
},
"main": "index",
"browserDependencies": {},
"dependencies": {},
"devDependencies": {}
}

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"author": "AJ ONeal <coolaj86@gmail.com> (http://coolaj86.com)",
"name": "json-storage",
"description": "A wrapper for storage engines which use the W3C Storage API",
"keywords": ["dom", "storage", "json", "w3c", "localStorage", "sessionStorage", "globalStorage", "Storage"],
"version": "2.1.2",
"repository": {
"type": "git",
"url": "git://git.coolaj86.com/coolaj86/json-storage.js.git"
},
"engines": {
"node": ">= v0.2.0"
},
"main": "json-storage",
"browserDependencies": {},
"dependencies": {},
"devDependencies": {}
}

View File

@ -2,8 +2,8 @@
"use strict"; "use strict";
var localStorage = require('localStorage') var localStorage = require('localStorage')
, JsonStorage = require('json-storage') , JsonStorage = require('../lib/').JsonStorage
, db = JsonStorage(localStorage) , db = JsonStorage.create(localStorage)
, assert = require('assert') , assert = require('assert')
; ;

View File

@ -3,8 +3,8 @@
var assert = require('assert') var assert = require('assert')
, localStorage = require('localStorage') , localStorage = require('localStorage')
, JsonStorage = require('json-storage') , JsonStorage = require('../lib/').JsonStorage
, db = JsonStorage(localStorage) , db = JsonStorage.create(localStorage)
; ;
assert.strictEqual(null, db.get('a')); assert.strictEqual(null, db.get('a'));