876 |
877 | You signed in with another tab or window. Reload to refresh your session.
878 | You signed out in another tab or window. Reload to refresh your session.
879 |
880 |
881 |
882 |
883 |
884 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8"
4 | - "6"
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.enable": false
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Tobe Osakwe 2016
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # feathers-seeder
2 |
3 | [](https://www.npmjs.com/package/feathers-seeder)
4 | [](https://travis-ci.org/thosakwe/feathers-seeder)
5 |
6 | Straightforward data seeder for FeathersJS services.
7 |
8 | * [About](#about)
9 | * [Installation](#installation)
10 | * [Configuration](#configuration)
11 | * [Example](#example)
12 | * [Thanks](#thanks)
13 |
14 | # About
15 | FeathersJS is a dynamic application framework that makes it easy to prototype secure real-time Node applications.
16 |
17 | It has uniform support for a wide variety of database options, both persistent and in-memory. However, it can often be a pain to bootstrap databases, especially for secure backends where creation permissions are locked down tightly.
18 |
19 | feathers-seeder attempts to solve that problem by allowing you to fill your database (specifically feathers-memory) with similar of identical data every time your application is launched.
20 |
21 | This can really be useful for projects using feathers-memory, or feathers-nedb to test applications.
22 |
23 | # Installation
24 | These magic words will do the trick:
25 | > npm install --save feathers-seeder
26 |
27 | # Usage
28 | 1. [Configure](#configuration) the seeder.
29 | 2. Call `app.seed()`. This will return a Promise.
30 |
31 | Example:
32 | ```js
33 | const app = feathers().configure(hooks());
34 | app
35 | .configure(seeder(app.get('seeder')))
36 | .seed()
37 | .then(() => {
38 | app.listen(3000);
39 | });
40 | ```
41 |
42 | # Configuration
43 | feathers-seeder should be called as a function, with a single configuration object as a parameter.
44 |
45 | To enable debug output logging, set the [`DEBUG`](https://github.com/visionmedia/debug#usage) env variable accordingly (e.g. `DEBUG=*`)
46 |
47 | All data in the service will be wiped before seeding, unless `delete` is set to `false`. It is `true` by default.
48 |
49 | To disable feathers-seeder (i.e. at production), you can simply set the value `disabled: true` on your configuration object. It is `false` by default.
50 |
51 | You can pass service parameters to each service via a `params` object. This can be useful for projects that have services locked down via hooks.
52 |
53 | This configuration object should contain an array called `services`. Each object in this array will have a key pointing to the path of a registered Feathers service, and its value will be a configuration detail as follows.
54 |
55 | Example:
56 |
57 | ```js
58 | {
59 | delete: true,
60 | disabled: false,
61 | params: { provider: 'rest' },
62 | services: [
63 | {
64 | path: 'users',
65 | template: {
66 | text: "Hello, world! I am {{name.firstName}}."
67 | }
68 | }
69 | ]
70 | }
71 | ```
72 |
73 | **Configuration options:**
74 | * count: `Number` - The number of times to generate objects. If you provide a `template` or `template(s)`, then `count` objects adhering to the template(s) will be generated. **Default = `1`**.
75 |
76 | * delete: `Boolean` - If set to true, then existing data for this service will be deleted before seeding. *Overrides global `delete` setting*.
77 |
78 | * disabled: `Boolean` - Disables seeding for this service.
79 |
80 | * params: `Object` - Additional parameters to pass to service methods. This is merged with (and supersedes) the global `params` configuration via Object.assign.
81 |
82 | * path: `String` - The path to the service to be seeded.
83 |
84 | * randomize: `Boolean` - (default true) - The seeder will pick a random template to generate the item for the service.
85 |
86 | * template: `Object` - A template object defining the structure of each object generated. For dynamic data, you can provide:
87 | - Template strings, as feathers-seeder uses **[@marak/Faker.js](https://github.com/marak/Faker.js/)** internally
88 | - Custom parameterless functions
89 |
90 | Example:
91 | ```js
92 | {
93 | template: {
94 | username: "{{internet.userName}}",
95 | password: "{{internet.password}}"
96 | name: "{{name.firstName}} {{name.lastName}}",
97 | email: "{{internet.email}}",
98 | lastLogin: () => moment().subtract(7, 'days').format()
99 | }
100 | }
101 | ```
102 |
103 | * templates: `Object[]` - An array of templates. If the option `randomize` is true, each time an object is to be generated,
104 | a random template will be chosen. Otherwise, all templates will be generated for the service.
105 |
106 | * callback: `Function(obj, cb)` - You can register a callback each time a database record is created. This allows you to seed
107 | nested services. :) *Callbacks MUST return a `Promise`.*
108 |
109 | ```js
110 | {
111 | services: [
112 | {
113 | count: 100,
114 | path: 'users',
115 | template: {
116 | name: '{{name.firstName}} {{name.lastName}}'
117 | },
118 |
119 | callback(user, seed) {
120 | console.info(`Happy birthday to ${user.name}!`);
121 |
122 | // Call `seed` with regular service configuration.
123 | // This will return a Promise.
124 |
125 | return seed({
126 | path: 'users/:userId/posts',
127 | params: {
128 | userId: user._id
129 | },
130 | template: {
131 | text: "It's my birthday! :)"
132 | }
133 | });
134 | }
135 | }
136 | ]
137 | }
138 | ```
139 |
140 | # Example
141 | ```js
142 | import feathers from 'feathers';
143 | import hooks from 'feathers-hooks';
144 | import memory from 'feathers-memory';
145 | import seeder from 'feathers-seeder';
146 |
147 | const options = {
148 | services: [
149 | {
150 | path: 'users',
151 | count: 10,
152 | template: {
153 | name: '{{name.firstName}} {{name.lastName}}'
154 | }
155 | }
156 | ]
157 | };
158 |
159 | const app = feathers()
160 | .use('/users', memory())
161 | .configure(seeder(options));
162 |
163 | app.seed().then(() => {
164 | app.service('users').find().then(users => console.log(users));
165 | });
166 |
167 | ```
168 |
169 | # Thanks
170 | Thank you for using feathers-seeder. If you find any bugs, feel free to [report an issue]().
171 |
172 | Follow me on Twitter: [@thosakwe](https://twitter.com/thosakwe)
173 |
174 | Or check out [my blog](http://blog.thosakwe.com).
175 |
--------------------------------------------------------------------------------
/lib/compiler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
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 | var _faker = require('faker');
12 |
13 | var _faker2 = _interopRequireDefault(_faker);
14 |
15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16 |
17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
18 |
19 | var debug = require('debug')('feathers-seeder');
20 |
21 | var Compiler = function () {
22 | function Compiler() {
23 | _classCallCheck(this, Compiler);
24 | }
25 |
26 | _createClass(Compiler, [{
27 | key: 'compile',
28 | value: function compile(template) {
29 | var _this = this;
30 |
31 | debug('About to compile template: ', template);
32 |
33 | var result = {};
34 | Object.keys(template).forEach(function (key) {
35 | var value = template[key];
36 | result[key] = _this._populate(key, value);
37 | });
38 |
39 | return result;
40 | }
41 | }, {
42 | key: '_populate',
43 | value: function _populate(key, value) {
44 | var _this2 = this;
45 |
46 | debug('Populating key: ' + key + ' from value: ' + value);
47 |
48 | if (typeof value === 'number' || typeof value === 'boolean' || value instanceof Date || value === null || value === undefined) {
49 | debug('Value is a primitive.');
50 |
51 | return value;
52 | } else if (value instanceof String || typeof value === 'string') {
53 | debug('Value is a string.');
54 |
55 | return _faker2.default.fake(value);
56 | } else if (Array.isArray(value)) {
57 | debug('Value is an array.');
58 |
59 | return value.map(function (x) {
60 | return _this2._populate(key, x);
61 | });
62 | } else if (typeof value === 'function') {
63 | return value();
64 | }
65 | // Otherwise, this is an object, and potentially a template itself
66 | else {
67 | debug('Value is a ' + (typeof value === 'undefined' ? 'undefined' : _typeof(value)));
68 |
69 | return this.compile(value);
70 | }
71 | }
72 | }]);
73 |
74 | return Compiler;
75 | }();
76 |
77 | exports.default = Compiler;
78 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = seeder;
7 |
8 | var _errors = require('@feathersjs/errors');
9 |
10 | var _errors2 = _interopRequireDefault(_errors);
11 |
12 | var _seeder = require('./seeder');
13 |
14 | var _seeder2 = _interopRequireDefault(_seeder);
15 |
16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17 |
18 | var debug = require('debug')('feathers-seeder');
19 |
20 | function seeder() {
21 | var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
22 |
23 | if (opts === false || opts.disabled === true) {
24 | return function () {
25 | this.seed = function () {
26 | debug('Seeder is disabled, not modifying database.');
27 |
28 | return Promise.resolve([]);
29 | };
30 | };
31 | }
32 |
33 | if (!(opts.services instanceof Array)) {
34 | throw new Error('You must include an array of services to be seeded.');
35 | }
36 |
37 | return function () {
38 | var app = this;
39 | var seeder = new _seeder2.default(app, opts);
40 |
41 | app.seed = function () {
42 | return seeder.seedApp().then().catch(function (err) {
43 | debug('Seeding error: ' + err);
44 |
45 | throw new _errors2.default.GeneralError(err);
46 | });
47 | };
48 | };
49 | }
50 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/lib/seeder.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | 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; }; }();
8 |
9 | var _compiler = require('./compiler');
10 |
11 | var _compiler2 = _interopRequireDefault(_compiler);
12 |
13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14 |
15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
16 |
17 | var debug = require('debug')('feathers-seeder');
18 |
19 | var Seeder = function () {
20 | function Seeder(app, opts) {
21 | _classCallCheck(this, Seeder);
22 |
23 | this.app = app;
24 | this.opts = opts;
25 |
26 | this.compiler = new _compiler2.default(opts.generators);
27 | }
28 |
29 | _createClass(Seeder, [{
30 | key: 'compileTemplate',
31 | value: function compileTemplate(template) {
32 | return this.compiler.compile(template);
33 | }
34 | }, {
35 | key: 'seedApp',
36 | value: function seedApp() {
37 | var _this = this;
38 |
39 | debug('Seeding app...');
40 |
41 | return new Promise(function (resolve, reject) {
42 | var promises = [];
43 |
44 | var _iteratorNormalCompletion = true;
45 | var _didIteratorError = false;
46 | var _iteratorError = undefined;
47 |
48 | try {
49 | for (var _iterator = _this.opts.services[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
50 | var cfg = _step.value;
51 |
52 | promises.push(_this.seed(cfg));
53 | }
54 | } catch (err) {
55 | _didIteratorError = true;
56 | _iteratorError = err;
57 | } finally {
58 | try {
59 | if (!_iteratorNormalCompletion && _iterator.return) {
60 | _iterator.return();
61 | }
62 | } finally {
63 | if (_didIteratorError) {
64 | throw _iteratorError;
65 | }
66 | }
67 | }
68 |
69 | debug('Running ' + promises.length + ' seeder(s)...');
70 |
71 | return Promise.all(promises).then(function (seeded) {
72 | debug('Created ' + seeded.length + ' total items:', seeded);
73 | return resolve(seeded);
74 | }).catch(reject);
75 | });
76 | }
77 | }, {
78 | key: 'seed',
79 | value: function seed(cfg) {
80 | var _this2 = this;
81 |
82 | return new Promise(function (resolve, reject) {
83 | if (!cfg.path) {
84 | throw new Error('You must include the path of every service you want to seed.');
85 | }
86 |
87 | if (!cfg.template && !cfg.templates) {
88 | throw new Error('You must specify a template or array of templates for seeded objects.');
89 | }
90 |
91 | if (cfg.count && cfg.randomize === false) {
92 | throw new Error('You may not specify both randomize = false with count');
93 | }
94 |
95 | var service = _this2.app.service(cfg.path);
96 | var params = Object.assign({}, _this2.opts.params, cfg.params);
97 | var count = Number(cfg.count) || 1;
98 | var randomize = typeof cfg.randomize === 'undefined' ? true : cfg.randomize;
99 | debug('Params seeding \'' + cfg.path + '\':', params);
100 | debug('Param randomize: ' + randomize);
101 | debug('Creating ' + count + ' instance(s)');
102 |
103 | // Delete from service, if necessary
104 | var shouldDelete = _this2.opts.delete !== false && cfg.delete !== false;
105 |
106 | if (!shouldDelete) {
107 | debug('Not deleting any items from ' + cfg.path + '.');
108 | }
109 |
110 | var deletePromise = shouldDelete ? service.remove(null, params) : Promise.resolve([]);
111 |
112 | return deletePromise.then(function (deleted) {
113 | debug('Deleted from \'' + cfg.path + ':\'', deleted);
114 |
115 | var pushPromise = function pushPromise(template) {
116 | return new Promise(function (resolve, reject) {
117 | var compiled = _this2.compileTemplate(template);
118 | debug('Compiled template:', compiled);
119 |
120 | return service.create(compiled, params).then(function (created) {
121 | debug('Created:', created);
122 |
123 | if (typeof cfg.callback !== 'function') {
124 | return resolve(created);
125 | } else {
126 | return cfg.callback(created, _this2.seed.bind(_this2)).then(function (result) {
127 | debug('Result of callback on \'' + cfg.path + '\':', result);
128 | return resolve(created);
129 | }).catch(reject);
130 | }
131 | }).catch(reject);
132 | });
133 | };
134 |
135 | // Now, let's seed the app.
136 | var promises = [];
137 |
138 | if (cfg.template && cfg.disabled !== true) {
139 | // Single template
140 | for (var i = 0; i < count; i++) {
141 | promises.push(pushPromise(cfg.template));
142 | }
143 | } else if (cfg.templates && cfg.disabled !== true) {
144 | // Multiple random templates
145 | if (randomize) {
146 | for (var _i = 0; _i < count; _i++) {
147 | var idx = Math.floor(Math.random() * cfg.templates.length);
148 | var template = cfg.templates[idx];
149 | debug('Picked random template index ' + idx);
150 | promises.push(pushPromise(template));
151 | }
152 | }
153 | // All templates
154 | else {
155 | for (var _i2 = 0; _i2 < cfg.templates.length; _i2++) {
156 | var _template = cfg.templates[_i2];
157 | promises.push(pushPromise(_template));
158 | }
159 | }
160 | }
161 |
162 | if (!promises.length) {
163 | debug('Seeder disabled for ' + cfg.path + ', not modifying database.');
164 | return resolve([]);
165 | } else {
166 | return Promise.all(promises).then(resolve).catch(reject);
167 | }
168 | }).catch(reject);
169 | });
170 | }
171 | }]);
172 |
173 | return Seeder;
174 | }();
175 |
176 | exports.default = Seeder;
177 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "feathers-seeder",
3 | "version": "2.0.0",
4 | "description": "Straightforward data seeder for FeathersJS services.",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "prepublish": "npm run compile && npm run test",
8 | "release:prerelease": "npm version prerelease && npm publish",
9 | "release:patch": "npm version patch && npm publish",
10 | "release:minor": "npm version minor && npm publish",
11 | "release:major": "npm version major && npm publish",
12 | "compile": "rimraf lib/ && babel -d lib/ src/",
13 | "watch": "babel --watch -d lib/ src/",
14 | "jshint": "jshint src/. test/. --config",
15 | "mocha": "mocha test/ --compilers js:babel-core/register",
16 | "test": "npm run compile && npm run jshint && npm run mocha"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/thosakwe/feathers-seeder"
21 | },
22 | "keywords": [
23 | "feathers",
24 | "feathers-plugin"
25 | ],
26 | "author": {
27 | "name": "Tobe O",
28 | "email": "thosakwe@gmail.com",
29 | "url": "https://blog.thosakwe.com"
30 | },
31 | "contributors": [],
32 | "bugs": {
33 | "url": "https://github.com/thosakwe/feathers-seeder/issues"
34 | },
35 | "engines": {
36 | "node": ">=0.10.0"
37 | },
38 | "license": "MIT",
39 | "directories": {
40 | "lib": "lib"
41 | },
42 | "devDependencies": {
43 | "babel-cli": "^6.6.5",
44 | "babel-core": "^6.7.2",
45 | "babel-plugin-add-module-exports": "^0.1.2",
46 | "babel-preset-es2015": "^6.6.0",
47 | "debug": "^2.3.3",
48 | "feathers-memory": "^2.1.3",
49 | "jshint": "^2.9.1",
50 | "mocha": "^5.2.0"
51 | },
52 | "dependencies": {
53 | "@feathersjs/cli": "^3.7.3",
54 | "@feathersjs/errors": "^3.3.0",
55 | "@feathersjs/feathers": "^3.1.7",
56 | "faker": "^4.1.0",
57 | "rimraf": "^2.5.2"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/compiler.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('feathers-seeder');
2 | import faker from 'faker';
3 |
4 | export default class Compiler {
5 | compile(template) {
6 | debug('About to compile template: ', template);
7 |
8 | let result = {};
9 | Object.keys(template).forEach(key => {
10 | let value = template[key];
11 | result[key] = this._populate(key, value);
12 | });
13 |
14 | return result;
15 | }
16 |
17 | _populate(key, value) {
18 | debug(`Populating key: ${key} from value: ${value}`);
19 |
20 | if (typeof value === 'number' || typeof value === 'boolean' || value instanceof Date ||
21 | value === null || value === undefined) {
22 | debug('Value is a primitive.');
23 |
24 | return value;
25 | }
26 | else if (value instanceof String || typeof value === 'string') {
27 | debug('Value is a string.');
28 |
29 | return faker.fake(value);
30 | }
31 | else if (Array.isArray(value)) {
32 | debug('Value is an array.');
33 |
34 | return value.map(x => this._populate(key, x));
35 | }
36 | else if (typeof value === 'function') {
37 | return value();
38 | }
39 | // Otherwise, this is an object, and potentially a template itself
40 | else {
41 | debug(`Value is a ${typeof value}`);
42 |
43 | return this.compile(value);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import errors from '@feathersjs/errors';
2 | import Seeder from './seeder';
3 |
4 | const debug = require('debug')('feathers-seeder');
5 |
6 | export default function seeder(opts = {}) {
7 | if (opts === false || opts.disabled === true) {
8 | return function() {
9 | this.seed = () => {
10 | debug('Seeder is disabled, not modifying database.');
11 |
12 | return Promise.resolve([]);
13 | };
14 | };
15 | }
16 |
17 | if (!(opts.services instanceof Array)) {
18 | throw new Error('You must include an array of services to be seeded.');
19 | }
20 |
21 | return function() {
22 | const app = this;
23 | const seeder = new Seeder(app, opts);
24 |
25 | app.seed = () => {
26 | return seeder.seedApp().then().catch(err => {
27 | debug(`Seeding error: ${err}`);
28 |
29 | throw new errors.GeneralError(err);
30 | });
31 | };
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/src/seeder.js:
--------------------------------------------------------------------------------
1 | import Compiler from './compiler';
2 |
3 | const debug = require('debug')('feathers-seeder');
4 |
5 | export default class Seeder {
6 | constructor(app, opts) {
7 | this.app = app;
8 | this.opts = opts;
9 |
10 | this.compiler = new Compiler(opts.generators);
11 | }
12 |
13 | compileTemplate(template) {
14 | return this.compiler.compile(template);
15 | }
16 |
17 | seedApp() {
18 | debug('Seeding app...');
19 |
20 | return new Promise((resolve, reject) => {
21 | const promises = [];
22 |
23 | for (const cfg of this.opts.services) {
24 | promises.push(this.seed(cfg));
25 | }
26 |
27 | debug(`Running ${promises.length} seeder(s)...`);
28 |
29 | return Promise.all(promises).then(seeded => {
30 | debug(`Created ${seeded.length} total items:`, seeded);
31 | return resolve(seeded);
32 | }).catch(reject);
33 | });
34 | }
35 |
36 | seed(cfg) {
37 | return new Promise((resolve, reject) => {
38 | if (!cfg.path) {
39 | throw new Error('You must include the path of every service you want to seed.');
40 | }
41 |
42 | if (!cfg.template && !cfg.templates) {
43 | throw new Error('You must specify a template or array of templates for seeded objects.');
44 | }
45 |
46 | if (cfg.count && cfg.randomize === false) {
47 | throw new Error('You may not specify both randomize = false with count');
48 | }
49 |
50 | const service = this.app.service(cfg.path);
51 | const params = Object.assign({}, this.opts.params, cfg.params);
52 | const count = Number(cfg.count) || 1;
53 | const randomize = typeof cfg.randomize === 'undefined' ? true : cfg.randomize;
54 | debug(`Params seeding '${cfg.path}':`, params);
55 | debug(`Param randomize: ${randomize}`);
56 | debug(`Creating ${count} instance(s)`);
57 |
58 | // Delete from service, if necessary
59 | const shouldDelete = this.opts.delete !== false &&
60 | cfg.delete !== false;
61 |
62 | if (!shouldDelete) {
63 | debug(`Not deleting any items from ${cfg.path}.`);
64 | }
65 |
66 | const deletePromise = shouldDelete ?
67 | service.remove(null, params) :
68 | Promise.resolve([]);
69 |
70 | return deletePromise.then(deleted => {
71 | debug(`Deleted from '${cfg.path}:'`, deleted);
72 |
73 | const pushPromise = template => {
74 | return new Promise((resolve, reject) => {
75 | const compiled = this.compileTemplate(template);
76 | debug('Compiled template:', compiled);
77 |
78 | return service.create(compiled, params).then(created => {
79 | debug('Created:', created);
80 |
81 | if (typeof cfg.callback !== 'function') {
82 | return resolve(created);
83 | } else {
84 | return cfg.callback(created, this.seed.bind(this)).then(result => {
85 | debug(`Result of callback on '${cfg.path}':`, result);
86 | return resolve(created);
87 | }).catch(reject);
88 | }
89 | }).catch(reject);
90 | });
91 | };
92 |
93 | // Now, let's seed the app.
94 | const promises = [];
95 |
96 | if (cfg.template && cfg.disabled !== true) {
97 | // Single template
98 | for (let i = 0; i < count; i++) {
99 | promises.push(pushPromise(cfg.template));
100 | }
101 | } else if (cfg.templates && cfg.disabled !== true) {
102 | // Multiple random templates
103 | if (randomize) {
104 | for (let i = 0; i < count; i++) {
105 | let idx = Math.floor(Math.random() * cfg.templates.length);
106 | let template = cfg.templates[idx];
107 | debug(`Picked random template index ${idx}`);
108 | promises.push(pushPromise(template));
109 | }
110 | }
111 | // All templates
112 | else {
113 | for (let i = 0; i < cfg.templates.length; i++) {
114 | let template = cfg.templates[i];
115 | promises.push(pushPromise(template));
116 | }
117 | }
118 | }
119 |
120 | if (!promises.length) {
121 | debug(`Seeder disabled for ${cfg.path}, not modifying database.`);
122 | return resolve([]);
123 | } else {
124 | return Promise.all(promises).then(resolve).catch(reject);
125 | }
126 | }).catch(reject);
127 | });
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/test/callback.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import feathers from '@feathersjs/feathers';
3 | import memory from 'feathers-memory';
4 | import seeder from '../lib';
5 |
6 | describe('callback', () => {
7 | it('can nest config', done => {
8 | const app = feathers();
9 | app.use('/albums', memory());
10 | app.use('/songs', memory());
11 |
12 | app.use('/albums/:albumId/songs', {
13 | find(params) {
14 | console.log(`Searching for songs from album #${params.albumId}...`);
15 | return app.service('songs').find({
16 | query: {
17 | albumId: params.albumId
18 | }
19 | });
20 | },
21 |
22 | create(data, params) {
23 | const songData = Object.assign(data, {
24 | albumId: params.albumId
25 | });
26 | return app.service('songs').create(songData);
27 | }
28 | });
29 |
30 | const seederConfig = {
31 | services: [{
32 | path: 'albums',
33 | template: {
34 | artist: 'Pink Floyd',
35 | name: 'Dark Side of the Moon'
36 | },
37 | callback(album, seed) {
38 | return seed({
39 | delete: false,
40 | path: 'albums/:albumId/songs',
41 | params: {
42 | albumId: album.id
43 | },
44 | template: {
45 | artistName: album.artist,
46 | name: 'On the Run'
47 | }
48 | });
49 | }
50 | }]
51 | };
52 |
53 | app.configure(seeder(seederConfig));
54 |
55 | app.seed().then(albums => {
56 | console.info('Created albums:', albums);
57 |
58 | app.service('songs').find().then(songs => {
59 | console.info('Found songs:', songs);
60 |
61 | const darkSide = albums[0][0];
62 | const onTheRun = songs[0];
63 |
64 | assert.equal(onTheRun.albumId, darkSide.id);
65 | assert.equal(onTheRun.artistName, darkSide.artist);
66 | done();
67 | }).catch(done);
68 | }).catch(done);
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/test/delete.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var feathers = require('@feathersjs/feathers');
3 | var memory = require('feathers-memory');
4 | var seeder = require('../lib');
5 |
6 | describe('deletions', function() {
7 | it('can disable deletions globally', function(done) {
8 | const app = feathers().use('/dummy', memory());
9 | const config = {
10 | delete: false,
11 | services: [
12 | {
13 | count: 2,
14 | path: 'dummy',
15 | template: { scooby: { dooby: 'doo' }, 'where are': '{{name.lastName}}' }
16 | }
17 | ]
18 | };
19 | const dummy = app.service('dummy');
20 | dummy.create([{hello: 'world'}, {foo: 'bar'}, {billie: 'jean'}]).then(function() {
21 | app.configure(seeder(config)).seed().then(function() {
22 | dummy.find().then(function(items) {
23 | assert.equal(items.length, 5);
24 | done();
25 | }).catch(done);
26 | }).catch(done);
27 | }).catch(done);
28 | });
29 |
30 | it('can disable deletions locally', function(done) {
31 | const app = feathers().use('/tickets', memory()).use('/artists', memory());
32 | const config = {
33 | services: [
34 | {
35 | count: 5,
36 | path: 'tickets',
37 | template: {
38 | buyerName: '{{name.firstName}} {{name.lastName}}'
39 | }
40 | },
41 | {
42 | delete: false,
43 | count: 3,
44 | path: 'artists',
45 | template: {
46 | john: '{{name.lastName}}'
47 | }
48 | }
49 | ]
50 | };
51 | const tickets = app.service('tickets');
52 | const artists = app.service('artists');
53 |
54 | // Items should be deleted from tickets, but artists should persist
55 | // Ignore this pyramid of doom, I'm kinda tired right now...
56 | tickets.create([{foo:'bar'}, {'hit it': 'fergie!'}]).then(function() {
57 | artists.create([{michael: 'jackson'}, {marvin: 'gaye'}]).then(function() {
58 | app.configure(seeder(config)).seed().then(function() {
59 | tickets.find().then(function(_tickets) {
60 | artists.find().then(function(_artists) {
61 | assert.equal(_tickets.length, 5);
62 | assert.equal(_artists.length, 5);
63 | done();
64 | }).catch(done);
65 | }).catch(done);
66 | }).catch(done);
67 | }).catch(done);
68 | }).catch(done);
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/test/disable.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var feathers = require('@feathersjs/feathers');
3 | var memory = require('feathers-memory');
4 | var seeder = require('../lib');
5 |
6 | describe('disable', function() {
7 | it('can be globally disabled', function(done) {
8 | const app = feathers().use('/dummy', memory());
9 | const config =
10 | {
11 | disabled: true,
12 | services:
13 | [
14 | {
15 | count: 1337,
16 | path: 'dummy',
17 | template: { hello: 'world' }
18 | }
19 | ]
20 | };
21 |
22 | app.configure(seeder(config)).seed().then(function() {
23 | app.service('dummy').find().then(function(items) {
24 | assert.equal(items.length, 0);
25 | done();
26 | }).catch(done);
27 | }).catch(done);
28 | });
29 |
30 | it('can be disabled on an individual basis', function(done) {
31 | // /a will be disabled, while /b is enabled.
32 | const app = feathers().use('/a', memory()).use('/b', memory());
33 | const config = {
34 | services:
35 | [
36 | {
37 | count: 700, // disabled should preside even if count is specified
38 | disabled: true,
39 | path: 'a',
40 | template: { 'this should' : 'not be seeded' }
41 | },
42 | {
43 | count: 10,
44 | path: 'b',
45 | template: { Barack: 'Obama' }
46 | }
47 | ]
48 | };
49 |
50 | app.configure(seeder(config)).seed().then(function() {
51 | app.service('a').find().then(function(items) {
52 | assert.equal(items.length, 0);
53 | done();
54 | }).catch(done);
55 | }).catch(done);
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/test/params.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var errors = require('@feathersjs/errors');
3 | var feathers = require('@feathersjs/feathers');
4 | var memory = require('feathers-memory');
5 | var seeder = require('../lib');
6 |
7 | describe('params', function() {
8 | it('can pass custom params globally', function(done) {
9 | const app = feathers().use('/dummy', memory());
10 | const config = {
11 | params: {
12 | hello: 'world'
13 | },
14 | services: [
15 | {
16 | count: 4,
17 | path: 'dummy',
18 | template: { michael: 'jackson' }
19 | }
20 | ]
21 | };
22 | const dummy = app.service('dummy');
23 |
24 | //Deny create if {hello:'world'} not present in params
25 | dummy.hooks({
26 | before: {
27 | create: function(hook) {
28 | if (hook.params.hello !== 'world') {
29 | console.error('Invalid params', hook.params);
30 | throw new errors.BadRequest({errors: ['Params must have {hello:"world"}']});
31 | }
32 | }
33 | }
34 | });
35 |
36 | app.configure(seeder(config)).seed().then(function() {
37 | dummy.find().then(function(items) {
38 | assert.equal(items.length, 4);
39 | done();
40 | }).catch(done);
41 | }).catch(done);
42 | });
43 |
44 | it('can pass custom params locally', function(done) {
45 | const app = feathers().use('/dummy', memory());
46 | const config = {
47 | services: [
48 | {
49 | params: {
50 | hello: 'world'
51 | },
52 | count: 4,
53 | path: 'dummy',
54 | template: { michael: 'jackson' }
55 | }
56 | ]
57 | };
58 | const dummy = app.service('dummy');
59 | //Deny create if {hello:'world'} not present in params
60 | dummy.hooks({
61 | before: {
62 | create: function(hook) {
63 | if (hook.params.hello !== 'world') {
64 | console.error('Invalid params', hook.params);
65 | throw new errors.BadRequest({errors: ['Params must have {hello:"world"}']});
66 | }
67 | }
68 | }
69 | });
70 |
71 | app.configure(seeder(config)).seed().then(function(created) {
72 | console.log('Dummy created: ', created);
73 |
74 | dummy.find().then(function(items) {
75 | assert.equal(items.length, 4);
76 | done();
77 | }).catch(done);
78 | }).catch(done);
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/test/seeder.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import feathers from '@feathersjs/feathers';
3 | import memory from 'feathers-memory';
4 | import seeder from '../lib';
5 |
6 | describe('feathers-seeder', () => {
7 | describe('basic', () => {
8 | it('can seed a basic in-memory service', done => {
9 | const SINGLE = {
10 | path: 'single',
11 | template: {
12 | name: '{{name.firstName}} {{name.lastName}}'
13 | }
14 | };
15 | const MULTIPLE = {
16 | path: 'multiple',
17 | count: 24,
18 | template: {
19 | username: '{{internet.userName}}'
20 | }
21 | };
22 | const RANDOM = {
23 | path: 'random',
24 | count: 10,
25 | templates: [{
26 | username: '{{internet.userName}}'
27 | }, {
28 | password: '{{internet.password}}'
29 | }]
30 | };
31 | const ALL = {
32 | path: 'all',
33 | randomize: false,
34 | templates: [{
35 | username: '{{internet.userName}}',
36 | age: 34,
37 | updatedAt: new Date(),
38 | profileMedium: `https://dgalywyr863hv.cloudfront.net/pictures/athletes/411352/88294/1/medium.jpg`,
39 | active: true,
40 | location: {
41 | lat: 45.3455656,
42 | lng: -45.2656565
43 | }
44 | }, {
45 | username: '{{internet.userName}}',
46 | age: 33,
47 | updatedAt: new Date(),
48 | profileMedium: `https://dgalywyr863hv.cloudfront.net/pictures/athletes/411352/88294/1/medium.jpg`,
49 | active: false,
50 | location: {
51 | lat: 45.3455656,
52 | lng: -45.2656565
53 | }
54 | }]
55 | };
56 |
57 | const services = [];
58 | services.push(SINGLE, MULTIPLE, RANDOM,ALL);
59 |
60 | const app = feathers()
61 | .use(`/${SINGLE.path}`, memory())
62 | .use(`/${MULTIPLE.path}`, memory())
63 | .use(`/${RANDOM.path}`, memory())
64 | .use(`/${ALL.path}`, memory())
65 | .configure(seeder({
66 | services
67 | }));
68 |
69 | app.seed().then(() => {
70 | app.service(`${SINGLE.path}`).find().then(items => {
71 | assert.equal(items.length, 1);
72 | console.log(`Seeded ${items.length}`);
73 | }).catch(done);
74 |
75 | app.service(`${MULTIPLE.path}`).find().then(items => {
76 | assert.equal(items.length, MULTIPLE.count);
77 | console.log(`Seeded ${items.length}`);
78 | }).catch(done);
79 |
80 | app.service(`${RANDOM.path}`).find().then(items => {
81 | assert.equal(items.length, RANDOM.count);
82 | console.log(`Seeded ${items.length}`);
83 | }).catch(done);
84 |
85 | app.service(`${ALL.path}`).find().then(items => {
86 | assert.equal(items.length, ALL.templates.length);
87 | console.log(`Seeded ${items.length}`);
88 | }).catch(done);
89 |
90 | done();
91 | }).catch(done);
92 | });
93 | });
94 | });
95 |
--------------------------------------------------------------------------------
/test/template-funcs.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import feathers from '@feathersjs/feathers';
3 | import memory from 'feathers-memory';
4 | import seeder from '../lib';
5 |
6 | describe('feathers-seeder', () => {
7 | describe('custom-generator', () => {
8 | it('can seed a basic in-memory service with template funcs', done => {
9 | const INDEX = 1;
10 | const inc = (arg) => { return ++arg; };
11 | const opts = {
12 | services: [{
13 | path: 'dummy',
14 | template: {
15 | value: () => inc(INDEX),
16 | }
17 | }]
18 | };
19 |
20 | const app = feathers()
21 | .use(`/dummy`, memory())
22 | .configure(seeder(opts));
23 |
24 | app.seed().then(() => {
25 | app.service('dummy').find().then(items => {
26 | assert.equal(items.length, 1);
27 | assert.equal(items[0].value, INDEX + 1);
28 |
29 | }).catch(done);
30 |
31 | done();
32 | }).catch(done);
33 | });
34 | });
35 | });
36 |
--------------------------------------------------------------------------------