├── .gitattributes ├── .gitignore ├── .travis.yml ├── .editorconfig ├── gulpfile.js ├── utils.js ├── LICENSE ├── index.js ├── package.json ├── .verb.md ├── .eslintrc.json ├── README.md └── test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | * text eol=lf 3 | 4 | # binaries 5 | *.ai binary 6 | *.psd binary 7 | *.jpg binary 8 | *.gif binary 9 | *.png binary 10 | *.jpeg binary 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.sublime-* 3 | _gh_pages 4 | bower_components 5 | node_modules 6 | npm-debug.log 7 | actual 8 | test/actual 9 | temp 10 | tmp 11 | TODO.md 12 | vendor 13 | .idea 14 | benchmark 15 | coverage 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | - "5" 6 | - "4" 7 | - "0.12" 8 | - "0.10" 9 | matrix: 10 | fast_finish: true 11 | allow_failures: 12 | - node_js: "0.10" 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | insert_final_newline = false 15 | 16 | [test/**] 17 | trim_trailing_whitespace = false 18 | insert_final_newline = false 19 | 20 | [templates/**] 21 | trim_trailing_whitespace = false 22 | insert_final_newline = false 23 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var mocha = require('gulp-mocha'); 5 | var istanbul = require('gulp-istanbul'); 6 | var eslint = require('gulp-eslint'); 7 | 8 | var lint = ['index.js', 'utils.js']; 9 | 10 | gulp.task('coverage', function() { 11 | return gulp.src(lint) 12 | .pipe(istanbul()) 13 | .pipe(istanbul.hookRequire()); 14 | }); 15 | 16 | gulp.task('mocha', ['coverage'], function() { 17 | return gulp.src('test.js') 18 | .pipe(mocha({reporter: 'spec'})) 19 | .pipe(istanbul.writeReports()); 20 | }); 21 | 22 | gulp.task('eslint', function() { 23 | return gulp.src(lint.concat('test.js')) 24 | .pipe(eslint()) 25 | }); 26 | 27 | gulp.task('default', ['mocha', 'eslint']); 28 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var debug = require('debug')('base:store'); 4 | var utils = require('lazy-cache')(require); 5 | var fn = require; 6 | require = utils; 7 | 8 | /** 9 | * Lazily required module dependencies 10 | */ 11 | 12 | require('data-store', 'store'); 13 | require('extend-shallow', 'extend'); 14 | require('is-registered'); 15 | require('is-valid-instance'); 16 | require('project-name', 'project'); 17 | require = fn; 18 | 19 | /** 20 | * Return true if `app` is a valid `Base` instance and base-store is not 21 | * already registered. 22 | */ 23 | 24 | utils.isValid = function(app) { 25 | if (!utils.isValidInstance(app)) { 26 | return false; 27 | } 28 | if (utils.isRegistered(app, 'base-store')) { 29 | return false; 30 | } 31 | debug('initializing <%s>, from <%s>', __filename, module.parent.id); 32 | return true; 33 | }; 34 | 35 | /** 36 | * Expose `utils` modules 37 | */ 38 | 39 | module.exports = utils; 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016, Jon Schlinkert. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * base-store 3 | * 4 | * Copyright (c) 2015-2016, Jon Schlinkert. 5 | * Licensed under the MIT License. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var utils = require('./utils'); 11 | 12 | module.exports = function(name, config) { 13 | if (typeof name !== 'string') { 14 | config = name; 15 | name = undefined; 16 | } 17 | 18 | return function plugin(app) { 19 | if (!utils.isValid(app)) return; 20 | 21 | // if `name` is not passed, get the project name 22 | if (typeof name === 'undefined') { 23 | name = utils.project(process.cwd()); 24 | } 25 | 26 | var opts = utils.extend({}, config, app.options.store); 27 | this.define('store', utils.store(name, opts)); 28 | 29 | /** 30 | * Bubble up a few events to `app` 31 | */ 32 | 33 | this.store.on('set', function(key, val) { 34 | app.emit('store.set', key, val); 35 | app.emit('store', 'set', key, val); 36 | }); 37 | 38 | this.store.on('get', function(key, val) { 39 | app.emit('store.get', key, val); 40 | app.emit('store', 'get', key, val); 41 | }); 42 | 43 | this.store.on('del', function(key, val) { 44 | app.emit('store.del', key, val); 45 | app.emit('store', 'del', key, val); 46 | }); 47 | 48 | return plugin; 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base-store", 3 | "description": "Plugin for getting and persisting config values with your base-methods application. Adds a 'store' object that exposes all of the methods from the data-store library. Also now supports sub-stores!", 4 | "version": "0.4.4", 5 | "homepage": "https://github.com/node-base/base-store", 6 | "author": "Jon Schlinkert (https://github.com/jonschlinkert)", 7 | "repository": "node-base/base-store", 8 | "bugs": { 9 | "url": "https://github.com/node-base/base-store/issues" 10 | }, 11 | "license": "MIT", 12 | "files": [ 13 | "index.js", 14 | "utils.js" 15 | ], 16 | "main": "index.js", 17 | "engines": { 18 | "node": ">=0.10.0" 19 | }, 20 | "scripts": { 21 | "test": "mocha" 22 | }, 23 | "dependencies": { 24 | "data-store": "^0.16.0", 25 | "debug": "^2.2.0", 26 | "extend-shallow": "^2.0.1", 27 | "is-registered": "^0.1.4", 28 | "is-valid-instance": "^0.1.0", 29 | "lazy-cache": "^2.0.1", 30 | "project-name": "^0.2.5" 31 | }, 32 | "devDependencies": { 33 | "base": "^0.8.1", 34 | "gulp": "^3.9.1", 35 | "gulp-eslint": "^2.0.0", 36 | "gulp-format-md": "^0.1.9", 37 | "gulp-istanbul": "^0.10.4", 38 | "gulp-mocha": "^2.2.0", 39 | "mocha": "^2.4.5", 40 | "should": "^8.3.1" 41 | }, 42 | "keywords": [ 43 | "app", 44 | "base", 45 | "baseplugin", 46 | "cache", 47 | "config", 48 | "data", 49 | "extend", 50 | "merge", 51 | "method", 52 | "plugin", 53 | "store" 54 | ], 55 | "verb": { 56 | "run": true, 57 | "toc": false, 58 | "layout": "default", 59 | "tasks": [ 60 | "readme" 61 | ], 62 | "plugins": [ 63 | "gulp-format-md" 64 | ], 65 | "related": { 66 | "highlight": "base-data", 67 | "description": "Other plugins for extending your [base][] application:", 68 | "list": [ 69 | "base", 70 | "base-options", 71 | "base-questions", 72 | "base-pipeline", 73 | "base-plugins" 74 | ] 75 | }, 76 | "reflinks": [ 77 | "base", 78 | "verb" 79 | ], 80 | "lint": { 81 | "reflinks": true 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /.verb.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | Adds `store` methods for doing things like this: 4 | 5 | ```js 6 | app.store.set('a', 'z'); // DOES persist 7 | console.log(app.store.get('a')); 8 | //=> 'z'; 9 | ``` 10 | 11 | ## API 12 | 13 | Add a `.store` method to your [base][] application: 14 | 15 | ```js 16 | var store = require('{%= name %}'); 17 | var Base = require('base'); 18 | var base = new Base(); 19 | 20 | // store `name` is required 21 | base.use(store('foo')); 22 | 23 | // optionally define a cwd to use for persisting the store 24 | // default cwd is `~/data-store/` 25 | base.use(store('foo', {cwd: 'a/b/c'})); 26 | ``` 27 | 28 | **example usage** 29 | 30 | ```js 31 | base.store 32 | .set('a', 'b') 33 | .set({c: 'd'}) 34 | .set('e.f', 'g') 35 | 36 | console.log(base.store.get('e.f')); 37 | //=> 'g' 38 | 39 | console.log(base.store.data); 40 | //=> {a: 'b', c: 'd', e: {f: 'g'}} 41 | ``` 42 | 43 | ## Sub-stores 44 | 45 | A sub-store is a custom store that is persisted to its own file in a sub-folder of its "parent" store. 46 | 47 | **Create a sub-store** 48 | 49 | ```js 50 | app.store.create('foo'); 51 | // creates an instance of store on `app.store.foo` 52 | 53 | app.store.foo.set('a', 'b'); 54 | app.store.foo.get('a'); 55 | //=> 'b' 56 | ``` 57 | 58 | Sub-store data is also persisted to a property on the "parent" store: 59 | 60 | ```js 61 | // set data on a sub-store 62 | app.store.foo.set('a', 'b'); 63 | 64 | // get the value from parent store 65 | app.store.get('foo.a'); 66 | //=> 'b' 67 | ``` 68 | 69 | 70 | ### plugin params 71 | 72 | * `name` **{String}**: Store name. 73 | * `options` **{Object}** 74 | 75 | - `cwd` **{String}**: Current working directory for storage. If not defined, the user home directory is used, based on OS. This is the only option currently, other may be added in the future. 76 | - `indent` **{Number}**: Number passed to `JSON.stringify` when saving the data. Defaults to `2` if `null` or `undefined` 77 | 78 | 79 | ## methods 80 | 81 | ### .store.set 82 | 83 | Assign `value` to `key` and save to disk. Can be a key-value pair or an object. 84 | 85 | **Params** 86 | 87 | * `key` **{String}** 88 | * `val` **{any}**: The value to save to `key`. Must be a valid JSON type: String, Number, Array or Object. 89 | * `returns` **{Object}** `Store`: for chaining 90 | 91 | **Example** 92 | 93 | ```js 94 | // key, value 95 | base.store.set('a', 'b'); 96 | //=> {a: 'b'} 97 | 98 | // extend the store with an object 99 | base.store.set({a: 'b'}); 100 | //=> {a: 'b'} 101 | 102 | // extend the the given value 103 | base.store.set('a', {b: 'c'}); 104 | base.store.set('a', {d: 'e'}, true); 105 | //=> {a: {b 'c', d: 'e'}} 106 | 107 | // overwrite the the given value 108 | base.store.set('a', {b: 'c'}); 109 | base.store.set('a', {d: 'e'}); 110 | //=> {d: 'e'} 111 | ``` 112 | 113 | ### .store.union 114 | 115 | Add or append an array of unique values to the given `key`. 116 | 117 | **Params** 118 | 119 | * `key` **{String}** 120 | * `returns` **{any}**: The array to add or append for `key`. 121 | 122 | **Example** 123 | 124 | ```js 125 | base.store.union('a', ['a']); 126 | base.store.union('a', ['b']); 127 | base.store.union('a', ['c']); 128 | base.store.get('a'); 129 | //=> ['a', 'b', 'c'] 130 | ``` 131 | 132 | ### .store.get 133 | 134 | Get the stored `value` of `key`, or return the entire store if no `key` is defined. 135 | 136 | **Params** 137 | 138 | * `key` **{String}** 139 | * `returns` **{any}**: The value to store for `key`. 140 | 141 | **Example** 142 | 143 | ```js 144 | base.store.set('a', {b: 'c'}); 145 | base.store.get('a'); 146 | //=> {b: 'c'} 147 | 148 | base.store.get(); 149 | //=> {b: 'c'} 150 | ``` 151 | 152 | ### .store.has 153 | 154 | Returns `true` if the specified `key` has truthy value. 155 | 156 | **Params** 157 | 158 | * `key` **{String}** 159 | * `returns` **{Boolean}**: Returns true if `key` has 160 | 161 | **Example** 162 | 163 | ```js 164 | base.store.set('a', 'b'); 165 | base.store.set('c', null); 166 | base.store.has('a'); //=> true 167 | base.store.has('c'); //=> false 168 | base.store.has('d'); //=> false 169 | ``` 170 | 171 | ### .store.hasOwn 172 | 173 | Returns `true` if the specified `key` exists. 174 | 175 | **Params** 176 | 177 | * `key` **{String}** 178 | * `returns` **{Boolean}**: Returns true if `key` exists 179 | 180 | **Example** 181 | 182 | ```js 183 | base.store.set('a', 'b'); 184 | base.store.set('b', false); 185 | base.store.set('c', null); 186 | base.store.set('d', true); 187 | 188 | base.store.hasOwn('a'); //=> true 189 | base.store.hasOwn('b'); //=> true 190 | base.store.hasOwn('c'); //=> true 191 | base.store.hasOwn('d'); //=> true 192 | base.store.hasOwn('foo'); //=> false 193 | ``` 194 | 195 | ### .store.save 196 | 197 | Persist the store to disk. 198 | 199 | **Params** 200 | 201 | * `dest` **{String}**: Optionally define a different destination than the default path. 202 | 203 | **Example** 204 | 205 | ```js 206 | base.store.save(); 207 | ``` 208 | 209 | ### .store.del 210 | 211 | Delete `keys` from the store, or delete the entire store if no keys are passed. A `del` event is also emitted for each key deleted. 212 | 213 | **Note that to delete the entire store you must pass `{force: true}`** 214 | 215 | **Params** 216 | 217 | * `keys` **{String|Array|Object}**: Keys to remove, or options. 218 | * `options` **{Object}** 219 | 220 | **Example** 221 | 222 | ```js 223 | base.store.del(); 224 | 225 | // to delete paths outside cwd 226 | base.store.del({force: true}); 227 | ``` 228 | 229 | ## History 230 | 231 | **v0.3.1** 232 | 233 | - Sub-stores are easier to create and get. You can now do `app.store.create('foo')` to create a sub-store, which is then available as `app.store.foo`. 234 | 235 | **v0.3.0** 236 | 237 | - Introducing sub-stores! -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "modules": true, 4 | "experimentalObjectRestSpread": true 5 | }, 6 | "env": { 7 | "browser": false, 8 | "es6": true, 9 | "node": true, 10 | "mocha": true 11 | }, 12 | "globals": { 13 | "document": false, 14 | "navigator": false, 15 | "window": false 16 | }, 17 | "rules": { 18 | "accessor-pairs": 2, 19 | "arrow-spacing": [ 20 | 2, 21 | { 22 | "before": true, 23 | "after": true 24 | } 25 | ], 26 | "block-spacing": [ 27 | 2, 28 | "always" 29 | ], 30 | "brace-style": [ 31 | 2, 32 | "1tbs", 33 | { 34 | "allowSingleLine": true 35 | } 36 | ], 37 | "comma-dangle": [ 38 | 2, 39 | "never" 40 | ], 41 | "comma-spacing": [ 42 | 2, 43 | { 44 | "before": false, 45 | "after": true 46 | } 47 | ], 48 | "comma-style": [ 49 | 2, 50 | "last" 51 | ], 52 | "constructor-super": 2, 53 | "curly": [ 54 | 2, 55 | "multi-line" 56 | ], 57 | "dot-location": [ 58 | 2, 59 | "property" 60 | ], 61 | "eol-last": 2, 62 | "eqeqeq": [ 63 | 2, 64 | "allow-null" 65 | ], 66 | "generator-star-spacing": [ 67 | 2, 68 | { 69 | "before": true, 70 | "after": true 71 | } 72 | ], 73 | "handle-callback-err": [ 74 | 2, 75 | "^(err|error)$" 76 | ], 77 | "indent": [ 78 | 2, 79 | 2, 80 | { 81 | "SwitchCase": 1 82 | } 83 | ], 84 | "key-spacing": [ 85 | 2, 86 | { 87 | "beforeColon": false, 88 | "afterColon": true 89 | } 90 | ], 91 | "keyword-spacing": [ 92 | 2, 93 | { 94 | "before": true, 95 | "after": true 96 | } 97 | ], 98 | "new-cap": [ 99 | 2, 100 | { 101 | "newIsCap": true, 102 | "capIsNew": false 103 | } 104 | ], 105 | "new-parens": 2, 106 | "no-array-constructor": 2, 107 | "no-caller": 2, 108 | "no-class-assign": 2, 109 | "no-cond-assign": 2, 110 | "no-const-assign": 2, 111 | "no-control-regex": 2, 112 | "no-debugger": 2, 113 | "no-delete-var": 2, 114 | "no-dupe-args": 2, 115 | "no-dupe-class-members": 2, 116 | "no-dupe-keys": 2, 117 | "no-duplicate-case": 2, 118 | "no-empty-character-class": 2, 119 | "no-eval": 2, 120 | "no-ex-assign": 2, 121 | "no-extend-native": 2, 122 | "no-extra-bind": 2, 123 | "no-extra-boolean-cast": 2, 124 | "no-extra-parens": [ 125 | 2, 126 | "functions" 127 | ], 128 | "no-fallthrough": 2, 129 | "no-floating-decimal": 2, 130 | "no-func-assign": 2, 131 | "no-implied-eval": 2, 132 | "no-inner-declarations": [ 133 | 2, 134 | "functions" 135 | ], 136 | "no-invalid-regexp": 2, 137 | "no-irregular-whitespace": 2, 138 | "no-iterator": 2, 139 | "no-label-var": 2, 140 | "no-labels": 2, 141 | "no-lone-blocks": 2, 142 | "no-mixed-spaces-and-tabs": 2, 143 | "no-multi-spaces": 2, 144 | "no-multi-str": 2, 145 | "no-multiple-empty-lines": [ 146 | 2, 147 | { 148 | "max": 1 149 | } 150 | ], 151 | "no-native-reassign": 0, 152 | "no-negated-in-lhs": 2, 153 | "no-new": 2, 154 | "no-new-func": 2, 155 | "no-new-object": 2, 156 | "no-new-require": 2, 157 | "no-new-wrappers": 2, 158 | "no-obj-calls": 2, 159 | "no-octal": 2, 160 | "no-octal-escape": 2, 161 | "no-proto": 0, 162 | "no-redeclare": 2, 163 | "no-regex-spaces": 2, 164 | "no-return-assign": 2, 165 | "no-self-compare": 2, 166 | "no-sequences": 2, 167 | "no-shadow-restricted-names": 2, 168 | "no-spaced-func": 2, 169 | "no-sparse-arrays": 2, 170 | "no-this-before-super": 2, 171 | "no-throw-literal": 2, 172 | "no-trailing-spaces": 0, 173 | "no-undef": 2, 174 | "no-undef-init": 2, 175 | "no-unexpected-multiline": 2, 176 | "no-unneeded-ternary": [ 177 | 2, 178 | { 179 | "defaultAssignment": false 180 | } 181 | ], 182 | "no-unreachable": 2, 183 | "no-unused-vars": [ 184 | 2, 185 | { 186 | "vars": "all", 187 | "args": "none" 188 | } 189 | ], 190 | "no-useless-call": 0, 191 | "no-with": 2, 192 | "one-var": [ 193 | 0, 194 | { 195 | "initialized": "never" 196 | } 197 | ], 198 | "operator-linebreak": [ 199 | 0, 200 | "after", 201 | { 202 | "overrides": { 203 | "?": "before", 204 | ":": "before" 205 | } 206 | } 207 | ], 208 | "padded-blocks": [ 209 | 0, 210 | "never" 211 | ], 212 | "quotes": [ 213 | 2, 214 | "single", 215 | "avoid-escape" 216 | ], 217 | "radix": 2, 218 | "semi": [ 219 | 2, 220 | "always" 221 | ], 222 | "semi-spacing": [ 223 | 2, 224 | { 225 | "before": false, 226 | "after": true 227 | } 228 | ], 229 | "space-before-blocks": [ 230 | 2, 231 | "always" 232 | ], 233 | "space-before-function-paren": [ 234 | 2, 235 | "never" 236 | ], 237 | "space-in-parens": [ 238 | 2, 239 | "never" 240 | ], 241 | "space-infix-ops": 2, 242 | "space-unary-ops": [ 243 | 2, 244 | { 245 | "words": true, 246 | "nonwords": false 247 | } 248 | ], 249 | "spaced-comment": [ 250 | 0, 251 | "always", 252 | { 253 | "markers": [ 254 | "global", 255 | "globals", 256 | "eslint", 257 | "eslint-disable", 258 | "*package", 259 | "!", 260 | "," 261 | ] 262 | } 263 | ], 264 | "use-isnan": 2, 265 | "valid-typeof": 2, 266 | "wrap-iife": [ 267 | 2, 268 | "any" 269 | ], 270 | "yoda": [ 271 | 2, 272 | "never" 273 | ] 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # base-store [![NPM version](https://img.shields.io/npm/v/base-store.svg?style=flat)](https://www.npmjs.com/package/base-store) [![NPM downloads](https://img.shields.io/npm/dm/base-store.svg?style=flat)](https://npmjs.org/package/base-store) [![Build Status](https://img.shields.io/travis/node-base/base-store.svg?style=flat)](https://travis-ci.org/node-base/base-store) 2 | 3 | Plugin for getting and persisting config values with your base-methods application. Adds a 'store' object that exposes all of the methods from the data-store library. Also now supports sub-stores! 4 | 5 | You might also be interested in [base-data](https://github.com/node-base/base-data). 6 | 7 | ## Install 8 | 9 | Install with [npm](https://www.npmjs.com/): 10 | 11 | ```sh 12 | $ npm install base-store --save 13 | ``` 14 | 15 | ## Usage 16 | 17 | Adds `store` methods for doing things like this: 18 | 19 | ```js 20 | app.store.set('a', 'z'); // DOES persist 21 | console.log(app.store.get('a')); 22 | //=> 'z'; 23 | ``` 24 | 25 | ## API 26 | 27 | Add a `.store` method to your [base](https://github.com/node-base/base) application: 28 | 29 | ```js 30 | var store = require('base-store'); 31 | var Base = require('base'); 32 | var base = new Base(); 33 | 34 | // store `name` is required 35 | base.use(store('foo')); 36 | 37 | // optionally define a cwd to use for persisting the store 38 | // default cwd is `~/data-store/` 39 | base.use(store('foo', {cwd: 'a/b/c'})); 40 | ``` 41 | 42 | **example usage** 43 | 44 | ```js 45 | base.store 46 | .set('a', 'b') 47 | .set({c: 'd'}) 48 | .set('e.f', 'g') 49 | 50 | console.log(base.store.get('e.f')); 51 | //=> 'g' 52 | 53 | console.log(base.store.data); 54 | //=> {a: 'b', c: 'd', e: {f: 'g'}} 55 | ``` 56 | 57 | ## Sub-stores 58 | 59 | A sub-store is a custom store that is persisted to its own file in a sub-folder of its "parent" store. 60 | 61 | **Create a sub-store** 62 | 63 | ```js 64 | app.store.create('foo'); 65 | // creates an instance of store on `app.store.foo` 66 | 67 | app.store.foo.set('a', 'b'); 68 | app.store.foo.get('a'); 69 | //=> 'b' 70 | ``` 71 | 72 | Sub-store data is also persisted to a property on the "parent" store: 73 | 74 | ```js 75 | // set data on a sub-store 76 | app.store.foo.set('a', 'b'); 77 | 78 | // get the value from parent store 79 | app.store.get('foo.a'); 80 | //=> 'b' 81 | ``` 82 | 83 | ### plugin params 84 | 85 | * `name` **{String}**: Store name. 86 | * `options` **{Object}** 87 | 88 | * `cwd` **{String}**: Current working directory for storage. If not defined, the user home directory is used, based on OS. This is the only option currently, other may be added in the future. 89 | * `indent` **{Number}**: Number passed to `JSON.stringify` when saving the data. Defaults to `2` if `null` or `undefined` 90 | 91 | ## methods 92 | 93 | ### .store.set 94 | 95 | Assign `value` to `key` and save to disk. Can be a key-value pair or an object. 96 | 97 | **Params** 98 | 99 | * `key` **{String}** 100 | * `val` **{any}**: The value to save to `key`. Must be a valid JSON type: String, Number, Array or Object. 101 | * `returns` **{Object}** `Store`: for chaining 102 | 103 | **Example** 104 | 105 | ```js 106 | // key, value 107 | base.store.set('a', 'b'); 108 | //=> {a: 'b'} 109 | 110 | // extend the store with an object 111 | base.store.set({a: 'b'}); 112 | //=> {a: 'b'} 113 | 114 | // extend the the given value 115 | base.store.set('a', {b: 'c'}); 116 | base.store.set('a', {d: 'e'}, true); 117 | //=> {a: {b 'c', d: 'e'}} 118 | 119 | // overwrite the the given value 120 | base.store.set('a', {b: 'c'}); 121 | base.store.set('a', {d: 'e'}); 122 | //=> {d: 'e'} 123 | ``` 124 | 125 | ### .store.union 126 | 127 | Add or append an array of unique values to the given `key`. 128 | 129 | **Params** 130 | 131 | * `key` **{String}** 132 | * `returns` **{any}**: The array to add or append for `key`. 133 | 134 | **Example** 135 | 136 | ```js 137 | base.store.union('a', ['a']); 138 | base.store.union('a', ['b']); 139 | base.store.union('a', ['c']); 140 | base.store.get('a'); 141 | //=> ['a', 'b', 'c'] 142 | ``` 143 | 144 | ### .store.get 145 | 146 | Get the stored `value` of `key`, or return the entire store if no `key` is defined. 147 | 148 | **Params** 149 | 150 | * `key` **{String}** 151 | * `returns` **{any}**: The value to store for `key`. 152 | 153 | **Example** 154 | 155 | ```js 156 | base.store.set('a', {b: 'c'}); 157 | base.store.get('a'); 158 | //=> {b: 'c'} 159 | 160 | base.store.get(); 161 | //=> {b: 'c'} 162 | ``` 163 | 164 | ### .store.has 165 | 166 | Returns `true` if the specified `key` has truthy value. 167 | 168 | **Params** 169 | 170 | * `key` **{String}** 171 | * `returns` **{Boolean}**: Returns true if `key` has 172 | 173 | **Example** 174 | 175 | ```js 176 | base.store.set('a', 'b'); 177 | base.store.set('c', null); 178 | base.store.has('a'); //=> true 179 | base.store.has('c'); //=> false 180 | base.store.has('d'); //=> false 181 | ``` 182 | 183 | ### .store.hasOwn 184 | 185 | Returns `true` if the specified `key` exists. 186 | 187 | **Params** 188 | 189 | * `key` **{String}** 190 | * `returns` **{Boolean}**: Returns true if `key` exists 191 | 192 | **Example** 193 | 194 | ```js 195 | base.store.set('a', 'b'); 196 | base.store.set('b', false); 197 | base.store.set('c', null); 198 | base.store.set('d', true); 199 | 200 | base.store.hasOwn('a'); //=> true 201 | base.store.hasOwn('b'); //=> true 202 | base.store.hasOwn('c'); //=> true 203 | base.store.hasOwn('d'); //=> true 204 | base.store.hasOwn('foo'); //=> false 205 | ``` 206 | 207 | ### .store.save 208 | 209 | Persist the store to disk. 210 | 211 | **Params** 212 | 213 | * `dest` **{String}**: Optionally define a different destination than the default path. 214 | 215 | **Example** 216 | 217 | ```js 218 | base.store.save(); 219 | ``` 220 | 221 | ### .store.del 222 | 223 | Delete `keys` from the store, or delete the entire store if no keys are passed. A `del` event is also emitted for each key deleted. 224 | 225 | **Note that to delete the entire store you must pass `{force: true}`** 226 | 227 | **Params** 228 | 229 | * `keys` **{String|Array|Object}**: Keys to remove, or options. 230 | * `options` **{Object}** 231 | 232 | **Example** 233 | 234 | ```js 235 | base.store.del(); 236 | 237 | // to delete paths outside cwd 238 | base.store.del({force: true}); 239 | ``` 240 | 241 | ## History 242 | 243 | **v0.3.1** 244 | 245 | * Sub-stores are easier to create and get. You can now do `app.store.create('foo')` to create a sub-store, which is then available as `app.store.foo`. 246 | 247 | **v0.3.0** 248 | 249 | * Introducing sub-stores! 250 | 251 | ## Related projects 252 | 253 | Other plugins for extending your [base](https://github.com/node-base/base) application: 254 | 255 | * [base-options](https://www.npmjs.com/package/base-options): Adds a few options methods to base-methods, like `option`, `enable` and `disable`. See the readme… [more](https://www.npmjs.com/package/base-options) | [homepage](https://github.com/jonschlinkert/base-options) 256 | * [base-pipeline](https://www.npmjs.com/package/base-pipeline): base-methods plugin that adds pipeline and plugin methods for dynamically composing streaming plugin pipelines. | [homepage](https://github.com/node-base/base-pipeline) 257 | * [base-plugins](https://www.npmjs.com/package/base-plugins): Upgrade's plugin support in base applications to allow plugins to be called any time after… [more](https://www.npmjs.com/package/base-plugins) | [homepage](https://github.com/node-base/base-plugins) 258 | * [base-questions](https://www.npmjs.com/package/base-questions): Plugin for base-methods that adds methods for prompting the user and storing the answers on… [more](https://www.npmjs.com/package/base-questions) | [homepage](https://github.com/node-base/base-questions) 259 | * [base](https://www.npmjs.com/package/base): base is the foundation for creating modular, unit testable and highly pluggable node.js applications, starting… [more](https://www.npmjs.com/package/base) | [homepage](https://github.com/node-base/base) 260 | 261 | ## Contributing 262 | 263 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/node-base/base-store/issues/new). 264 | 265 | ## Building docs 266 | 267 | Generate readme and API documentation with [verb](https://github.com/verbose/verb): 268 | 269 | ```sh 270 | $ npm install verb && npm run docs 271 | ``` 272 | 273 | Or, if [verb](https://github.com/verbose/verb) is installed globally: 274 | 275 | ```sh 276 | $ verb 277 | ``` 278 | 279 | ## Running tests 280 | 281 | Install dev dependencies: 282 | 283 | ```sh 284 | $ npm install -d && npm test 285 | ``` 286 | 287 | ## Author 288 | 289 | **Jon Schlinkert** 290 | 291 | * [github/jonschlinkert](https://github.com/jonschlinkert) 292 | * [twitter/jonschlinkert](http://twitter.com/jonschlinkert) 293 | 294 | ## License 295 | 296 | Copyright © 2016, [Jon Schlinkert](https://github.com/jonschlinkert). 297 | Released under the [MIT license](https://github.com/node-base/base-store/blob/master/LICENSE). 298 | 299 | *** 300 | 301 | _This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on May 19, 2016._ -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | require('should'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var assert = require('assert'); 8 | var Base = require('base'); 9 | var Store = require('data-store'); 10 | var store = require('./'); 11 | var base; 12 | 13 | describe('base-store', function() { 14 | describe('plugin', function() { 15 | it('should be an instance of Store', function() { 16 | var base = new Base(); 17 | base.isApp = true; 18 | base.use(store('base-data-tests')); 19 | assert(base.store instanceof Store); 20 | }); 21 | 22 | it('should not register when `isApp` is not true', function() { 23 | var base = new Base(); 24 | base.isApp = false; 25 | base.use(store('base-data-tests')); 26 | assert.equal(typeof base.store, 'undefined'); 27 | }); 28 | }); 29 | 30 | describe('app.store', function() { 31 | beforeEach(function() { 32 | base = new Base(); 33 | base.isApp = true; 34 | }); 35 | 36 | afterEach(function() { 37 | base.store.data = {}; 38 | base.store.del({force: true}); 39 | }); 40 | 41 | it('should detect store name if not passed:', function() { 42 | base.use(store()); 43 | assert.equal(base.store.name, 'base-store'); 44 | }); 45 | 46 | it('should create a store with the given `name`', function() { 47 | base.use(store('foo-bar-baz')); 48 | assert.equal(base.store.name, 'foo-bar-baz'); 49 | }); 50 | 51 | it('should create a store at the given `cwd`', function() { 52 | base.use(store('abc', {cwd: 'actual'})); 53 | base.store.set('foo', 'bar'); 54 | path.basename(base.store.path).should.equal('abc.json'); 55 | base.store.data.should.have.property('foo', 'bar'); 56 | assert.equal(fs.existsSync(path.join(__dirname, 'actual/abc.json')), true); 57 | }); 58 | 59 | it('should create a store using the given `indent` value', function() { 60 | base.use(store('abc', {cwd: 'actual', indent: 0})); 61 | base.store.set('foo', 'bar'); 62 | var contents = fs.readFileSync(path.join(__dirname, 'actual/abc.json'), 'utf8'); 63 | assert.equal(contents, '{"foo":"bar"}'); 64 | }); 65 | }); 66 | 67 | describe('methods', function() { 68 | beforeEach(function() { 69 | base = new Base(); 70 | base.isApp = true; 71 | base.use(store('base-data-tests')); 72 | }); 73 | 74 | afterEach(function() { 75 | base.store.data = {}; 76 | base.store.del({force: true}); 77 | }); 78 | 79 | it('should `.set()` a value on the store', function() { 80 | base.store.set('one', 'two'); 81 | base.store.data.one.should.equal('two'); 82 | }); 83 | 84 | it('should `.set()` an object', function() { 85 | base.store.set({four: 'five', six: 'seven'}); 86 | base.store.data.four.should.equal('five'); 87 | base.store.data.six.should.equal('seven'); 88 | }); 89 | 90 | it('should `.set()` a nested value', function() { 91 | base.store.set('a.b.c.d', {e: 'f'}); 92 | base.store.data.a.b.c.d.e.should.equal('f'); 93 | }); 94 | 95 | it('should `.union()` a value on the store', function() { 96 | base.store.union('one', 'two'); 97 | base.store.data.one.should.eql(['two']); 98 | }); 99 | 100 | it('should not union duplicate values', function() { 101 | base.store.union('one', 'two'); 102 | base.store.data.one.should.eql(['two']); 103 | 104 | base.store.union('one', ['two']); 105 | base.store.data.one.should.eql(['two']); 106 | }); 107 | 108 | it('should concat an existing array:', function() { 109 | base.store.union('one', 'a'); 110 | base.store.data.one.should.eql(['a']); 111 | 112 | base.store.union('one', ['b']); 113 | base.store.data.one.should.eql(['a', 'b']); 114 | 115 | base.store.union('one', ['c', 'd']); 116 | base.store.data.one.should.eql(['a', 'b', 'c', 'd']); 117 | }); 118 | 119 | it('should return true if a key `.has()` on the store', function() { 120 | base.store.set('foo', 'bar'); 121 | base.store.set('baz', null); 122 | base.store.set('qux', undefined); 123 | 124 | assert(base.store.has('foo')); 125 | assert(base.store.has('baz')); 126 | assert(!base.store.has('bar')); 127 | assert(!base.store.has('qux')); 128 | }); 129 | 130 | it('should return true if a nested key `.has()` on the store', function() { 131 | base.store.set('a.b.c.d', {x: 'zzz'}); 132 | base.store.set('a.b.c.e', {f: null}); 133 | base.store.set('a.b.g.j', {k: undefined}); 134 | 135 | assert(!base.store.has('a.b.bar')); 136 | assert(base.store.has('a.b.c.d')); 137 | assert(base.store.has('a.b.c.d.x')); 138 | assert(!base.store.has('a.b.c.d.z')); 139 | assert(base.store.has('a.b.c.e')); 140 | assert(base.store.has('a.b.c.e.f')); 141 | assert(!base.store.has('a.b.c.e.z')); 142 | assert(base.store.has('a.b.g.j')); 143 | assert(!base.store.has('a.b.g.j.k')); 144 | assert(!base.store.has('a.b.g.j.z')); 145 | }); 146 | 147 | it('should return true if a key exists `.hasOwn()` on the store', function() { 148 | base.store.set('foo', 'bar'); 149 | base.store.set('baz', null); 150 | base.store.set('qux', undefined); 151 | 152 | assert(base.store.hasOwn('foo')); 153 | assert(!base.store.hasOwn('bar')); 154 | assert(base.store.hasOwn('baz')); 155 | assert(base.store.hasOwn('qux')); 156 | }); 157 | 158 | it('should return true if a nested key exists `.hasOwn()` on the store', function() { 159 | base.store.set('a.b.c.d', {x: 'zzz'}); 160 | base.store.set('a.b.c.e', {f: null}); 161 | base.store.set('a.b.g.j', {k: undefined}); 162 | 163 | assert(!base.store.hasOwn('a.b.bar')); 164 | assert(base.store.hasOwn('a.b.c.d')); 165 | assert(base.store.hasOwn('a.b.c.d.x')); 166 | assert(!base.store.hasOwn('a.b.c.d.z')); 167 | assert(base.store.has('a.b.c.e.f')); 168 | assert(base.store.hasOwn('a.b.c.e.f')); 169 | assert(!base.store.hasOwn('a.b.c.e.bar')); 170 | assert(!base.store.has('a.b.g.j.k')); 171 | assert(base.store.hasOwn('a.b.g.j.k')); 172 | assert(!base.store.hasOwn('a.b.g.j.foo')); 173 | }); 174 | 175 | it('should `.get()` a stored value', function() { 176 | base.store.set('three', 'four'); 177 | base.store.get('three').should.equal('four'); 178 | }); 179 | 180 | it('should `.get()` a nested value', function() { 181 | base.store.set({a: {b: {c: 'd'}}}); 182 | base.store.get('a.b.c').should.equal('d'); 183 | }); 184 | 185 | it('should `.del()` a stored value', function() { 186 | base.store.set('a', 'b'); 187 | base.store.set('c', 'd'); 188 | base.store.data.should.have.property('a'); 189 | base.store.data.should.have.property('c'); 190 | 191 | base.store.del('a'); 192 | base.store.del('c'); 193 | base.store.data.should.not.have.property('a'); 194 | base.store.data.should.not.have.property('c'); 195 | }); 196 | 197 | it('should `.del()` multiple stored values', function() { 198 | base.store.set('a', 'b'); 199 | base.store.set('c', 'd'); 200 | base.store.set('e', 'f'); 201 | base.store.del(['a', 'c', 'e']); 202 | base.store.data.should.eql({}); 203 | }); 204 | }); 205 | }); 206 | 207 | describe('create', function() { 208 | beforeEach(function() { 209 | base = new Base(); 210 | base.isApp = true; 211 | base.use(store('abc')); 212 | 213 | // init the actual store json file 214 | base.store.set('a', 'b'); 215 | }); 216 | 217 | afterEach(function() { 218 | base.store.data = {}; 219 | base.store.del({force: true}); 220 | }); 221 | 222 | it('should expose a `create` method', function() { 223 | assert.equal(typeof base.store.create, 'function'); 224 | }); 225 | 226 | it('should create a "sub-store" with the given name', function() { 227 | var store = base.store.create('created'); 228 | assert.equal(store.name, 'created'); 229 | }); 230 | 231 | it('should create a "sub-store" with the project name when no name is passed', function() { 232 | var store = base.store.create(); 233 | assert.equal(store.name, 'base-store'); 234 | }); 235 | 236 | it('should throw an error when a conflicting store name is used', function(cb) { 237 | try { 238 | base.store.create('create'); 239 | cb(new Error('expected an error')); 240 | } catch (err) { 241 | assert.equal(err.message, 'Cannot create store: "create", since "create" is a reserved property key. Please choose a different store name.'); 242 | cb(); 243 | } 244 | }); 245 | 246 | it('should add a store object to store[name]', function() { 247 | base.store.create('foo'); 248 | assert.equal(typeof base.store.foo, 'object'); 249 | assert.equal(typeof base.store.foo.set, 'function'); 250 | base.store.foo.del({force: true}); 251 | }); 252 | 253 | it('should save the store in a namespaced directory under the parent', function() { 254 | base.store.create('foo'); 255 | var dir = path.dirname(base.store.path); 256 | 257 | assert.equal(base.store.foo.path, path.join(dir, 'abc/foo.json')); 258 | base.store.foo.set('a', 'b'); 259 | base.store.foo.del({force: true}); 260 | }); 261 | 262 | it('should set values on the custom store', function() { 263 | base.store.create('foo'); 264 | base.store.foo.set('a', 'b'); 265 | assert.equal(base.store.foo.data.a, 'b'); 266 | base.store.foo.del({force: true}); 267 | }); 268 | 269 | it('should get values from the custom store', function() { 270 | base.store.create('foo'); 271 | base.store.foo.set('a', 'b'); 272 | assert.equal(base.store.foo.get('a'), 'b'); 273 | base.store.foo.del({force: true}); 274 | }); 275 | }); 276 | 277 | describe('events', function() { 278 | beforeEach(function() { 279 | base = new Base(); 280 | base.isApp = true; 281 | base.use(store('abc')); 282 | }); 283 | 284 | afterEach(function() { 285 | base.store.data = {}; 286 | base.store.del({force: true}); 287 | }); 288 | 289 | it('should emit `set` when an object is set:', function() { 290 | var keys = []; 291 | base.store.on('set', function(key) { 292 | keys.push(key); 293 | }); 294 | 295 | base.store.set({a: {b: {c: 'd'}}}); 296 | keys.should.eql(['a']); 297 | }); 298 | 299 | it('should emit `store.set` on app:', function() { 300 | var keys = []; 301 | base.on('store.set', function(key) { 302 | keys.push(key); 303 | }); 304 | 305 | base.store.set({a: {b: {c: 'd'}}}); 306 | keys.should.eql(['a']); 307 | }); 308 | 309 | it('should emit `store` on app when store.set is called:', function() { 310 | var keys = []; 311 | base.on('store', function(method, key, val) { 312 | keys.push(key); 313 | }); 314 | 315 | base.store.set({a: {b: {c: 'd'}}}); 316 | keys.should.eql(['a']); 317 | }); 318 | 319 | it('should emit `set` when a key/value pair is set:', function() { 320 | var keys = []; 321 | 322 | base.store.on('set', function(key) { 323 | keys.push(key); 324 | }); 325 | 326 | base.store.set('a', 'b'); 327 | keys.should.eql(['a']); 328 | }); 329 | 330 | it('should emit `set` when an object value is set:', function() { 331 | var keys = []; 332 | 333 | base.store.on('set', function(key) { 334 | keys.push(key); 335 | }); 336 | 337 | base.store.set('a', {b: 'c'}); 338 | keys.should.eql(['a']); 339 | }); 340 | 341 | it('should emit `set` when an array of objects is passed:', function(cb) { 342 | var keys = []; 343 | 344 | base.store.on('set', function(key) { 345 | keys.push(key); 346 | }); 347 | 348 | base.store.set([{a: 'b'}, {c: 'd'}]); 349 | keys.should.eql(['a', 'c']); 350 | cb(); 351 | }); 352 | 353 | it('should emit `has`:', function(cb) { 354 | var keys = []; 355 | 356 | base.store.on('has', function(val) { 357 | assert(val); 358 | cb(); 359 | }); 360 | 361 | base.store.set('a', 'b'); 362 | base.store.has('a'); 363 | }); 364 | 365 | it('should emit `del` when a value is deleted:', function(cb) { 366 | base.store.on('del', function(keys) { 367 | keys.should.eql('a'); 368 | assert.equal(typeof base.store.get('a'), 'undefined'); 369 | cb(); 370 | }); 371 | 372 | base.store.set('a', {b: 'c'}); 373 | base.store.get('a').should.eql({b: 'c'}); 374 | base.store.del('a'); 375 | }); 376 | 377 | it('should emit `store.del` on app when a value is deleted:', function(cb) { 378 | base.on('store.del', function(keys) { 379 | keys.should.eql('a'); 380 | assert.equal(typeof base.store.get('a'), 'undefined'); 381 | cb(); 382 | }); 383 | 384 | base.store.set('a', {b: 'c'}); 385 | base.store.get('a').should.eql({b: 'c'}); 386 | base.store.del('a'); 387 | }); 388 | 389 | it('should emit `store` on app when a value is deleted:', function(cb) { 390 | base.store.set('a', {b: 'c'}); 391 | base.store.get('a').should.eql({b: 'c'}); 392 | 393 | base.once('store', function(method, key) { 394 | method.should.eql('del'); 395 | key.should.eql('a'); 396 | cb(); 397 | }); 398 | base.store.del('a'); 399 | }); 400 | 401 | it('should emit deleted keys on `del`:', function(cb) { 402 | var arr = []; 403 | 404 | base.store.on('del', function(key) { 405 | arr.push(key); 406 | assert.equal(Object.keys(base.store.data).length, 0); 407 | }); 408 | 409 | base.store.set('a', 'b'); 410 | base.store.set('c', 'd'); 411 | base.store.set('e', 'f'); 412 | 413 | base.store.del({force: true}); 414 | arr.should.eql(['a', 'c', 'e']); 415 | cb(); 416 | }); 417 | 418 | it('should throw an error if force is not passed', function() { 419 | base.store.set('a', 'b'); 420 | base.store.set('c', 'd'); 421 | base.store.set('e', 'f'); 422 | 423 | (function() { 424 | base.store.del(); 425 | }).should.throw('options.force is required to delete the entire cache.'); 426 | }); 427 | }); 428 | --------------------------------------------------------------------------------