diff --git a/.eslintrc.json b/.eslintrc.json index 61e8895..24b8984 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,4 +1,8 @@ { + "extends": [ + "eslint:recommended" + ], + "env": { "browser": false, "es6": true, @@ -6,6 +10,15 @@ "mocha": true }, + "parserOptions":{ + "ecmaVersion": 9, + "sourceType": "module", + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true + } + }, + "globals": { "document": false, "navigator": false, diff --git a/.travis.yml b/.travis.yml index 44adec2..f6cf862 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,3 @@ node_js: - '6' - '5' - '4' -matrix: - fast_finish: true - allow_failures: - - node_js: '5' - - node_js: '4' diff --git a/.verb.md b/.verb.md index 544ae0d..17bca7a 100644 --- a/.verb.md +++ b/.verb.md @@ -4,19 +4,18 @@ // default cwd is `~/data-store/` (in user-home) var store = require('{%= name %}')('my-app'); -store - .set('a', 'b') - .set({c: 'd'}) - .set('e.f', 'g') +store.set('a', 'b') + .set({ c: 'd' }) + .set('e.f', 'g'); console.log(store.get('e.f')); //=> 'g' console.log(store.get()); -//=> {name: 'app', data: {a: 'b', c: 'd', e: {f: 'g' }}} +//=> { name: 'app', data: { a: 'b', c: 'd', e: { f: 'g' } } } console.log(store.data); -//=> {a: 'b', c: 'd', e: {f: 'g'}} +//=> { a: 'b', c: 'd', e: { f: 'g' } } ``` ## API diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..767fb95 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,267 @@ +# Release history + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +
+ Guiding Principles + +- Changelogs are for humans, not machines. +- There should be an entry for every single version. +- The same types of changes should be grouped. +- Versions and sections should be linkable. +- The latest version comes first. +- The release date of each versions is displayed. +- Mention whether you follow Semantic Versioning. + +
+ +
+ Types of changes + +Changelog entries are classified using the following labels _(from [keep-a-changelog](http://keepachangelog.com/)_): + +- `Added` for new features. +- `Changed` for changes in existing functionality. +- `Deprecated` for soon-to-be removed features. +- `Removed` for now removed features. +- `Fixed` for any bug fixes. +- `Security` in case of vulnerabilities. + +
+ + +## [2.0.0] - 2017-05-22 + +- Use XDG directory +- Merge pull request #8 from jamen/master + +## [1.0.0] - 2017-05-22 + +- run update +- update deps, remove verbfile.js +- add example to readme +- allow `store.path` to be set directly + +## [0.16.1] - 2016-07-11 + +- run update +- adds debug, minor edits +- use `verb-generate-readme` +- generate docs + +## [0.16.0] - 2016-05-19 + +- expose sub-store data on property of parent store +- generate docs + +## [0.15.5] - 2016-03-02 + +- allow dashes in sub-store names +- generate docs + +## [0.15.4] - 2016-02-27 + +- fixes https://github.com/jonschlinkert/data-store/commit/a62b36aa7fc9a7215bb15ab5738dd79686eae25c#commitcomment-16372520, thanks to @tunnckoCore for point it out + +## [0.15.3] - 2016-02-27 + +- simplify to use `this.name` +- generate docs + +## [0.15.2] - 2016-02-27 + +- handle path differently, fix basename bug +- generate docs + +## [0.15.1] - 2016-02-27 + +- fix keys getter, path bug +- generate docs + +## [0.15.0] - 2016-02-27 + +- use `cache-base` lib instead of `base`, also adds `create` method +- adds verbfile.js +- update dependencies +- generate docs + +## [0.14.0] - 2016-02-04 + +- only write data that was set through the API + +## [0.13.0] - 2016-01-23 + +- use `base` lib +- clean up deps +- lint +- update deps, verb config + +## [0.12.1] - 2016-01-02 + +- run `update` +- copyright date +- remove code that is already provided by base-methods +- update deps + +## [0.12.0] - 2015-11-09 + +- use eslint +- lint +- update deps +- `get` now expects key to not be undefined +- clean up, save on set, update readme + +## [0.11.1] - 2015-10-23 + +- adds events to `has` and `hasOwn` +- streamline tests, add test for `has` event +- update docs + +## [0.11.0] - 2015-10-21 + +- make del sync +- update docs for 0.11.0 + +## [0.10.1] - 2015-10-19 + +- use `resolve-dir` +- rebuild docs + +## [0.10.0] - 2015-10-14 + +- lint +- update lazy-cache signature +- adds gulp +- use base-methods +- adds coverage + +## [0.9.0] - 2015-08-30 + +- move libs to utils +- adds static `extend` method +- adds 0.9.0 tests +- examples for 0.9.0 +- build docs + +## [0.8.2] - 2015-08-19 + +- add an indent option to pass to JSON.stringify +- save after union and del commands +- updating option comments and rebuilding readme +- Merge pull request #6 from jonschlinkert/indent-and-save +- update to latest lazy-cache + +## [0.8.1] - 2015-08-01 + +- fix `.has` method +- add `.hasOwn` method +- Merge pull request #5 from chorks/hasown-method +- make it lazier + +## [0.8.0] - 2015-07-05 + +- adds `union` method +- generate docs + +## [0.6.0] - 2015-05-07 + +- breaking change: `delete` => `del` +- update metadata +- fix template +- generate docs + +## [0.5.0] - 2015-04-19 + +- adds events +- events tests +- remove junk + +## [0.4.1] - 2015-03-28 + +- persist store + +## [0.4.0] - 2015-03-28 + +- adds travis badge +- lint +- adds example.js to editorconfig +- update example +- refactored +- update examples +- include example.js +- update tests + +## [0.3.3] - 2015-02-09 + +- travis + +## [0.3.2] - 2015-02-08 + +- rename license file +- remove junk +- build readme + +## [0.3.1] - 2014-12-17 + +- Merge remote-tracking branch 'origin/prev' +- lint + +## [0.3.0] - 2014-12-17 + +- fix readme +- adds npmignore +- adds example, tests +- update dotfiles +- remove extra heading in readme +- update ignore patterns +- fix examples +- update ignore patterns +- adds `extists` and `delete` methods +- build readme + +## [0.2.0] - 2014-11-15 + +- first commit +- remove console.log +- remove old fixtures +- update verb and dotfiles +- refactor +- run verb + +[2.0.0]: https://github.com/jonschlinkert/data-store/compare/0.2.0...HEAD +[1.0.0]: https://github.com/jonschlinkert/data-store/compare/0.16.1...1.0.0 +[0.16.1]: https://github.com/jonschlinkert/data-store/compare/0.16.0...0.16.1 +[0.16.0]: https://github.com/jonschlinkert/data-store/compare/0.15.5...0.16.0 +[0.15.5]: https://github.com/jonschlinkert/data-store/compare/0.15.4...0.15.5 +[0.15.4]: https://github.com/jonschlinkert/data-store/compare/0.15.3...0.15.4 +[0.15.3]: https://github.com/jonschlinkert/data-store/compare/0.15.2...0.15.3 +[0.15.2]: https://github.com/jonschlinkert/data-store/compare/0.15.1...0.15.2 +[0.15.1]: https://github.com/jonschlinkert/data-store/compare/0.15.0...0.15.1 +[0.15.0]: https://github.com/jonschlinkert/data-store/compare/0.14.0...0.15.0 +[0.14.0]: https://github.com/jonschlinkert/data-store/compare/0.13.0...0.14.0 +[0.13.0]: https://github.com/jonschlinkert/data-store/compare/0.12.1...0.13.0 +[0.12.1]: https://github.com/jonschlinkert/data-store/compare/0.12.0...0.12.1 +[0.12.0]: https://github.com/jonschlinkert/data-store/compare/0.11.1...0.12.0 +[0.11.1]: https://github.com/jonschlinkert/data-store/compare/0.11.0...0.11.1 +[0.11.0]: https://github.com/jonschlinkert/data-store/compare/0.10.1...0.11.0 +[0.10.1]: https://github.com/jonschlinkert/data-store/compare/0.10.0...0.10.1 +[0.10.0]: https://github.com/jonschlinkert/data-store/compare/0.9.0...0.10.0 +[0.9.0]: https://github.com/jonschlinkert/data-store/compare/0.8.2...0.9.0 +[0.8.2]: https://github.com/jonschlinkert/data-store/compare/0.8.1...0.8.2 +[0.8.1]: https://github.com/jonschlinkert/data-store/compare/0.8.0...0.8.1 +[0.8.0]: https://github.com/jonschlinkert/data-store/compare/0.6.1...0.8.0 +[0.6.1]: https://github.com/jonschlinkert/data-store/compare/0.6.0...0.6.1 +[0.6.0]: https://github.com/jonschlinkert/data-store/compare/0.5.0...0.6.0 +[0.5.0]: https://github.com/jonschlinkert/data-store/compare/0.4.1...0.5.0 +[0.4.1]: https://github.com/jonschlinkert/data-store/compare/0.4.0...0.4.1 +[0.4.0]: https://github.com/jonschlinkert/data-store/compare/0.3.3...0.4.0 +[0.3.3]: https://github.com/jonschlinkert/data-store/compare/0.3.2...0.3.3 +[0.3.2]: https://github.com/jonschlinkert/data-store/compare/0.3.1...0.3.2 +[0.3.1]: https://github.com/jonschlinkert/data-store/compare/0.3.0...0.3.1 +[0.3.0]: https://github.com/jonschlinkert/data-store/compare/0.2.0...0.3.0 + +[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog + diff --git a/README.md b/README.md index bc4b8f2..fbba63e 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ > Easily get, set and persist config data. -You might also be interested in [base-store](https://github.com/node-base/base-store). - -## Table of Contents +Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support. - [Install](#install) - [Usage example](#usage-example) @@ -27,27 +25,22 @@ $ npm install --save data-store // default cwd is `~/data-store/` (in user-home) var store = require('data-store')('my-app'); -store - .set('a', 'b') - .set({c: 'd'}) - .set('e.f', 'g') +store.set('a', 'b') + .set({ c: 'd' }) + .set('e.f', 'g'); console.log(store.get('e.f')); //=> 'g' console.log(store.get()); -//=> {name: 'app', data: {a: 'b', c: 'd', e: {f: 'g' }}} +//=> { name: 'app', data: { a: 'b', c: 'd', e: { f: 'g' } } } console.log(store.data); -//=> {a: 'b', c: 'd', e: {f: 'g'}} +//=> { a: 'b', c: 'd', e: { f: 'g' } } ``` ## API -### [Store](index.js#L42) - -Initialize a new `Store` with the given `name` and `options`. - **Params** * `name` **{String}**: Store name to use for the basename of the `.json` file. @@ -58,35 +51,16 @@ Initialize a new `Store` with the given `name` and `options`. **Example** ```js -var store = require('data-store')('abc'); +const store = require('data-store')('abc'); //=> '~/data-store/a.json' -var store = require('data-store')('abc', { +const store = require('data-store')('abc', { cwd: 'test/fixtures' }); //=> './test/fixtures/abc.json' ``` -### [.create](index.js#L99) - -Create a namespaced "sub-store" that persists data to its file in a sub-folder of the same directory as the "parent" store. - -**Params** - -* `name` **{String}**: The name of the sub-store. -* `options` **{Object}** -* `returns` **{Object}**: Returns the sub-store instance. - -**Example** - -```js -store.create('foo'); -store.foo.set('a', 'b'); -console.log(store.foo.get('a')); -//=> 'b' -``` - -### [.set](index.js#L147) +### [.set](index.js#L82) Assign `value` to `key` and save to disk. Can be a key-value pair or an object. @@ -106,38 +80,29 @@ store.set('a', 'b'); // extend the store with an object store.set({a: 'b'}); //=> {a: 'b'} - -// extend the the given value -store.set('a', {b: 'c'}); -store.set('a', {d: 'e'}, true); -//=> {a: {b 'c', d: 'e'}} - -// overwrite the the given value -store.set('a', {b: 'c'}); -store.set('a', {d: 'e'}); -//=> {d: 'e'} ``` -### [.union](index.js#L163) +### [.union](index.js#L114) -Add or append an array of unique values to the given `key`. +Get the stored `value` of `key`, or return the entire store if no `key` is defined. **Params** * `key` **{String}** -* `returns` **{any}**: The array to add or append for `key`. +* `returns` **{any}**: The value to store for `key`. **Example** ```js -store.union('a', ['a']); -store.union('a', ['b']); -store.union('a', ['c']); +store.set('a', {b: 'c'}); store.get('a'); -//=> ['a', 'b', 'c'] +//=> {b: 'c'} + +store.get(); +//=> {b: 'c'} ``` -### [.get](index.js#L189) +### [.get](index.js#L142) Get the stored `value` of `key`, or return the entire store if no `key` is defined. @@ -157,9 +122,9 @@ store.get(); //=> {b: 'c'} ``` -### [.has](index.js#L205) +### [.has](index.js#L162) -Returns `true` if the specified `key` has truthy value. +Returns `true` if the specified `key` has a value. **Params** @@ -176,10 +141,6 @@ store.has('c'); //=> false store.has('d'); //=> false ``` -### [.hasOwn](index.js#L226) - -Returns `true` if the specified `key` exists. - **Params** * `key` **{String}** @@ -200,34 +161,6 @@ store.hasOwn('d'); //=> true store.hasOwn('foo'); //=> false ``` -### [.save](index.js#L246) - -Persist the store to disk. - -**Params** - -* `dest` **{String}**: Optionally define an alternate destination file path. - -**Example** - -```js -store.save(); -``` - -### [.clear](index.js#L261) - -Clear in-memory cache. - -**Example** - -```js -store.clear(); -``` - -### [.del](index.js#L286) - -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. - **Note that to delete the entire store you must pass `{force: true}`** **Params** @@ -244,38 +177,34 @@ store.del(); store.del({force: true}); ``` -### [.define](index.js#L347) - -Define a non-enumerable property on the instance. - -**Params** +**Example** -* `key` **{String}** -* `value` **{any}** -* `returns` **{Object}**: Returns the instance for chaining. +```js +store.save(); +``` ## About -### Related projects +
+Contributing -* [base-store](https://www.npmjs.com/package/base-store): Plugin for getting and persisting config values with your base-methods application. Adds a 'store' object… [more](https://github.com/node-base/base-store) | [homepage](https://github.com/node-base/base-store "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!") -* [cache-base](https://www.npmjs.com/package/cache-base): Basic object cache with `get`, `set`, `del`, and `has` methods for node.js/javascript projects. | [homepage](https://github.com/jonschlinkert/cache-base "Basic object cache with `get`, `set`, `del`, and `has` methods for node.js/javascript projects.") -* [get-value](https://www.npmjs.com/package/get-value): Use property paths (`a.b.c`) to get a nested value from an object. | [homepage](https://github.com/jonschlinkert/get-value "Use property paths (`a.b.c`) to get a nested value from an object.") -* [set-value](https://www.npmjs.com/package/set-value): Create nested values and any intermediaries using dot notation (`'a.b.c'`) paths. | [homepage](https://github.com/jonschlinkert/set-value "Create nested values and any intermediaries using dot notation (`'a.b.c'`) paths.") +Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). -### Contributing +
-Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). +
+Running Tests -### Contributors +Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: -| **Commits** | **Contributor** | -| --- | --- | -| 128 | [jonschlinkert](https://github.com/jonschlinkert) | -| 3 | [doowb](https://github.com/doowb) | -| 2 | [tunnckoCore](https://github.com/tunnckoCore) | +```sh +$ npm install && npm test +``` -### Building docs +
+ +
+Building docs _(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_ @@ -285,26 +214,40 @@ To generate the readme, run the following command: $ npm install -g verbose/verb#dev verb-generate-readme && verb ``` -### Running tests +
-Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: +### Related projects -```sh -$ npm install && npm test -``` +You might also be interested in these projects: + +* [base](https://www.npmjs.com/package/base): Framework for rapidly creating high quality, server-side node.js applications, using plugins like building blocks | [homepage](https://github.com/node-base/base "Framework for rapidly creating high quality, server-side node.js applications, using plugins like building blocks") +* [cache-base](https://www.npmjs.com/package/cache-base): Basic object cache with `get`, `set`, `del`, and `has` methods for node.js/javascript projects. | [homepage](https://github.com/jonschlinkert/cache-base "Basic object cache with `get`, `set`, `del`, and `has` methods for node.js/javascript projects.") +* [get-value](https://www.npmjs.com/package/get-value): Use property paths like 'a.b.c' to get a nested value from an object. Even works… [more](https://github.com/jonschlinkert/get-value) | [homepage](https://github.com/jonschlinkert/get-value "Use property paths like 'a.b.c' to get a nested value from an object. Even works when keys have dots in them (no other dot-prop library can do this!).") +* [has-value](https://www.npmjs.com/package/has-value): Returns true if a value exists, false if empty. Works with deeply nested values using… [more](https://github.com/jonschlinkert/has-value) | [homepage](https://github.com/jonschlinkert/has-value "Returns true if a value exists, false if empty. Works with deeply nested values using object paths.") +* [set-value](https://www.npmjs.com/package/set-value): Create nested values and any intermediaries using dot notation (`'a.b.c'`) paths. | [homepage](https://github.com/jonschlinkert/set-value "Create nested values and any intermediaries using dot notation (`'a.b.c'`) paths.") + +### Contributors + +| **Commits** | **Contributor** | +| --- | --- | +| 135 | [jonschlinkert](https://github.com/jonschlinkert) | +| 3 | [doowb](https://github.com/doowb) | +| 2 | [charlike-old](https://github.com/charlike-old) | +| 1 | [jamen](https://github.com/jamen) | ### Author **Jon Schlinkert** -* [github/jonschlinkert](https://github.com/jonschlinkert) -* [twitter/jonschlinkert](https://twitter.com/jonschlinkert) +* [LinkedIn Profile](https://linkedin.com/in/jonschlinkert) +* [GitHub Profile](https://github.com/jonschlinkert) +* [Twitter Profile](https://twitter.com/jonschlinkert) ### License -Copyright © 2017, [Jon Schlinkert](https://github.com/jonschlinkert). +Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert). Released under the [MIT License](LICENSE). *** -_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on May 22, 2017._ \ No newline at end of file +_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on April 19, 2018._ \ No newline at end of file diff --git a/example.js b/example.js index 3a400a8..622c6ac 100644 --- a/example.js +++ b/example.js @@ -1,16 +1,18 @@ // default cwd is `~/data-store/` -var store = require('./')('app', {cwd: 'actual'}); +const Store = require('./'); +const store = new Store('app', { cwd: 'test/actual' }); -store - .set('a', 'b') - .set({c: 'd'}) - .set('e.f', 'g') +store.set('a', 'b'); +store.set({ c: 'd' }); +store.set('e.f', 'g') console.log(store.get('e.f')); //=> 'g' console.log(store.get()); -//=> {name: 'app', data: {a: 'b', c: 'd', e: {f: 'g' }}} +//=> { name: 'app', data: { a: 'b', c: 'd', e: { f: 'g' } } } console.log(store.data); -//=> {a: 'b', c: 'd', e: {f: 'g'}} +//=> { a: 'b', c: 'd', e: { f: 'g' } } + +console.log(store.keys) diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 459a7e2..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -var gulp = require('gulp'); -var mocha = require('gulp-mocha'); -var istanbul = require('gulp-istanbul'); -var eslint = require('gulp-eslint'); - -var lint = ['index.js', 'utils.js']; - -gulp.task('coverage', function() { - return gulp.src(lint) - .pipe(istanbul()) - .pipe(istanbul.hookRequire()); -}); - -gulp.task('mocha', ['coverage'], function() { - return gulp.src('test.js') - .pipe(mocha({reporter: 'spec'})) - .pipe(istanbul.writeReports()); -}); - -gulp.task('eslint', function() { - return gulp.src(lint.concat('test.js')) - .pipe(eslint()) -}); - -gulp.task('default', ['mocha', 'eslint']); diff --git a/index.js b/index.js index 401f426..6db8429 100644 --- a/index.js +++ b/index.js @@ -1,32 +1,25 @@ 'use strict'; -/** - * Module dependencies - */ - -var path = require('path'); -var util = require('util'); -var base = require('cache-base'); -var Base = base.namespace('cache'); -var debug = require('debug')('data-store'); -var proto = Base.prototype; -var utils = require('./utils'); - -/** - * Expose `Store` - */ - -module.exports = Store; +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const assert = require('assert'); +const Emitter = require('events'); +const flatten = (...args) => [].concat.apply([], args); +const unique = arr => arr.filter((v, i) => arr.indexOf(v) === i); +const mode = opts => opts.mode || (parseInt('0777', 8) & ~process.umask()); +const strip = str => str.replace(/\\(?=\.)/g, ''); +const split = str => str.split(/(? '~/data-store/a.json' * - * var store = require('data-store')('abc', { + * const store = require('data-store')('abc', { * cwd: 'test/fixtures' * }); * //=> './test/fixtures/abc.json' @@ -39,355 +32,291 @@ module.exports = Store; * @api public */ -function Store(name, options) { - if (!(this instanceof Store)) { - return new Store(name, options); +class Store extends Emitter { + constructor(name, options = {}, defaults = {}) { + assert.equal(typeof name, 'string', 'expected options.name to be a string'); + super(); + this.name = name; + this.options = options; + this.indent = this.options.indent != null ? this.options.indent : 2; + this.cwd = this.options.cwd || os.homedir(); + this.folder = this.options.folder || '.data-store'; + this.dirname = path.join(this.cwd, this.folder); + this.path = this.options.path || path.join(this.dirname, this.name + '.json'); + this.relative = path.relative(process.cwd(), this.path); + this.data = Object.assign({}, defaults, this.data); } - if (typeof name !== 'string') { - options = name; - name = null; - } - - Base.call(this); - this.isStore = true; - this.options = options || {}; - this.initStore(name); -} + /** + * Get and set the `store.data` object that is persisted. + */ -/** - * Inherit `Base` - */ - -util.inherits(Store, Base); - -/** - * Initialize store defaults - */ - -Store.prototype.initStore = function(name) { - this.name = name || utils.project(process.cwd()); - this.cwd = utils.resolve(this.options.cwd || '~/.config/data-store'); - this.path = this.options.path || path.resolve(this.cwd, this.name + '.json'); - this.relative = path.relative(process.cwd(), this.path); - - debug('Initializing store <%s>', this.path); - - this.data = this.readFile(this.path); - this.define('cache', utils.clone(this.data)); - this.on('set', function() { + set data(val) { + this._data = val; this.save(); - }.bind(this)); -}; - -/** - * Create a namespaced "sub-store" that persists data to its file - * in a sub-folder of the same directory as the "parent" store. - * - * ```js - * store.create('foo'); - * store.foo.set('a', 'b'); - * console.log(store.foo.get('a')); - * //=> 'b' - * ``` - * @param {String} `name` The name of the sub-store. - * @param {Object} `options` - * @return {Object} Returns the sub-store instance. - * @api public - */ - -Store.prototype.create = function(name, options) { - if (utils.isStore(this, name)) { - return this[name]; } - utils.validateName(this, name); - - var self = this; - var cwd = path.join(path.dirname(this.path), this.name); - var substore = new Store(name, { cwd: cwd }); - this[name] = substore; - - substore.on('set', function(key, val) { - self.set([name, key], val); - }); - - return substore; -}; - -/** - * Assign `value` to `key` and save to disk. Can be - * a key-value pair or an object. - * - * ```js - * // key, value - * store.set('a', 'b'); - * //=> {a: 'b'} - * - * // extend the store with an object - * store.set({a: 'b'}); - * //=> {a: 'b'} - * - * // extend the the given value - * store.set('a', {b: 'c'}); - * store.set('a', {d: 'e'}, true); - * //=> {a: {b 'c', d: 'e'}} - * - * // overwrite the the given value - * store.set('a', {b: 'c'}); - * store.set('a', {d: 'e'}); - * //=> {d: 'e'} - * ``` - * @name .set - * @param {String} `key` - * @param {any} `val` The value to save to `key`. Must be a valid JSON type: String, Number, Array or Object. - * @return {Object} `Store` for chaining - * @api public - */ - -/** - * Add or append an array of unique values to the given `key`. - * - * ```js - * store.union('a', ['a']); - * store.union('a', ['b']); - * store.union('a', ['c']); - * store.get('a'); - * //=> ['a', 'b', 'c'] - * ``` - * - * @param {String} `key` - * @return {any} The array to add or append for `key`. - * @api public - */ - -Store.prototype.union = function(key, val) { - utils.union(this.cache, key, val); - this.emit('union', key, val); - this.save(); - return this; -}; - -/** - * Get the stored `value` of `key`, or return the entire store - * if no `key` is defined. - * - * ```js - * store.set('a', {b: 'c'}); - * store.get('a'); - * //=> {b: 'c'} - * - * store.get(); - * //=> {b: 'c'} - * ``` - * - * @name .get - * @param {String} `key` - * @return {any} The value to store for `key`. - * @api public - */ - -/** - * Returns `true` if the specified `key` has truthy value. - * - * ```js - * store.set('a', 'b'); - * store.set('c', null); - * store.has('a'); //=> true - * store.has('c'); //=> false - * store.has('d'); //=> false - * ``` - * @name .has - * @param {String} `key` - * @return {Boolean} Returns true if `key` has - * @api public - */ + get data() { + return this._data || (this._data = this.load()); + } -/** - * Returns `true` if the specified `key` exists. - * - * ```js - * store.set('a', 'b'); - * store.set('b', false); - * store.set('c', null); - * store.set('d', true); - * - * store.hasOwn('a'); //=> true - * store.hasOwn('b'); //=> true - * store.hasOwn('c'); //=> true - * store.hasOwn('d'); //=> true - * store.hasOwn('foo'); //=> false - * ``` - * - * @param {String} `key` - * @return {Boolean} Returns true if `key` exists - * @api public - */ + /** + * Assign `value` to `key` and save to disk. Can be + * a key-value pair or an object. + * + * ```js + * // key, value + * store.set('a', 'b'); + * //=> {a: 'b'} + * + * // extend the store with an object + * store.set({a: 'b'}); + * //=> {a: 'b'} + * ``` + * @name .set + * @param {String} `key` + * @param {any} `val` The value to save to `key`. Must be a valid JSON type: String, Number, Array or Object. + * @return {Object} `Store` for chaining + * @api public + */ + + set(key, val) { + if (typeof key !== 'string') { + for (const k of Array.isArray(key) ? key : Object.keys(key)) { + this.set(k, key[k]); + } + return this; + } + assert.equal(typeof key, 'string', 'expected key to be a string'); + set(this.data, key, val); + this.emit('set', key, val); + this.save(); + return this; + } -Store.prototype.hasOwn = function(key) { - var val; - if (key.indexOf('.') === -1) { - val = this.cache.hasOwnProperty(key); - } else { - val = utils.hasOwn(this.cache, key); + /** + * Get the stored `value` of `key`, or return the entire store + * if no `key` is defined. + * + * ```js + * store.set('a', {b: 'c'}); + * store.get('a'); + * //=> {b: 'c'} + * + * store.get(); + * //=> {b: 'c'} + * ``` + * @name .union + * @param {String} `key` + * @return {any} The value to store for `key`. + * @api public + */ + + union(key, ...rest) { + assert.equal(typeof key, 'string', 'expected key to be a string'); + let arr = this.get(key); + if (arr == null) arr = []; + if (!Array.isArray(arr)) arr = [arr]; + this.set(key, unique(flatten(...arr, ...rest))); + return this; } - return val; -}; -/** - * Persist the store to disk. - * - * ```js - * store.save(); - * ``` - * @param {String} `dest` Optionally define an alternate destination file path. - * @api public - */ + /** + * Get the stored `value` of `key`, or return the entire store + * if no `key` is defined. + * + * ```js + * store.set('a', {b: 'c'}); + * store.get('a'); + * //=> {b: 'c'} + * + * store.get(); + * //=> {b: 'c'} + * ``` + * + * @name .get + * @param {String} `key` + * @return {any} The value to store for `key`. + * @api public + */ + + get(key) { + return key ? get(this.data, key) : this.data; + } -Store.prototype.save = function(dest) { - this.data = this.cache; - writeJson(dest || this.path, this.cache, this.options.indent); - return this; -}; + /** + * Returns `true` if the specified `key` has a value. + * + * ```js + * store.set('a', 'b'); + * store.set('c', null); + * store.has('a'); //=> true + * store.has('c'); //=> false + * store.has('d'); //=> false + * ``` + * @name .has + * @param {String} `key` + * @return {Boolean} Returns true if `key` has + * @api public + */ + + has(key) { + assert.equal(typeof key, 'string', 'expected key to be a string'); + return typeof get(this.data, key) !== 'undefined'; + } -/** - * Clear in-memory cache. - * - * ```js - * store.clear(); - * ``` - * @api public - */ + /** + * Returns `true` if the specified `key` exists. + * + * ```js + * store.set('a', 'b'); + * store.set('b', false); + * store.set('c', null); + * store.set('d', true); + * + * store.hasOwn('a'); //=> true + * store.hasOwn('b'); //=> true + * store.hasOwn('c'); //=> true + * store.hasOwn('d'); //=> true + * store.hasOwn('foo'); //=> false + * ``` + * + * @param {String} `key` + * @return {Boolean} Returns true if `key` exists + * @api public + */ + + hasOwn(key) { + assert.equal(typeof key, 'string', 'expected key to be a string'); + return hasOwn(this.data, key); + } -Store.prototype.clear = function() { - this.cache = {}; - this.data = {}; - return this; -}; + /** + * 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. + * + * **Note that to delete the entire store you must pass `{force: true}`** + * + * ```js + * store.del(); + * + * // to delete paths outside cwd + * store.del({force: true}); + * ``` + * + * @param {String|Array|Object} `keys` Keys to remove, or options. + * @param {Object} `options` + * @api public + */ + + del(key) { + if (!key) key = this.keys; + if (Array.isArray(key)) { + for (const k of key) this.del(k); + return this; + } + assert.equal(typeof key, 'string', 'expected key to be a string'); + delete this.data[key]; + this.emit('del', key); + this.save(); + return this; + } -/** - * 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. - * - * **Note that to delete the entire store you must pass `{force: true}`** - * - * ```js - * store.del(); - * - * // to delete paths outside cwd - * store.del({force: true}); - * ``` - * - * @param {String|Array|Object} `keys` Keys to remove, or options. - * @param {Object} `options` - * @api public - */ + /** + * Stringify the store + */ -Store.prototype.del = function(keys, options) { - var isArray = Array.isArray(keys); - if (typeof keys === 'string' || isArray) { - keys = utils.arrayify(keys); - } else { - options = keys; - keys = null; + json(replacer = null, space = this.indent) { + return JSON.stringify(this.data, replacer, space); } - options = options || {}; - - if (keys) { - keys.forEach(function(key) { - proto.del.call(this, key); - }.bind(this)); - this.save(); + /** + * Persist the store to the file system. + * + * ```js + * store.save(); + * ``` + * @api public + */ + + save() { + mkdirSync(path.dirname(this.path), this.options.mkdir); + fs.writeFileSync(this.path, this.json(), { mode: 0o0600 }); return this; } - if (options.force !== true) { - throw new Error('options.force is required to delete the entire cache.'); + /** + * Load the store. + * @return {Object} + */ + + load() { + try { + return JSON.parse(fs.readFileSync(this.path)); + } catch (err) { + if (err.code === 'EACCES') { + err.message += '\ndata-store does not have permission to load this file\n'; + throw err; + } + if (err.code === 'ENOENT' || err.name === 'SyntaxError') { + this.data = {}; + return {}; + } + } } - keys = Object.keys(this.cache); - this.clear(); - - // if no keys are passed, delete the entire store - utils.del.sync(this.path, options); - keys.forEach(function(key) { - this.emit('del', key); - }.bind(this)); - return this; -}; + get keys() { + return Object.keys(this.data); + } +} /** - * Returns an array of all Store properties. + * Create a directory and any intermediate directories that might exist. */ -utils.define(Store.prototype, 'keys', { - configurable: true, - enumerable: true, - set: function(keys) { - utils.define(this, 'keys', keys); - }, - get: function fn() { - if (fn.keys) return fn.keys; - fn.keys = []; - for (var key in this) fn.keys.push(key); - return fn.keys; +function mkdirSync(dirname, options = {}) { + assert.equal(typeof dirname, 'string', 'expected a string'); + const opts = Object.assign({ cwd: process.cwd() }, options); + const segs = path.relative(opts.cwd, dirname).split(path.sep); + for (let i = 0; i <= segs.length; i++) { + try { + fs.mkdirSync(path.join(opts.cwd, ...segs.slice(0, i)), mode(opts)); + } catch (err) { + if (err.code !== 'EEXIST') { + throw err; + } + } } -}); - -/** - * Define a non-enumerable property on the instance. - * - * @param {String} `key` - * @param {any} `value` - * @return {Object} Returns the instance for chaining. - * @api public - */ + return dirname; +} -Store.prototype.define = function(key, value) { - utils.define(this, key, value); - return this; -}; +function get(obj = {}, prop = '') { + return obj[prop] || split(prop).reduce((acc, k) => acc && acc[strip(k)], obj); +} -/** - * Read JSON files. - * - * @param {String} `fp` - * @return {Object} - */ +function set(obj = {}, prop = '', val) { + return split(prop).reduce((acc, k, i, arr) => { + return (acc[k] = arr.length - 1 > i ? (acc[k] || {}) : val); + }, obj); +} -Store.prototype.readFile = function(filepath) { - try { - var str = utils.fs.readFileSync(path.resolve(filepath), 'utf8'); - this.loadedConfig = true; - return JSON.parse(str); - } catch (err) {} - this.loadedConfig = false; - return {}; -}; +function hasOwn(obj = {}, prop = '') { + if (!prop) return false; + if (obj.hasOwnProperty(prop)) return true; + if (prop.indexOf('.') === -1) { + return obj.hasOwnProperty(prop); + } + const segs = split(prop); + const last = segs.pop(); + const val = get(obj, segs.join('.')); + if (val && typeof val === 'object') { + return val.hasOwnProperty(last); + } + return false; +} /** - * Synchronously write files to disk, also creating any - * intermediary directories if they don't exist. - * - * @param {String} `dest` - * @param {String} `str` - * @param {Number} `indent` Indent passed to JSON.stringify (default 2) + * Expose `Store` */ -function writeJson(dest, str, indent) { - if (typeof indent === 'undefined' || indent === null) { - indent = 2; - } - var dir = path.dirname(dest); - try { - if (!utils.fs.existsSync(dir)) { - utils.mkdirp.sync(dir); - } - utils.fs.writeFileSync(dest, JSON.stringify(str, null, indent)); - } catch (err) { - err.origin = __filename; - err.reason = 'data-store cannot write to: ' + dest; - throw new Error(err); - } -} +module.exports = Store; diff --git a/package.json b/package.json index 1b2ca06..b389c6d 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,9 @@ "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "contributors": [ "Brian Woodward (https://twitter.com/doowb)", - "Charlike Mike Reagent (https://i.am.charlike.online)", - "Jon Schlinkert (http://twitter.com/jonschlinkert)" + "Jamen Marz (jamenmarz.com)", + "Jon Schlinkert (http://twitter.com/jonschlinkert)", + "Olsten Larck (https://i.am.charlike.online)" ], "repository": "jonschlinkert/data-store", "bugs": { @@ -15,38 +16,19 @@ }, "license": "MIT", "files": [ - "index.js", - "utils.js" + "index.js" ], "main": "index.js", "engines": { - "node": ">=0.10.0" + "node": ">=4" }, "scripts": { "test": "mocha" }, - "dependencies": { - "cache-base": "^1.0.0", - "clone-deep": "^0.3.0", - "debug": "^2.6.8", - "define-property": "^1.0.0", - "extend-shallow": "^2.0.1", - "graceful-fs": "^4.1.11", - "has-own-deep": "^0.1.4", - "lazy-cache": "^2.0.2", - "mkdirp": "^0.5.1", - "project-name": "^0.2.6", - "resolve-dir": "^1.0.0", - "rimraf": "^2.6.1", - "union-value": "^1.0.0" - }, "devDependencies": { - "gulp": "^3.9.1", - "gulp-eslint": "^3.0.1", - "gulp-format-md": "^0.1.12", - "gulp-istanbul": "^1.1.1", - "gulp-mocha": "^3.0.1", - "mocha": "^3.4.1" + "delete": "^1.1.0", + "gulp-format-md": "^1.0.0", + "mocha": "^3.5.3" }, "keywords": [ "app", @@ -83,16 +65,14 @@ "gulp-format-md" ], "related": { - "highlight": "base-store", "list": [ - "base-store", + "base", "cache-base", "get-value", + "has-value", "set-value" ] }, - "reflinks": [ - ], "lint": { "reflinks": true } diff --git a/test.js b/test/test.js similarity index 58% rename from test.js rename to test/test.js index cd174d2..2153338 100644 --- a/test.js +++ b/test/test.js @@ -1,28 +1,22 @@ -/*! - * data-store - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - 'use strict'; require('mocha'); -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); -var utils = require('./utils'); -var Store = require('./'); -var store; +const fs = require('fs'); +const path = require('path'); +const del = require('delete'); +const assert = require('assert'); +const Store = require('..'); +const tests = (...args) => path.resolve(__dirname, ...args); +let store; describe('store', function() { beforeEach(function() { - store = new Store('abc'); + store = new Store('abc', { base: tests() }); }); afterEach(function() { store.data = {}; - store.del({force: true}); + return del(tests('actual')); }); describe('create', function() { @@ -36,64 +30,26 @@ describe('store', function() { assert.equal(store.data.foo, 'bar'); }); - it('should return an instance without `new`:', function() { - store = Store('abc'); - store.set('foo', 'zzz'); - assert(store.data.hasOwnProperty('foo')); - assert.equal(store.data.foo, 'zzz'); - }); + // it.only('should create a store at the given `base`', function() { + // store = new Store('abc', { base: tests('actual') }); - it('should create a store at the given `cwd`', function() { - store = new Store('abc', {cwd: 'actual'}); - - store.set('foo', 'bar'); - assert.equal(path.basename(store.path), 'abc.json'); - assert(store.data.hasOwnProperty('foo')); - assert.equal(store.data.foo, 'bar'); - assert.equal(fs.existsSync(path.join(__dirname, 'actual/abc.json')), true); - }); + // store.set('foo', 'bar'); + // assert.equal(path.basename(store.path), 'abc.json'); + // assert.equal(store.path, tests('actual/abc.json')); + // assert(store.data.hasOwnProperty('foo')); + // assert.equal(store.data.foo, 'bar'); + // console.log(store.path) + // assert.equal(fs.existsSync(tests('actual/abc.json')), true); + // }); it('should create a store using the given `indent` value', function() { - store = new Store('abc', {cwd: 'actual', indent: 0}); + store = new Store('abc', { base: tests('actual'), indent: 0 }); store.set('foo', 'bar'); - var contents = fs.readFileSync(path.join(__dirname, 'actual/abc.json'), 'utf8'); + const contents = fs.readFileSync(store.path, 'utf8'); assert.equal(contents, '{"foo":"bar"}'); }); }); - describe('sub-store', function() { - it('should create a "sub-store" with the given name', function() { - var created = store.create('created'); - assert.equal(created.name, 'created'); - }); - - it('should create a "sub-store" with the project name when no name is passed', function() { - var foo = store.create(); - assert.equal(foo.name, 'data-store'); - }); - - it('should set values on a sub-store', function() { - var foo = store.create('created'); - foo.set('one', 'two'); - assert.equal(foo.get('one'), 'two'); - }); - - it('should expose sub-store data on parent store', function() { - var foo = store.create('created'); - foo.set('one', 'two'); - assert.equal(foo.get('one'), 'two'); - - assert.equal(store.get('created.one'), 'two'); - }); - - it('should work with dots', function() { - var foo = store.create('one.two'); - foo.set('a', 'b'); - assert.equal(foo.get('a'), 'b'); - assert.equal(store.get('one.two.a'), 'b'); - }); - }); - describe('set', function() { it('should `.set()` a value on the store', function() { store.set('one', 'two'); @@ -101,21 +57,21 @@ describe('store', function() { }); it('should `.set()` an object', function() { - store.set({four: 'five', six: 'seven'}); + store.set({ four: 'five', six: 'seven' }); assert.equal(store.data.four, 'five'); assert.equal(store.data.six, 'seven'); }); it('should `.set()` a nested value', function() { - store.set('a.b.c.d', {e: 'f'}); + store.set('a.b.c.d', { e: 'f' }); assert.equal(store.data.a.b.c.d.e, 'f'); }); - it('should not save data that is added directly to `storedata`', function() { + it('should save data that is added directly to `storedata`', function() { store.data.foo = 'bar'; - store.set('a.b.c.d', {e: 'f'}); + store.set('a.b.c.d', { e: 'f' }); assert.equal(store.data.a.b.c.d.e, 'f'); - assert.equal(typeof store.data.foo, 'undefined'); + assert.equal(store.data.foo, 'bar'); }); }); @@ -158,9 +114,9 @@ describe('store', function() { }); it('should return true if a nested key `.has()` on the store', function() { - store.set('a.b.c.d', {x: 'zzz'}); - store.set('a.b.c.e', {f: null}); - store.set('a.b.g.j', {k: undefined}); + store.set('a.b.c.d', { x: 'zzz' }); + store.set('a.b.c.e', { f: null }); + store.set('a.b.g.j', { k: undefined }); assert(store.has('a.b.c.d')); assert(store.has('a.b.c.d.x')); @@ -176,8 +132,8 @@ describe('store', function() { }); }); - describe('hasOwn', function() { - it('should return true if a key exists `.hasOwn()` on the store', function() { + describe('hasOwn', function() { + it('should return true if a key exists on the store', function() { store.set('foo', 'bar'); store.set('baz', null); store.set('qux', undefined); @@ -188,10 +144,25 @@ describe('store', function() { assert(store.hasOwn('qux')); }); + it('should work with escaped keys', function() { + store.set('foo\\.baz', 'bar'); + store.set('baz', null); + store.set('qux', undefined); + + assert(!store.hasOwn('foo')); + assert(!store.hasOwn('bar')); + assert(store.hasOwn('foo.baz')); + assert(store.hasOwn('baz')); + assert(store.hasOwn('qux')); + + store.set('foo\\.bar.baz\\.qux', 'fez'); + assert(store.hasOwn('foo\\.bar.baz\\.qux')); + }); + it('should return true if a nested key exists `.hasOwn()` on the store', function() { - store.set('a.b.c.d', {x: 'zzz'}); - store.set('a.b.c.e', {f: null}); - store.set('a.b.g.j', {k: undefined}); + store.set('a.b.c.d', { x: 'zzz' }); + store.set('a.b.c.e', { f: null }); + store.set('a.b.g.j', { k: undefined }); assert(store.hasOwn('a.b.c.d')); assert(store.hasOwn('a.b.c.d.x')); @@ -214,7 +185,7 @@ describe('store', function() { }); it('should `.get()` a nested value', function() { - store.set({a: {b: {c: 'd'}}}); + store.set({ a: { b: { c: 'd' } } }); assert.equal(store.get('a.b.c'), 'd'); }); }); @@ -235,11 +206,11 @@ describe('store', function() { assert(!store.data.hasOwnProperty('c')); }); - it('should `.del()` multiple stored values', function() { + it('should delete multiple stored values', function() { store.set('a', 'b'); store.set('c', 'd'); store.set('e', 'f'); - store.del(['a', 'c', 'e']); + ['a', 'c', 'e'].forEach(v => store.del(v)); assert.deepEqual(store.data, {}); }); }); @@ -252,22 +223,21 @@ describe('events', function() { afterEach(function() { store.data = {}; - store.del({force: true}); }); describe('set', function() { it('should emit `set` when an object is set:', function() { - var keys = []; + const keys = []; store.on('set', function(key) { keys.push(key); }); - store.set({a: {b: {c: 'd'}}}); + store.set({ a: { b: { c: 'd' } } }); assert.deepEqual(keys, ['a']); }); it('should emit `set` when a key/value pair is set:', function() { - var keys = []; + const keys = []; store.on('set', function(key) { keys.push(key); @@ -278,83 +248,54 @@ describe('events', function() { }); it('should emit `set` when an object value is set:', function() { - var keys = []; + const keys = []; store.on('set', function(key) { keys.push(key); }); - store.set('a', {b: 'c'}); + store.set('a', { b: 'c' }); assert.deepEqual(keys, ['a']); }); it('should emit `set` when an array of objects is passed:', function() { - var keys = []; + const keys = []; store.on('set', function(key) { keys.push(key); }); - store.set([{a: 'b'}, {c: 'd'}]); + store.set([{ a: 'b' }, { c: 'd' }]); assert.deepEqual(keys, ['a', 'c']); }); }); - describe('has', function() { - it('should emit `has`:', function(done) { - var keys = []; - - store.on('has', function(val) { - assert(val); - done(); - }); - - store.set('a', 'b'); - store.has('a'); - }); - }); - describe('del', function() { - it('should emit `del` when a value is delted:', function(done) { + it('should emit `del` when a value is delted:', function(cb) { store.on('del', function(keys) { assert.deepEqual(keys, 'a'); - assert(typeof store.get('a') === 'undefined'); - done(); + assert.equal(typeof store.get('a'), 'undefined'); + cb(); }); - store.set('a', {b: 'c'}); - assert.deepEqual(store.get('a'), {b: 'c'}); + store.set('a', { b: 'c' }); + assert.deepEqual(store.get('a'), { b: 'c' }); store.del('a'); }); - it('should emit deleted keys on `del`:', function(done) { - var arr = []; - - store.on('del', function(key) { - arr.push(key); - assert(Object.keys(store.data).length === 0); - }); + it('should emit deleted keys on `del`:', function(cb) { + const arr = []; + store.on('del', key => arr.push(key)); store.set('a', 'b'); store.set('c', 'd'); store.set('e', 'f'); - store.del({force: true}); - assert.deepEqual(arr, ['a', 'c', 'e']); - done(); - }); - it('should throw an error if force is not passed', function(cb) { - store.set('a', 'b'); - store.set('c', 'd'); - store.set('e', 'f'); + store.del(); - try { - store.del(); - cb(new Error('expected an error')); - } catch (err) { - assert.equal(err.message, 'options.force is required to delete the entire cache.'); - cb(); - } + assert.deepEqual(arr, ['a', 'c', 'e']); + assert.equal(Object.keys(store.data).length, 0); + cb(); }); }); }); diff --git a/utils.js b/utils.js deleted file mode 100644 index fb5277f..0000000 --- a/utils.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -var utils = module.exports = require('lazy-cache')(require); -var fn = require; -require = utils; // eslint-disable-line - -/** - * Utils - */ - -require('clone-deep', 'clone'); -require('extend-shallow', 'extend'); -require('define-property', 'define'); -require('graceful-fs', 'fs'); -require('has-own-deep', 'hasOwn'); -require('mkdirp', 'mkdirp'); -require('project-name', 'project'); -require('resolve-dir', 'resolve'); -require('rimraf', 'del'); -require('union-value', 'union'); -require = fn; // eslint-disable-line - -utils.noop = function() { - return; -}; - -utils.last = function(arr) { - return arr[arr.length - 1]; -}; - -utils.arrayify = function(val) { - return val ? (Array.isArray(val) ? val : [val]) : []; -}; - -/** - * Throw an error if sub-store `name` is already a key on `store`. - * - * @param {Object} `store` - * @param {String} `name` - */ - -utils.validateName = function(store, name) { - if (~store.keys.indexOf(name) && !utils.isStore(store, name)) { - throw utils.formatConflictError(name); - } -}; - -/** - * Return true if `name` is a store object. - */ - -utils.isStore = function(store, name) { - return !!store[name] - && (typeof store[name] === 'object') - && store[name].isStore === true; -}; - -/** - * Format the error used when sub-store `name` is - * invalid. - * - * @param {String} `name` - */ - -utils.formatConflictError = function(name) { - var msg = 'Cannot create store: ' - + '"' + name + '", since ' - + '"' + name + '" is a reserved property key. ' - + 'Please choose a different store name.'; - return new Error(msg); -};