├── .babelrc ├── .editorconfig ├── .gitignore ├── .istanbul.yml ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── mocha.opts ├── package.json ├── src └── index.js └── test └── index.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ "add-module-exports" ], 3 | "presets": [ "es2015" ] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | # The compiled/babelified modules 31 | lib/ 32 | tmp/ 33 | dist/ 34 | 35 | # VSCode config 36 | .vscode 37 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | verbose: false 2 | instrumentation: 3 | root: src/ 4 | excludes: 5 | - lib/ 6 | include-all-sources: true 7 | reporting: 8 | print: summary 9 | reports: 10 | - html 11 | - text 12 | - lcov 13 | watermarks: 14 | statements: [50, 80] 15 | lines: [50, 80] 16 | functions: [50, 80] 17 | branches: [50, 80] -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .travis.yml 3 | .babelrc 4 | .idea/ 5 | src/ 6 | test/ 7 | !lib/ 8 | !dist/ 9 | .github/ 10 | coverage 11 | .istanbul.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | - '6' 5 | - '4' 6 | sudo: false -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v1.0.1](https://github.com/NicoKnoll/feathers-subscriptions-manager/tree/v1.0.1) (2016-12-02) 4 | 5 | 6 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Feathers 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # feathers-subscriptions-manager 2 | [![Build Status](https://travis-ci.org/nicoknoll/feathers-subscriptions-manager.svg?branch=master)](https://travis-ci.org/nicoknoll/feathers-subscriptions-manager) 3 | [![Downloads](https://img.shields.io/npm/dt/feathers-subscriptions-manager.svg)](https://www.npmjs.com/package/feathers-subscriptions-manager) 4 | 5 | > A subscriptions manager for feathers.js reactive subscriptions through feathers-reactive. 6 | 7 | The Feathers Subscription Manager allows you to wait for multiple subscriptions and then call a shared callback function. After the initial load this function will be called every time one of the subscriptions get updated. 8 | 9 | 10 | ## Requirements 11 | This Subscriptions Manager will only work for [feathers-reactive](https://github.com/feathersjs/feathers-reactive). Please follow their [setup instructions](https://github.com/feathersjs/feathers-reactive#setting-options-and-rxjs) before you start using the "feathers-subscriptions-manager". 12 | 13 | ``` 14 | npm install --save feathers-reactive 15 | ``` 16 | 17 | 18 | ## How does it work 19 | The Feathers Subscription Manager will let you register all the subscriptions (**addSubscription()**) you want and watch them. Each of the subscriptions will have some kind of a callback action (examples below). This _action_ has to return an object, that will be merged into the Subscriptions Manager's state. 20 | 21 | When all subscriptions have received their data the **.ready(callback(data, initial))** callback method will be called and will have the merged state object as first parameter. In this function you can e.g. have a "renderLayout" function. 22 | 23 | The callback will now be called on every change of one of the watched subscriptions as well. 24 | 25 | (optional) The second parameter of the callback function (_initial_) is a boolean that will let you know if it is the initial call (True) or an update call (False). 26 | 27 | 28 | ## Usage 29 | ### Set up subsManager: 30 | 31 | ```js 32 | import { SubsManager } from 'feathers-subscriptions-manager'; 33 | const subsManager = new SubsManager(); 34 | ``` 35 | 36 | 37 | ### addSubscription(_cursor_, _action_) 38 | 39 | _cursor_ is a feathers service call. 40 | 41 | ```js 42 | const userService = app.service('users'); 43 | const findUsersCursor = userService.find(...); 44 | ``` 45 | 46 | _action_ can be a string or a function. 47 | 48 | ```js 49 | /* Use with strings */ 50 | /* Recommended if you e.g. make a "get" call */ 51 | /* Result: {users: data} */ 52 | subsManager.addSubscription(findUsersCursor, 'users'); 53 | 54 | 55 | /* Use with function */ 56 | /* Recommended if you want to have a proxy between getting the data from the server and finishing the the call */ 57 | /* Result: {users: users.data} */ 58 | subsManager.addSubscription(findUsersCursor, (users) => { 59 | return {users: users.data}; 60 | }); 61 | 62 | ``` 63 | 64 | 65 | ### ready(callback(_data_, _initial_)) 66 | 67 | ```js 68 | subsManager.ready((data, initial) => { 69 | // all subscriptions are ready 70 | // you can use the resulting object as "data" 71 | if(initial) { 72 | renderLayout(data); 73 | } else { 74 | updateLayout(data); 75 | } 76 | }); 77 | ``` 78 | -------------------------------------------------------------------------------- /mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive test/ 2 | --compilers js:babel-core/register -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-subscriptions-manager", 3 | "version": "1.0.1", 4 | "description": "A subscriptions manager for feathers.js reactive subscriptions through feathers-reactive.", 5 | "main": "lib/index", 6 | "scripts": { 7 | "prepublish": "npm run compile && npm run browserify", 8 | "publish": "git push origin --tags && npm run changelog && git push origin", 9 | "browserify": "mkdir -p dist/ && browserify src/index.js -t babelify --standalone feathers-subscriptions-manager --outfile dist/feathers-subscriptions-manager.js", 10 | "release:patch": "npm version patch && npm publish", 11 | "release:minor": "npm version minor && npm publish", 12 | "release:major": "npm version major && npm publish", 13 | "changelog": "github_changelog_generator && git add CHANGELOG.md && git commit -am \"Updating changelog\"", 14 | "compile": "rimraf lib/ && babel -d lib/ src/", 15 | "watch": "babel --watch -d lib/ src/", 16 | "lint": "eslint-if-supported semistandard --fix", 17 | "mocha": "mocha --opts mocha.opts", 18 | "test": "npm run compile && npm run lint && npm run coverage", 19 | "coverage": "istanbul cover node_modules/mocha/bin/_mocha -- --opts mocha.opts" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/NicoKnoll/feathers-subscriptions-manager.git" 24 | }, 25 | "keywords": [ 26 | "feathers", 27 | "feathers-plugin" 28 | ], 29 | "author": { 30 | "name": "Nico Knoll", 31 | "email": "mail@nico.is", 32 | "url": "https://nico.is" 33 | }, 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/NicoKnoll/feathers-subscriptions-manager/issues" 37 | }, 38 | "homepage": "https://github.com/NicoKnoll/feathers-subscriptions-manager", 39 | "engines": { 40 | "node": ">= 4" 41 | }, 42 | "semistandard": { 43 | "env": [ 44 | "mocha" 45 | ], 46 | "ignore": [ 47 | "/lib" 48 | ] 49 | }, 50 | "directories": { 51 | "lib": "lib" 52 | }, 53 | "dependencies": { 54 | "debug": "^2.2.0", 55 | "feathers-commons": "^0.8.7", 56 | "feathers-reactive": "^0.4.1" 57 | }, 58 | "devDependencies": { 59 | "babel-cli": "^6.4.5", 60 | "babel-core": "^6.4.5", 61 | "babel-plugin-add-module-exports": "^0.2.1", 62 | "babel-plugin-transform-es2015-modules-commonjs": "^6.7.7", 63 | "babel-plugin-transform-function-bind": "^6.5.2", 64 | "babel-preset-es2015": "^6.3.13", 65 | "babelify": "^7.3.0", 66 | "browserify": "^13.0.1", 67 | "eslint-if-supported": "^1.0.1", 68 | "feathers": "^2.0.0", 69 | "feathers-memory": "^1.0.1", 70 | "feathers-hooks": "^1.5.2", 71 | "istanbul": "^1.1.0-alpha.1", 72 | "mocha": "^3.1.2", 73 | "rimraf": "^2.5.2", 74 | "rxjs": "^5.0.0-beta.8", 75 | "semistandard": "^9.1.0" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | class SubsManager { 2 | 3 | constructor () { 4 | this._state = {}; 5 | this._initial = true; 6 | this._subscriptions = []; 7 | this._countSubscriptions = 0; 8 | this._readyCallback = () => {}; 9 | } 10 | 11 | set state (obj) { 12 | this._state = Object.assign(this.state, obj); 13 | } 14 | 15 | get state () { 16 | return Object.assign({}, this._state); 17 | } 18 | 19 | isString (value) { 20 | return (typeof value === 'string'); 21 | } 22 | 23 | isObject (value) { 24 | return (value !== null && typeof value === 'object'); 25 | } 26 | 27 | isFunction (value) { 28 | return (value instanceof Function); 29 | } 30 | 31 | addSubscription (cursor, action) { 32 | this._countSubscriptions += 1; 33 | 34 | let actionCallback; 35 | 36 | if (this.isString(action)) { 37 | // use func as key for data in state 38 | actionCallback = this.callbackFromString.bind(this, action); 39 | } else if (this.isFunction(action)) { 40 | // execute func which has to return object 41 | actionCallback = this.callbackFromFunction.bind(this, action); 42 | } else { 43 | throw (new Error('Second parameter of addSubscription has to be either a string or a function.')); 44 | } 45 | 46 | this._subscriptions.push(cursor.subscribe(actionCallback.bind(this))); 47 | } 48 | 49 | callbackFromString (action, data) { 50 | let result = {}; 51 | result[action] = data; 52 | this.state = result; 53 | this.subscriptionReady(); 54 | } 55 | 56 | callbackFromFunction (action, data) { 57 | let result = action.bind(this)(data) || {}; 58 | if (!this.isObject(result)) { 59 | throw (new Error('Function has to return an object.')); 60 | } 61 | this.state = action.bind(this)(data); 62 | this.subscriptionReady(); 63 | } 64 | 65 | subscriptionReady () { 66 | if (this._countSubscriptions > 0) { 67 | this._countSubscriptions -= 1; 68 | } 69 | 70 | if (this._countSubscriptions === 0) { 71 | this._readyCallback(this.state, this._initial); 72 | this._initial = false; 73 | } 74 | } 75 | 76 | ready (cb) { 77 | this._readyCallback = cb.bind(this); 78 | } 79 | } 80 | 81 | export default { 82 | SubsManager 83 | }; 84 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import rx from 'feathers-reactive'; 3 | import feathers from 'feathers'; 4 | import memory from 'feathers-memory'; 5 | import RxJS from 'rxjs'; 6 | 7 | import { SubsManager } from '../src'; 8 | 9 | describe('feathers-subscriptions-manager', () => { 10 | let app, service, subsManager; 11 | 12 | beforeEach(done => { 13 | app = feathers().configure(rx(RxJS)).use('/messages', memory()); 14 | 15 | service = app.service('messages').rx(); 16 | 17 | service.create({ 18 | text: 'A test message' 19 | }).then(() => done()); 20 | 21 | subsManager = new SubsManager(); 22 | }); 23 | 24 | it('is CommonJS compatible', () => { 25 | let SubsManager = require('../lib'); 26 | assert.equal(typeof SubsManager.SubsManager, 'function'); 27 | }); 28 | 29 | it('is setting/getting state', () => { 30 | subsManager.state = {a: 'b'}; 31 | assert.deepEqual(subsManager.state, {a: 'b'}); 32 | }); 33 | 34 | it('is recognizing strings', () => { 35 | assert.equal(subsManager.isString('test'), true); 36 | assert.equal(subsManager.isString({}), false); 37 | assert.equal(subsManager.isString(42), false); 38 | assert.equal(subsManager.isString(() => {}), false); 39 | }); 40 | 41 | it('is recognizing objects', () => { 42 | assert.equal(subsManager.isObject('test'), false); 43 | assert.equal(subsManager.isObject({}), true); 44 | assert.equal(subsManager.isObject({a: 'b'}), true); 45 | assert.equal(subsManager.isObject(42), false); 46 | assert.equal(subsManager.isObject(() => {}), false); 47 | }); 48 | 49 | it('is recognizing functions', () => { 50 | assert.equal(subsManager.isFunction('test'), false); 51 | assert.equal(subsManager.isFunction({}), false); 52 | assert.equal(subsManager.isFunction({a: 'b'}), false); 53 | assert.equal(subsManager.isFunction(42), false); 54 | assert.equal(subsManager.isFunction(() => {}), true); 55 | assert.equal(subsManager.isFunction(function () {}), true); 56 | }); 57 | 58 | it('is possible to add subscriptions', (done) => { 59 | const cursor = service.find(); 60 | 61 | subsManager.addSubscription(cursor, 'resultString'); 62 | 63 | subsManager.addSubscription(cursor, (result) => { 64 | return {resultFunction: result}; 65 | }); 66 | 67 | subsManager.ready((data, initial) => { 68 | assert.deepEqual(data, { 69 | resultString: [ { text: 'A test message', id: 0 } ], 70 | resultFunction: [ { text: 'A test message', id: 0 } ] 71 | }); 72 | done(); 73 | }); 74 | }); 75 | }); 76 | --------------------------------------------------------------------------------