├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── Runfile ├── gulpfile.js ├── localdb.js ├── localdb.min.js ├── localdb.min.js.map ├── package.json ├── src └── js │ └── localdb.js └── test ├── index.html └── tests.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "strict": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 27 | node_modules 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.0" 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LocalDB 2 | 3 | [![NPM version](https://img.shields.io/npm/v/localdb.svg?style=flat-square)](https://www.npmjs.com/package/localdb) 4 | [![NPM download](https://img.shields.io/npm/dm/localdb.svg?style=flat-square)](https://www.npmjs.com/package/localdb) 5 | [![Travis Build](https://img.shields.io/travis/egoist/localdb.svg?style=flat-square)](https://travis-ci.org/egoist/localdb) 6 | 7 | ## Example 8 | 9 | ```javascript 10 | import localdb from 'localdb' 11 | const Notes = new localdb('notes', 'Array', true) 12 | 13 | // insert some collections and return the collections 14 | let notes = Notes 15 | .add({title: 'Today is a big day', category: 'diary'}) 16 | .add({title: 'I met my ex today', category: 'diary'}) 17 | .add({title: 'Levandowski is amazing!', category: 'football'}) 18 | .get() 19 | 20 | // remove all post categoried in football 21 | Notes.remove('category', 'football') 22 | 23 | // find posts and update 24 | const query = {title: 'diary'} 25 | const opts = {limit: 2, sort: 1, sortBy: 'title', skip: 0} 26 | Notes.find(query, opts).forEach(note => { 27 | note.author = 'egoist' 28 | Notes.save(note) 29 | }) 30 | 31 | // override the whole database and generate meta 32 | Notes.override([{title: 'New post'}], true) 33 | 34 | // populate another class, eg: your Post have a Author field 35 | const Post = new localdb('Post', 'Array') 36 | const User = new localdb('User', 'Array') 37 | 38 | // you should have the Author's objectId to create an instance of that class 39 | const author = User.extend('some_object_id') 40 | 41 | Post.add({ 42 | title: 'mt post title', 43 | author: author 44 | }) 45 | 46 | // then you can populate that field before .find or .findOne 47 | Post.populate('author').findOne() 48 | 49 | // create an Object database and set some property 50 | const Site = new localdb('site', 'Object') 51 | Site.set('sitename', 'Google') 52 | 53 | // get sitename 54 | const sitename = Site.get('sitename') 55 | 56 | // destroy some database 57 | Site.destroy() 58 | ``` 59 | 60 | ## API 61 | 62 | ### new localdb(name:String, type = 'Array', timestamp = false) 63 | ### new localdb(opts:Object) 64 | 65 | 创建一个新的数据库,可选类型为 `Array`,`Object`,并赋值给变量 `db` 66 | 67 | ### db.add(obj:Object) 68 | 69 | 当类型为 `Array` 时可用,在数据库中增加一条集合 70 | 71 | ### db.get(where) 72 | 73 | `where` 为 `null` 时,返回数据库中的内容,返回类型为 `null` 或 `Object` 或 `Array` 74 | 75 | `where` 的类型为 `string` 或者 `number` 时返回对应的 `Object[key]` 或者 `Array[index]` 76 | 77 | ### db.findOne(query:Object) 78 | 79 | 查询并返回符合条件的一个集合 80 | 81 | ### db.find(query:Object, opts:Object) 82 | 83 | 查询并返回数个集合 84 | 85 | ```javascript 86 | var opts = { limit: 0, sortBy: 'index', sort: 1, skip: 0 } 87 | ``` 88 | 89 | ### db.save(obj:Oject) 90 | 91 | 当类型为 `Array` 时可用,obj 为 `.findOne` 或 `.find` 返回的结果,类型为 `Object`,你可以作出更改之后用 `.save` 更新到数据库 92 | 93 | ### db.set(key:String, value) 94 | 95 | 当类型为 `Object` 时可用,更新此数据库的一个键值对,没有则新建 96 | 97 | ### db.inc/dec(key, number) 98 | 99 | 当类型为 `Object` 时可用,让一个 key 的值自增/自减 number 个单位, key 值不存在时默认为 0 100 | 101 | ### db.remove(key:String, value) 102 | 103 | 当类型为 `Array` 按键值对删除对应的集合 104 | 当类型为 `Object` 时直接删除该 key 105 | 106 | ### db.extend(objectId) 107 | 108 | 创建一个该数据库的 `Pointer` 用于 `populate` 109 | 110 | ### db.populate(field) 111 | 112 | 在 `.find` 或 `.findOne` 时获取该 `field` 指向的 `collection` 113 | 114 | ### db.override(colleciton, reinit = false) 115 | 116 | 用 `collection` 整个覆盖旧的数据库 117 | 118 | `reinit` 为 `true` 将自动按照 `opts` 配置为每个 `Object` 生成 `_id` `createdAt` `updatedAt` 119 | 120 | ### db.destroy() 121 | 122 | 销毁数据库 123 | 124 | --- 125 | 126 | 相关文章: [一个简单的 localStorage 扩展实现](https://egoist.github.io/2015/09/30/a-light-weight-localstorage-orm/) 127 | 128 | ## License 129 | 130 | This project is released under SOX license that means you can do whatever you want to do, but you have to open source your copy on github if you let the public uses it. All copies should be released under the same license. The owner of each copy is only reponsible for his own copy, not for the parents, not for the children. 131 | 132 | permitted use: 133 | fork on github 134 | change 135 | do evil with your copy 136 | 137 | prohibted use: 138 | do evil with copies not of your own 139 | open source your copy without declaring your parent copy 140 | -------------------------------------------------------------------------------- /Runfile: -------------------------------------------------------------------------------- 1 | register('deploy', ['git', 'publish']) 2 | 3 | register('git', () => { 4 | var message = argv._[1] || 'update' 5 | exec('git add -A') 6 | exec(`git commit -m "${message}"`) 7 | exec('git push origin master') 8 | }) 9 | 10 | alias('publish', 'npm publish') 11 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp') 4 | var babel = require('gulp-babel') 5 | var serve = require('gulp-serve') 6 | var sourcemaps = require('gulp-sourcemaps') 7 | var uglify = require('gulp-uglify') 8 | var rename = require('gulp-rename') 9 | var qunit = require('gulp-qunit') 10 | 11 | gulp.task('serve', serve({ 12 | root: ['.'], 13 | port: 3001 14 | })) 15 | 16 | gulp.task('babel', function() { 17 | var stream = gulp.src('./src/js/localdb.js') 18 | .pipe(babel()) 19 | .pipe(gulp.dest(__dirname)); 20 | return stream; 21 | }) 22 | 23 | gulp.task('js', ['babel'], function() { 24 | var stream = gulp.src('./localdb.js') 25 | .pipe(sourcemaps.init()) 26 | .pipe(uglify()) 27 | .pipe(rename({ 28 | suffix: '.min' 29 | })) 30 | .pipe(sourcemaps.write('.')) 31 | .pipe(gulp.dest('.')); 32 | return stream; 33 | }) 34 | 35 | gulp.task('test', function() { 36 | gulp.src('./test/index.html') 37 | .pipe(qunit({ 38 | timeout: 20 39 | })) 40 | }) 41 | 42 | gulp.task('watch', function() { 43 | return gulp.watch('./src/js/*.js', ['js']) 44 | }) 45 | 46 | gulp.task('build', ['js']) 47 | 48 | gulp.task('default', ['build', 'serve', 'watch']) 49 | -------------------------------------------------------------------------------- /localdb.js: -------------------------------------------------------------------------------- 1 | /* 2 | * localdb 3 | * (c) 2015 4 | * github.com/egoist/localdb 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 10 | 11 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 12 | 13 | (function () { 14 | 15 | var defaultOpts = { limit: 0, sortBy: 'index', sort: 1, skip: 0 }; 16 | 17 | var definition = function definition(W, LS) { 18 | var LocalDB = (function () { 19 | function LocalDB(name) { 20 | var type = arguments.length <= 1 || arguments[1] === undefined ? 'Array' : arguments[1]; 21 | var timestamp = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 22 | 23 | _classCallCheck(this, LocalDB); 24 | 25 | if (typeof name === 'string') { 26 | this.db = name; 27 | this.type = type; 28 | this.timestamp = timestamp; 29 | } else if (typeof name === 'object') { 30 | var opts = name; 31 | this.db = opts.name; 32 | this.type = opts.type || 'Array'; 33 | this.timestamp = opts.timestamp || false; 34 | } 35 | this.populate_keys = null; 36 | } 37 | 38 | _createClass(LocalDB, [{ 39 | key: 'get', 40 | value: function get(where) { 41 | // where maps to key or index, depending on the type if Object or Array 42 | var collection = JSON.parse(LS.getItem(this.db)); 43 | if (!where && typeof where !== 'number') return collection;else if (typeof collection === 'undefined' || !collection) return null;else return collection[where]; 44 | } 45 | }, { 46 | key: 'findOne', 47 | value: function findOne(query) { 48 | return this.find(query, { 49 | limit: 1 50 | }); 51 | } 52 | }, { 53 | key: 'find', 54 | value: function find(query) { 55 | var _this = this; 56 | 57 | var opts = arguments.length <= 1 || arguments[1] === undefined ? defaultOpts : arguments[1]; 58 | 59 | if (this.type !== 'Array') { 60 | return console.error('The .findOne method only works if the database is an Array!'); 61 | } 62 | 63 | opts.limit = opts.limit || defaultOpts.limit; 64 | opts.sortBy = opts.sortBy || defaultOpts.sortBy; 65 | opts.sort = opts.sort || defaultOpts.sort; 66 | opts.skip = opts.skip || defaultOpts.skip; 67 | 68 | var collection = this.get() || []; 69 | if (query) { 70 | collection = collection.filter(function (obj) { 71 | var has = []; 72 | for (var key in query) { 73 | has.push(obj[key] === query[key]); 74 | } 75 | if (arrayEachTrue(has)) { 76 | return true; 77 | } else { 78 | return false; 79 | } 80 | }); 81 | } 82 | if (opts.limit === 1 && opts.skip === 0) { 83 | collection = collection[0]; 84 | } else { 85 | 86 | collection = collection.sort(function (a, b) { 87 | if (a[opts.sortBy] < b[opts.sortBy]) { 88 | return -opts.sort; 89 | } else if (a[opts.sortBy] > b[opts.sortBy]) { 90 | return opts.sort; 91 | } else { 92 | return 0; 93 | } 94 | }); 95 | 96 | if (opts.limit === 0) { 97 | collection = collection.slice(opts.skip); 98 | } else { 99 | collection = collection.slice(opts.skip, opts.limit + opts.skip); 100 | } 101 | } 102 | 103 | var populate = function populate(collection) { 104 | 105 | if (_this.populate_keys && _this.populate_keys.length > 0) _this.populate_keys.forEach(function (key) { 106 | var ref = collection[key]; 107 | if (ref) { 108 | var db = new LocalDB(ref.__className, 'Array'); 109 | var temp = db.findOne({ '_id': ref.objectId }); 110 | collection[key] = temp; 111 | } 112 | }); 113 | return collection; 114 | }; 115 | 116 | if (Array.isArray(collection)) { 117 | collection.forEach(function (c) { 118 | c = populate(c); 119 | }); 120 | } else if (typeof collection === 'object') { 121 | collection = populate(collection); 122 | } 123 | 124 | this.populate_keys = null; 125 | 126 | return collection; 127 | } 128 | }, { 129 | key: 'add', 130 | value: function add(obj) { 131 | if (this.type !== 'Array') { 132 | console.error('The .add method only works if the database is an Array!'); 133 | } 134 | var collection = this.get() || []; 135 | var index = 0; 136 | if (collection && collection.length > 0) { 137 | index = collection.length; 138 | } 139 | obj = this.initObj(index, obj); 140 | collection.push(obj); 141 | this.override(collection); 142 | return this; 143 | } 144 | }, { 145 | key: 'initObj', 146 | value: function initObj(index, obj) { 147 | obj.index = index; 148 | if (!obj._id) { 149 | obj._id = objectId(); 150 | } 151 | if (this.timestamp) { 152 | if (!obj.createdAt) { 153 | obj.createdAt = new Date(); 154 | } 155 | obj.updatedAt = new Date(); 156 | } 157 | return obj; 158 | } 159 | }, { 160 | key: 'set', 161 | value: function set(key, value) { 162 | // works if db is object 163 | if (this.type !== 'Object') { 164 | console.error('The .set method only works if the database is an Object!'); 165 | } else { 166 | var collection = this.get() || {}; 167 | collection[key] = value; 168 | this.override(collection); 169 | } 170 | return this; 171 | } 172 | }, { 173 | key: 'inc', 174 | value: function inc(key) { 175 | var value = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1]; 176 | 177 | return this.incOrDec('inc', key, value); 178 | } 179 | }, { 180 | key: 'dec', 181 | value: function dec(key) { 182 | var value = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1]; 183 | 184 | return this.incOrDec('dec', key, value); 185 | } 186 | }, { 187 | key: 'incOrDec', 188 | value: function incOrDec(type, key) { 189 | var value = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2]; 190 | 191 | if (this.type !== 'Object') { 192 | console.error('The .set method only works if the database is an Object!'); 193 | } else { 194 | var collection = this.get() || {}; 195 | if (type === 'inc') { 196 | collection[key] = (collection[key] || 0) + value; 197 | } else if (type === 'dec') { 198 | collection[key] = (collection[key] || 0) - value; 199 | } 200 | this.override(collection); 201 | } 202 | return this; 203 | } 204 | }, { 205 | key: 'save', 206 | value: function save(obj) { 207 | if (this.type !== 'Array') { 208 | console.error('The .save method only works if the database is an Array!'); 209 | } 210 | var collection = this.get(); 211 | if (collection[obj.index]._id === obj._id) { 212 | collection[obj.index] = obj; 213 | this.override(collection); 214 | } 215 | return this; 216 | } 217 | }, { 218 | key: 'extend', 219 | value: function extend(id) { 220 | if (!id) { 221 | return console.error('You should provide an objectId to reference'); 222 | } 223 | return { 224 | __type: 'Pointer', 225 | __className: this.db, 226 | objectId: id 227 | }; 228 | } 229 | }, { 230 | key: 'populate', 231 | value: function populate() { 232 | for (var _len = arguments.length, keys = Array(_len), _key = 0; _key < _len; _key++) { 233 | keys[_key] = arguments[_key]; 234 | } 235 | 236 | this.populate_keys = keys; 237 | return this; 238 | } 239 | }, { 240 | key: 'override', 241 | value: function override(collection) { 242 | var reinit = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 243 | 244 | if (this.type === 'Array' && reinit) { 245 | for (var i = 0; i < collection.length; i++) { 246 | collection[i] = this.initObj(i, collection[i]); 247 | } 248 | } 249 | LS.setItem(this.db, JSON.stringify(collection)); 250 | return this; 251 | } 252 | }, { 253 | key: 'remove', 254 | value: function remove(key, value) { 255 | var collection = this.get(); 256 | 257 | if (this.type === 'Array') { 258 | if (collection.length === 0) { 259 | return this; 260 | } 261 | collection = collection.filter(function (obj) { 262 | if (obj[key] === value) { 263 | return false; 264 | } else { 265 | return true; 266 | } 267 | }); 268 | for (var i = 0; i < collection.length; i++) { 269 | collection[i].index = i; 270 | } 271 | } else if (this.type === 'Object') { 272 | delete collection[key]; 273 | } 274 | 275 | LS.setItem(this.db, JSON.stringify(collection)); 276 | return this; 277 | } 278 | }, { 279 | key: 'destroy', 280 | value: function destroy() { 281 | LS.removeItem(this.db); 282 | return this; 283 | } 284 | }]); 285 | 286 | return LocalDB; 287 | })(); 288 | 289 | return LocalDB; 290 | }; 291 | 292 | function arrayEachTrue(array) { 293 | for (var i = 0; i < array.length; i++) { 294 | if (!array[i]) { 295 | return false; 296 | } 297 | } 298 | return true; 299 | } 300 | 301 | function objectId() { 302 | var timestamp = (new Date().getTime() / 1000 | 0).toString(16); 303 | return timestamp + 'xxxxxxxxxxxxxxxx'.replace(/[x]/g, function () { 304 | return (Math.random() * 16 | 0).toString(16); 305 | }).toLowerCase(); 306 | } 307 | 308 | ;(function (context, name, definition) { 309 | if (typeof module !== 'undefined') { 310 | module.exports = definition; 311 | } else if (typeof context !== 'undefined') { 312 | context[name] = definition; 313 | } 314 | })(window, 'localdb', definition(window, window.localStorage)); 315 | })(); -------------------------------------------------------------------------------- /localdb.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var _createClass=function(){function e(e,t){for(var r=0;rt[s.sortBy]?s.sort:0}),a=0===s.limit?a.slice(s.skip):a.slice(s.skip,s.limit+s.skip));var o=function(e){return i.populate_keys&&i.populate_keys.length>0&&i.populate_keys.forEach(function(t){var r=e[t];if(r){var i=new n(r.__className,"Array"),s=i.findOne({_id:r.objectId});e[t]=s}}),e};return Array.isArray(a)?a.forEach(function(e){e=o(e)}):"object"==typeof a&&(a=o(a)),this.populate_keys=null,a}},{key:"add",value:function(e){"Array"!==this.type&&console.error("The .add method only works if the database is an Array!");var t=this.get()||[],r=0;return t&&t.length>0&&(r=t.length),e=this.initObj(r,e),t.push(e),this.override(t),this}},{key:"initObj",value:function(e,r){return r.index=e,r._id||(r._id=t()),this.timestamp&&(r.createdAt||(r.createdAt=new Date),r.updatedAt=new Date),r}},{key:"set",value:function(e,t){if("Object"!==this.type)console.error("The .set method only works if the database is an Object!");else{var r=this.get()||{};r[e]=t,this.override(r)}return this}},{key:"inc",value:function(e){var t=arguments.length<=1||void 0===arguments[1]?1:arguments[1];return this.incOrDec("inc",e,t)}},{key:"dec",value:function(e){var t=arguments.length<=1||void 0===arguments[1]?1:arguments[1];return this.incOrDec("dec",e,t)}},{key:"incOrDec",value:function(e,t){var r=arguments.length<=2||void 0===arguments[2]?1:arguments[2];if("Object"!==this.type)console.error("The .set method only works if the database is an Object!");else{var n=this.get()||{};"inc"===e?n[t]=(n[t]||0)+r:"dec"===e&&(n[t]=(n[t]||0)-r),this.override(n)}return this}},{key:"save",value:function(e){"Array"!==this.type&&console.error("The .save method only works if the database is an Array!");var t=this.get();return t[e.index]._id===e._id&&(t[e.index]=e,this.override(t)),this}},{key:"extend",value:function(e){return e?{__type:"Pointer",__className:this.db,objectId:e}:console.error("You should provide an objectId to reference")}},{key:"populate",value:function(){for(var e=arguments.length,t=Array(e),r=0;e>r;r++)t[r]=arguments[r];return this.populate_keys=t,this}},{key:"override",value:function(e){var t=arguments.length<=1||void 0===arguments[1]?!1:arguments[1];if("Array"===this.type&&t)for(var r=0;r b[opts.sortBy]) {\n return opts.sort;\n } else {\n return 0;\n }\n });\n\n if (opts.limit === 0) {\n collection = collection.slice(opts.skip);\n } else {\n collection = collection.slice(opts.skip, opts.limit + opts.skip);\n }\n }\n\n var populate = function populate(collection) {\n\n if (_this.populate_keys && _this.populate_keys.length > 0) _this.populate_keys.forEach(function (key) {\n var ref = collection[key];\n if (ref) {\n var db = new LocalDB(ref.__className, 'Array');\n var temp = db.findOne({ '_id': ref.objectId });\n collection[key] = temp;\n }\n });\n return collection;\n };\n\n if (Array.isArray(collection)) {\n collection.forEach(function (c) {\n c = populate(c);\n });\n } else if (typeof collection === 'object') {\n collection = populate(collection);\n }\n\n this.populate_keys = null;\n\n return collection;\n }\n }, {\n key: 'add',\n value: function add(obj) {\n if (this.type !== 'Array') {\n console.error('The .add method only works if the database is an Array!');\n }\n var collection = this.get() || [];\n var index = 0;\n if (collection && collection.length > 0) {\n index = collection.length;\n }\n obj = this.initObj(index, obj);\n collection.push(obj);\n this.override(collection);\n return this;\n }\n }, {\n key: 'initObj',\n value: function initObj(index, obj) {\n obj.index = index;\n if (!obj._id) {\n obj._id = objectId();\n }\n if (this.timestamp) {\n if (!obj.createdAt) {\n obj.createdAt = new Date();\n }\n obj.updatedAt = new Date();\n }\n return obj;\n }\n }, {\n key: 'set',\n value: function set(key, value) {\n // works if db is object\n if (this.type !== 'Object') {\n console.error('The .set method only works if the database is an Object!');\n } else {\n var collection = this.get() || {};\n collection[key] = value;\n this.override(collection);\n }\n return this;\n }\n }, {\n key: 'inc',\n value: function inc(key) {\n var value = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1];\n\n return this.incOrDec('inc', key, value);\n }\n }, {\n key: 'dec',\n value: function dec(key) {\n var value = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1];\n\n return this.incOrDec('dec', key, value);\n }\n }, {\n key: 'incOrDec',\n value: function incOrDec(type, key) {\n var value = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2];\n\n if (this.type !== 'Object') {\n console.error('The .set method only works if the database is an Object!');\n } else {\n var collection = this.get() || {};\n if (type === 'inc') {\n collection[key] = (collection[key] || 0) + value;\n } else if (type === 'dec') {\n collection[key] = (collection[key] || 0) - value;\n }\n this.override(collection);\n }\n return this;\n }\n }, {\n key: 'save',\n value: function save(obj) {\n if (this.type !== 'Array') {\n console.error('The .save method only works if the database is an Array!');\n }\n var collection = this.get();\n if (collection[obj.index]._id === obj._id) {\n collection[obj.index] = obj;\n this.override(collection);\n }\n return this;\n }\n }, {\n key: 'extend',\n value: function extend(id) {\n if (!id) {\n return console.error('You should provide an objectId to reference');\n }\n return {\n __type: 'Pointer',\n __className: this.db,\n objectId: id\n };\n }\n }, {\n key: 'populate',\n value: function populate() {\n for (var _len = arguments.length, keys = Array(_len), _key = 0; _key < _len; _key++) {\n keys[_key] = arguments[_key];\n }\n\n this.populate_keys = keys;\n return this;\n }\n }, {\n key: 'override',\n value: function override(collection) {\n var reinit = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];\n\n if (this.type === 'Array' && reinit) {\n for (var i = 0; i < collection.length; i++) {\n collection[i] = this.initObj(i, collection[i]);\n }\n }\n LS.setItem(this.db, JSON.stringify(collection));\n return this;\n }\n }, {\n key: 'remove',\n value: function remove(key, value) {\n var collection = this.get();\n\n if (this.type === 'Array') {\n if (collection.length === 0) {\n return this;\n }\n collection = collection.filter(function (obj) {\n if (obj[key] === value) {\n return false;\n } else {\n return true;\n }\n });\n for (var i = 0; i < collection.length; i++) {\n collection[i].index = i;\n }\n } else if (this.type === 'Object') {\n delete collection[key];\n }\n\n LS.setItem(this.db, JSON.stringify(collection));\n return this;\n }\n }, {\n key: 'destroy',\n value: function destroy() {\n LS.removeItem(this.db);\n return this;\n }\n }]);\n\n return LocalDB;\n })();\n\n return LocalDB;\n };\n\n function arrayEachTrue(array) {\n for (var i = 0; i < array.length; i++) {\n if (!array[i]) {\n return false;\n }\n }\n return true;\n }\n\n function objectId() {\n var timestamp = (new Date().getTime() / 1000 | 0).toString(16);\n return timestamp + 'xxxxxxxxxxxxxxxx'.replace(/[x]/g, function () {\n return (Math.random() * 16 | 0).toString(16);\n }).toLowerCase();\n }\n\n ;(function (context, name, definition) {\n if (typeof module !== 'undefined') {\n module.exports = definition;\n } else if (typeof context !== 'undefined') {\n context[name] = definition;\n }\n })(window, 'localdb', definition(window, window.localStorage));\n})();"],"sourceRoot":"/source/"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "localdb", 3 | "version": "0.2.12", 4 | "description": "Better localStorage", 5 | "main": "localdb.js", 6 | "scripts": { 7 | "build": "gulp build", 8 | "test": "gulp test" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "gulp": "^3.9.0", 14 | "gulp-babel": "^5.2.1", 15 | "gulp-qunit": "^1.2.1", 16 | "gulp-rename": "^1.2.2", 17 | "gulp-serve": "^1.0.0", 18 | "gulp-sourcemaps": "^1.5.2", 19 | "gulp-uglify": "^1.4.0", 20 | "qunitjs": "^1.20.0" 21 | }, 22 | "standard": { 23 | "parser": "babel-eslint" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/js/localdb.js: -------------------------------------------------------------------------------- 1 | /* 2 | * localdb 3 | * (c) 2015 4 | * github.com/egoist/localdb 5 | */ 6 | 7 | () => { 8 | 9 | const defaultOpts = { limit: 0, sortBy: 'index', sort: 1, skip: 0 } 10 | 11 | let definition = (W, LS) => { 12 | class LocalDB { 13 | constructor (name, type = 'Array', timestamp = false) { 14 | if (typeof name === 'string') { 15 | this.db = name 16 | this.type = type 17 | this.timestamp = timestamp 18 | } else if (typeof name === 'object') { 19 | const opts = name 20 | this.db = opts.name 21 | this.type = opts.type || 'Array' 22 | this.timestamp = opts.timestamp || false 23 | } 24 | this.populate_keys = null 25 | } 26 | 27 | get (where) { 28 | // where maps to key or index, depending on the type if Object or Array 29 | const collection = JSON.parse(LS.getItem(this.db)) 30 | if (!where && typeof where !== 'number') 31 | return collection 32 | else if (typeof collection === 'undefined' || !collection) 33 | return null 34 | else 35 | return collection[where] 36 | } 37 | 38 | findOne (query) { 39 | return this.find(query, { 40 | limit: 1 41 | }) 42 | } 43 | 44 | find (query, opts = defaultOpts) { 45 | if (this.type !== 'Array') { 46 | return console.error('The .findOne method only works if the database is an Array!') 47 | } 48 | 49 | opts.limit = opts.limit || defaultOpts.limit 50 | opts.sortBy = opts.sortBy || defaultOpts.sortBy 51 | opts.sort = opts.sort || defaultOpts.sort 52 | opts.skip = opts.skip || defaultOpts.skip 53 | 54 | let collection = this.get() || [] 55 | if (query) { 56 | collection = collection.filter((obj) => { 57 | let has = [] 58 | for (var key in query) { 59 | has.push(obj[key] === query[key]) 60 | } 61 | if (arrayEachTrue(has)) { 62 | return true 63 | } else { 64 | return false 65 | } 66 | }) 67 | } 68 | if (opts.limit === 1 && opts.skip === 0) { 69 | collection = collection[0] 70 | } else { 71 | 72 | collection = collection.sort((a, b) => { 73 | if (a[opts.sortBy] < b[opts.sortBy]) { 74 | return -opts.sort 75 | } else if (a[opts.sortBy] > b[opts.sortBy]) { 76 | return opts.sort 77 | } else { 78 | return 0 79 | } 80 | }) 81 | 82 | if (opts.limit === 0) { 83 | collection = collection.slice(opts.skip) 84 | } else { 85 | collection = collection.slice(opts.skip, opts.limit + opts.skip) 86 | } 87 | } 88 | 89 | let populate = (collection) => { 90 | 91 | if (this.populate_keys && this.populate_keys.length > 0) 92 | this.populate_keys.forEach(key => { 93 | const ref = collection[key] 94 | if (ref) { 95 | const db = new LocalDB(ref.__className, 'Array') 96 | const temp = db.findOne({'_id': ref.objectId}) 97 | collection[key] = temp 98 | 99 | } 100 | }) 101 | return collection 102 | } 103 | 104 | if (Array.isArray(collection)) { 105 | collection.forEach(c => { 106 | c = populate(c) 107 | }) 108 | } else if (typeof collection === 'object') { 109 | collection = populate(collection) 110 | } 111 | 112 | 113 | this.populate_keys = null 114 | 115 | return collection 116 | } 117 | 118 | add (obj) { 119 | if (this.type !== 'Array') { 120 | console.error('The .add method only works if the database is an Array!') 121 | } 122 | let collection = this.get() || [] 123 | let index = 0 124 | if (collection && collection.length > 0) { 125 | index = collection.length 126 | } 127 | obj = this.initObj(index, obj) 128 | collection.push(obj) 129 | this.override(collection) 130 | return this 131 | } 132 | 133 | initObj (index, obj) { 134 | obj.index = index 135 | if (!obj._id) { 136 | obj._id = objectId() 137 | } 138 | if (this.timestamp) { 139 | if (!obj.createdAt) { 140 | obj.createdAt = new Date() 141 | } 142 | obj.updatedAt = new Date() 143 | } 144 | return obj 145 | } 146 | 147 | set (key, value) { 148 | // works if db is object 149 | if (this.type !== 'Object') { 150 | console.error('The .set method only works if the database is an Object!') 151 | } else { 152 | let collection = this.get() || {} 153 | collection[key] = value 154 | this.override(collection) 155 | } 156 | return this 157 | } 158 | 159 | inc (key, value = 1) { 160 | return this.incOrDec('inc', key, value) 161 | } 162 | 163 | dec (key, value = 1) { 164 | return this.incOrDec('dec', key, value) 165 | } 166 | 167 | incOrDec (type, key, value = 1) { 168 | if (this.type !== 'Object') { 169 | console.error('The .set method only works if the database is an Object!') 170 | } else { 171 | let collection = this.get() || {} 172 | if (type === 'inc') { 173 | collection[key] = (collection[key] || 0) + value 174 | } else if (type === 'dec') { 175 | collection[key] = (collection[key] || 0) - value 176 | } 177 | this.override(collection) 178 | } 179 | return this 180 | } 181 | 182 | save (obj) { 183 | if (this.type !== 'Array') { 184 | console.error('The .save method only works if the database is an Array!') 185 | } 186 | let collection = this.get() 187 | if (collection[obj.index]._id === obj._id) { 188 | collection[obj.index] = obj 189 | this.override(collection) 190 | } 191 | return this 192 | } 193 | 194 | extend (id) { 195 | if (!id) { 196 | return console.error('You should provide an objectId to reference') 197 | } 198 | return { 199 | __type: 'Pointer', 200 | __className: this.db, 201 | objectId: id 202 | } 203 | } 204 | 205 | populate (...keys) { 206 | this.populate_keys = keys 207 | return this 208 | } 209 | 210 | override (collection, reinit = false) { 211 | if (this.type === 'Array' && reinit) { 212 | for (var i = 0; i < collection.length; i++) { 213 | collection[i] = this.initObj(i, collection[i]) 214 | } 215 | } 216 | LS.setItem(this.db, JSON.stringify(collection)) 217 | return this 218 | } 219 | 220 | remove (key, value) { 221 | let collection = this.get() 222 | 223 | if (this.type === 'Array') { 224 | if (collection.length === 0) { 225 | return this 226 | } 227 | collection = collection.filter(obj => { 228 | if (obj[key] === value) { 229 | return false 230 | } else { 231 | return true 232 | } 233 | }) 234 | for (var i = 0; i < collection.length; i++) { 235 | collection[i].index = i 236 | } 237 | } else if (this.type === 'Object') { 238 | delete collection[key] 239 | } 240 | 241 | LS.setItem(this.db, JSON.stringify(collection)) 242 | return this 243 | } 244 | 245 | destroy () { 246 | LS.removeItem(this.db) 247 | return this 248 | } 249 | 250 | } 251 | 252 | return LocalDB 253 | } 254 | 255 | function arrayEachTrue (array) { 256 | for (var i = 0; i < array.length; i++) { 257 | if (!array[i]) { 258 | return false 259 | } 260 | } 261 | return true 262 | } 263 | 264 | function objectId () { 265 | const timestamp = (new Date().getTime() / 1000 | 0).toString(16) 266 | return timestamp + 'xxxxxxxxxxxxxxxx'.replace(/[x]/g, () => (Math.random() * 16 | 0).toString(16)).toLowerCase() 267 | } 268 | 269 | ;(context, name, definition) => { 270 | if (typeof module !== 'undefined') { 271 | module.exports = definition 272 | } else if (typeof context !== 'undefined') { 273 | context[name] = definition 274 | } 275 | }(window, 'localdb', definition(window, window.localStorage)) 276 | 277 | }() 278 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tests 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | var booksdb = new localdb('books', 'Array') 2 | var config = new localdb('config', 'Object') 3 | var User = new localdb({ 4 | name: 'User', 5 | type: 'Array', 6 | timestamp: true 7 | }) 8 | 9 | test('localdb is a function', function () { 10 | ok(typeof localdb == 'function') 11 | }) 12 | 13 | test('add a book and return books', function () { 14 | var books = initBooks() 15 | ok(books instanceof Array) 16 | 17 | }) 18 | 19 | test('inc and dec', function () { 20 | ok(config.inc('count').get('count') === 1) 21 | ok(config.dec('count').get('count') === 0) 22 | ok(config.inc('count', 2).get('count') === 2) 23 | }) 24 | 25 | test('remove a book and return books', function () { 26 | var books = initBooks() 27 | var updated = booksdb.remove('title', 'Diao').get() 28 | ok(updated.length == books.length - 1) 29 | }) 30 | 31 | test('find a book in books', function () { 32 | initBooks() 33 | var book = booksdb.findOne({title: 'Diao', author: 'SOX'}) 34 | ok(book.title === 'Diao' && book.author === 'SOX') 35 | }) 36 | 37 | test('desending all books by index', function () { 38 | initBooks() 39 | var books = booksdb.find(null, {sort: -1}) 40 | ok(books[0].title === '大大泡泡堂') 41 | }) 42 | 43 | test('update a book', function () { 44 | initBooks() 45 | var book = booksdb.findOne({title: 'Diao'}) 46 | book.title = 'Love' 47 | booksdb.save(book) 48 | book = booksdb.findOne({title: 'Love'}) 49 | ok(book.title === 'Love') 50 | }) 51 | 52 | test('update books', function () { 53 | initBooks() 54 | var books = booksdb.find() 55 | books.forEach(function (b) { 56 | b.what = 'ever' 57 | booksdb.save(b) 58 | }) 59 | var result = true 60 | booksdb.find().forEach(function (b) { 61 | if (b.what !== 'ever') { 62 | result = false 63 | return 64 | } 65 | }) 66 | ok(result) 67 | }) 68 | 69 | test('extend and populate', function () { 70 | 71 | var user_id = initUsers()[0]._id 72 | var Music = new localdb('music', 'Array', true) 73 | Music.destroy() 74 | var user = User.extend(user_id) 75 | ok(user.__type) 76 | 77 | var music = { 78 | name: 'searet base', 79 | singer: 'zone', 80 | user: user 81 | } 82 | var m = Music.add(music).add(music).populate('user').find() 83 | console.log(m) 84 | }) 85 | 86 | test('remove a key', function () { 87 | var site = config.set('date', Date.now()).set('owner', 'God').get() 88 | var existed = (typeof site.owner === 'string') ? true : false 89 | site = config.remove('owner').get() 90 | var existing = (typeof site.owner === 'string') ? true : false 91 | ok(existed && !existing) 92 | }) 93 | 94 | test('update an Object db', function () { 95 | var site = config.set('sitename', 'Google').get() 96 | ok(site.sitename === 'Google') 97 | }) 98 | 99 | test('add a user with auto timestamp', function () { 100 | initUsers() 101 | var user = User.findOne({username: 'kevin'}) 102 | ok(user.createdAt) 103 | }) 104 | 105 | test('skip and limit when finding user', function () { 106 | initUsers() 107 | var user = User.find(null, {skip: 1, limit: 1}) 108 | ok(user[0].username === 'joe') 109 | }) 110 | 111 | test('get some key/index in database', function () { 112 | var sitename = config.set('sitename', 'Google').get('sitename') 113 | ok(sitename === 'Google') 114 | 115 | initUsers() 116 | var username = User.get(1).username 117 | ok(username === 'joe') 118 | }) 119 | 120 | test('override with Array', function () { 121 | var user = User.override([{ 122 | username: 'foo' 123 | }, { 124 | username: 'bar' 125 | }], true).findOne() 126 | ok(user._id && user.createdAt && user.updatedAt) 127 | }) 128 | 129 | test('destroy db and fetch unexisting element', function () { 130 | var db = booksdb.destroy().get() 131 | ok(booksdb.get(0) === null) 132 | ok(db === null) 133 | }) 134 | 135 | function initBooks () { 136 | booksdb.destroy() 137 | return booksdb.add({ 138 | title: 'Gone with wind', 139 | author: 'Kinpika', 140 | year: 2013 141 | }).add({ 142 | title: 'Diao', 143 | author: 'SOX', 144 | year: 2013 145 | }).add({ 146 | title: '大大泡泡堂', 147 | author: 'SOX', 148 | year: 2013 149 | }).get() 150 | } 151 | 152 | function initUsers () { 153 | User.destroy() 154 | User.add({ 155 | username: 'kevin', 156 | birth: '1994' 157 | }).add({ 158 | username: 'joe', 159 | age: 10 160 | }).add({ 161 | username: 'daniel', 162 | age: 19, 163 | weight: 50 164 | }) 165 | 166 | return User.get() 167 | } 168 | --------------------------------------------------------------------------------