├── .travis.yml ├── .npmignore ├── api ├── models │ └── User.js └── app.js ├── config ├── publisher.js └── models.js ├── .jshintrc ├── .gitignore ├── Gruntfile.js ├── test ├── bootstrap.spec.js └── publisher.spec.js ├── package.json ├── doc.html ├── index.js └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | services: 5 | - redis-server 6 | before_script: 7 | - npm install -g grunt-cli -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | api 2 | config 3 | node_modules 4 | ssl 5 | .DS_STORE 6 | *~ 7 | .idea 8 | nbproject 9 | test 10 | .git 11 | .gitignore 12 | .tmp 13 | *.swo 14 | *.swp 15 | *.swn 16 | *.swm 17 | .jshintrc 18 | .editorconfig 19 | doc.html 20 | .travis.yml -------------------------------------------------------------------------------- /api/models/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sample model 3 | * @type {Object} 4 | */ 5 | module.exports = { 6 | attributes: { 7 | username: { 8 | type: 'string' 9 | }, 10 | email: { 11 | type: 'email' 12 | }, 13 | emailSentAt: { 14 | type: 'datetime' 15 | } 16 | } 17 | }; -------------------------------------------------------------------------------- /config/publisher.js: -------------------------------------------------------------------------------- 1 | module.exports.publisher = { 2 | //default key prefix for kue in 3 | //redis server 4 | prefix: 'q', 5 | 6 | //default redis configuration 7 | redis: { 8 | //default redis server port 9 | port: 6379, 10 | //default redis server host 11 | host: '127.0.0.1' 12 | }, 13 | //number of milliseconds 14 | //to wait 15 | //before shutdown publisher 16 | shutdownDelay: 5000 17 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "browser": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "esnext": true, 8 | "immed": true, 9 | "latedef": true, 10 | "newcap": true, 11 | "noarg": true, 12 | "node": true, 13 | "mocha": true, 14 | "quotmark": "single", 15 | "strict": true, 16 | "undef": true, 17 | "unused": true, 18 | "expr": true, 19 | "ignore": true, 20 | "globals": { 21 | "User": true, 22 | "sails": true, 23 | "_": true, 24 | "async": true 25 | } 26 | } -------------------------------------------------------------------------------- /api/app.js: -------------------------------------------------------------------------------- 1 | var sails = require('sails'); 2 | 3 | sails 4 | .lift({ // configuration for testing purposes 5 | port: 7070, 6 | environment: 'test', 7 | log: { 8 | noShip: false 9 | }, 10 | models: { 11 | migrate: 'drop' 12 | }, 13 | hooks: { 14 | sockets: false, 15 | pubsub: false, 16 | grunt: false //we dont need grunt in test 17 | } 18 | }, function(error, sails) { 19 | if (error) { 20 | return done(error); 21 | } 22 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | #Temporary data 6 | .tmp 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # Deployed apps should consider commenting this line out: 27 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 28 | node_modules 29 | doc 30 | startup.sh -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | 5 | // Add the grunt-mocha-test and jshint tasks. 6 | grunt.loadNpmTasks('grunt-mocha-test'); 7 | grunt.loadNpmTasks('grunt-contrib-jshint'); 8 | 9 | grunt.initConfig({ 10 | // Configure a mochaTest task 11 | mochaTest: { 12 | test: { 13 | options: { 14 | reporter: 'spec', 15 | timeout: 20000 16 | }, 17 | src: ['test/**/*.js'] 18 | } 19 | }, 20 | jshint: { 21 | options: { 22 | reporter: require('jshint-stylish'), 23 | jshintrc: '.jshintrc' 24 | }, 25 | all: [ 26 | 'Gruntfile.js', 27 | 'index.js', 28 | 'test/**/*.js' 29 | ] 30 | } 31 | }); 32 | 33 | //custom tasks 34 | grunt.registerTask('default', ['jshint', 'mochaTest']); 35 | grunt.registerTask('test', ['jshint', 'mochaTest']); 36 | 37 | }; -------------------------------------------------------------------------------- /test/bootstrap.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This file is useful when you want to execute some 5 | * code before and after running your tests 6 | * (e.g. lifting and lowering your sails application): 7 | */ 8 | var sails = require('sails'); 9 | /** 10 | * Lifting sails before all tests 11 | */ 12 | before(function(done) { 13 | sails 14 | .lift({ // configuration for testing purposes 15 | port: 7070, 16 | environment: 'test', 17 | log: { 18 | noShip: true 19 | }, 20 | models: { 21 | migrate: 'drop' 22 | }, 23 | hooks: { 24 | sockets: false, 25 | pubsub: false, 26 | grunt: false //we dont need grunt in test 27 | } 28 | }, function(error, sails) { 29 | if (error) { 30 | return done(error); 31 | } 32 | done(null, sails); 33 | }); 34 | }); 35 | 36 | 37 | /** 38 | * Lowering sails after done testing 39 | */ 40 | after(function(done) { 41 | User 42 | .destroy() 43 | .then(function() { 44 | sails.lower(done); 45 | }) 46 | .catch(function(error) { 47 | done(error); 48 | }); 49 | }); -------------------------------------------------------------------------------- /config/models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default model configuration 3 | * (sails.config.models) 4 | * 5 | * Unless you override them, the following properties will be included 6 | * in each of your models. 7 | * 8 | * For more info on Sails models, see: 9 | * http://sailsjs.org/#/documentation/concepts/ORM 10 | */ 11 | module.exports.models = { 12 | 13 | /*************************************************************************** 14 | * * 15 | * Your app's default connection. i.e. the name of one of your app's * 16 | * connections (see `config/connections.js`) * 17 | * * 18 | ***************************************************************************/ 19 | // connection: 'localDiskDb', 20 | 21 | /*************************************************************************** 22 | * * 23 | * How and whether Sails will attempt to automatically rebuild the * 24 | * tables/collections/etc. in your schema. * 25 | * * 26 | * See http://sailsjs.org/#/documentation/concepts/ORM/model-settings.html * 27 | * * 28 | ***************************************************************************/ 29 | // migrate: 'alter' 30 | 31 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sails-hook-publisher", 3 | "version": "0.2.1", 4 | "description": "Kue based job publisher(producer) for sails", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node ./api/app.js", 8 | "pretest": "npm link && npm link sails-hook-publisher", 9 | "test": "grunt test", 10 | "posttest": "rm -rf .tmp" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/lykmapipo/sails-hook-publisher.git" 15 | }, 16 | "author": "Lally Elias", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "hhttps://github.com/lykmapipo/sails-hook-publisher/issues" 20 | }, 21 | "homepage": "https://github.com/lykmapipo/sails-hook-publisher", 22 | "contributors": [{ 23 | "name": "lykmapipo", 24 | "github": "https://github.com/lykmapipo" 25 | }], 26 | "keywords": [ 27 | "sails", 28 | "kue", 29 | "queue", 30 | "publisher", 31 | "pubsub", 32 | "producer", 33 | "job", 34 | "redis", 35 | "createJob" 36 | ], 37 | "sails": { 38 | "isHook": true 39 | }, 40 | "dependencies": { 41 | "kue": "^0.11.1" 42 | }, 43 | "devDependencies": { 44 | "async": "^0.9.0", 45 | "chai": "^1.10.0", 46 | "faker": "^2.1.2", 47 | "mocha": "^2.1.0", 48 | "sails": "^0.11.0", 49 | "sails-disk": "^0.10.7", 50 | "grunt": "^0.4.5", 51 | "grunt-contrib-jshint": "^0.11.2", 52 | "grunt-mocha-test": "^0.12.7", 53 | "jshint-stylish": "^1.0.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /doc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | sails-hook-publisher | Kue based job publisher(producer) for sails. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 |
36 |
37 |

sails-hook-publisher

38 | 44 |
45 |
46 | 47 | 48 |
49 |
50 | 51 |
52 | 55 |
56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /test/publisher.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('chai').expect; 4 | var kue = require('kue'); 5 | var faker = require('faker'); 6 | 7 | var email = faker.internet.email(); 8 | var username = faker.internet.userName(); 9 | 10 | describe('Hook#publisher', function() { 11 | 12 | it('should be loaded as installable hook', function(done) { 13 | expect(sails.hooks.publisher).to.not.be.null; 14 | done(); 15 | }); 16 | 17 | it('should have defaults configuration', function(done) { 18 | expect(sails.config.publisher).to.not.be.null; 19 | expect(sails.config.publisher.prefix).to.equal('q'); 20 | expect(sails.config.publisher.shutdownDelay).to.equal(5000); 21 | expect(sails.config.publisher.redis.port).to.equal(6379); 22 | expect(sails.config.publisher.redis.host).to.equal('127.0.0.1'); 23 | 24 | done(); 25 | }); 26 | 27 | it('should have a queue to create job(s) and listen for queue events', function(done) { 28 | var publisher = sails.hooks.publisher; 29 | 30 | expect(publisher.queue).to.not.be.null; 31 | expect(publisher.queue).to.be.a('object'); 32 | 33 | done(); 34 | }); 35 | 36 | it('should be able create job(s)', function(done) { 37 | var publisher = sails.hooks.publisher; 38 | 39 | expect(publisher.create).to.not.be.null; 40 | expect(publisher.createJob).to.not.be.null; 41 | expect(publisher.createJob).to.be.a('function'); 42 | expect(publisher.create).to.be.a('function'); 43 | 44 | done(); 45 | }); 46 | 47 | it('should be able published job(s) to workers for processing and listen for queue events', function(done) { 48 | var subscriber = kue.createQueue(); 49 | var publisher = sails.hooks.publisher; 50 | 51 | //fake subsriber 52 | //you may use https://github.com/lykmapipo/sails-hook-subscriber 53 | //for sails ready kue subscriber 54 | subscriber 55 | .process('email', function(job, done) { 56 | done(null, { 57 | sentAt: new Date(), 58 | status: 'ok' 59 | }); 60 | }); 61 | 62 | //use publihser to create job 63 | var job = publisher.create('email', { 64 | title: 'welcome ' + username, 65 | to: email, 66 | message: 'welcome !!' 67 | }); 68 | 69 | //listen for publisher queue events 70 | publisher 71 | .queue 72 | .on('job complete', function(id, deliveryStatus) { 73 | expect(deliveryStatus.sentAt).to.not.be.null; 74 | expect(deliveryStatus.status).to.not.be.null; 75 | done(); 76 | }); 77 | 78 | job 79 | .save(function(error) { 80 | if (error) { 81 | done(error); 82 | } 83 | }); 84 | }); 85 | 86 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @description Kue based job publisher(producer) for sails. 5 | * 6 | * @param {Object} sails a sails application 7 | * @return {Object} sails-hook-publisher which follow installable sails-hook spec 8 | */ 9 | module.exports = function(sails) { 10 | var kue = require('kue'); 11 | 12 | //reference kue based queue 13 | var publisher; 14 | 15 | //return hook 16 | return { 17 | 18 | //Defaults configurations 19 | defaults: { 20 | __configKey__: { 21 | //control activeness of publisher 22 | //its active by default 23 | active: true, 24 | 25 | // default key prefix for kue in 26 | // redis server 27 | prefix: 'q', 28 | 29 | //default redis configuration 30 | redis: { 31 | //default redis server port 32 | port: 6379, 33 | //default redis server host 34 | host: '127.0.0.1' 35 | }, 36 | //number of milliseconds 37 | //to wait 38 | //before shutdown publisher 39 | shutdownDelay: 5000 40 | } 41 | }, 42 | 43 | //expose kue create 44 | //to allow job creation using publisher 45 | create: undefined, 46 | createJob: undefined, 47 | 48 | //expose publisher (kue queue) as a queue 49 | //that can be used to listen to queue events 50 | //Warning!: aim of this queue is to only 51 | //create jobs and listen for queue events, 52 | //if you want to subscribe/process jobs 53 | //consider using `https://github.com/lykmapipo/sails-hook-subscriber` 54 | queue: publisher, 55 | 56 | //Runs automatically when the hook initializes 57 | initialize: function(done) { 58 | //reference this hook 59 | var hook = this; 60 | 61 | //extend defaults configuration 62 | //with provided configuration from sails 63 | //config 64 | var config = sails.config[this.configKey]; 65 | 66 | // If the hook has been deactivated, just return 67 | if (!config.active) { 68 | sails.log.info('sails-hooks-publisher deactivated.'); 69 | return done(); 70 | } 71 | 72 | // Lets wait on some of the sails core hooks to 73 | // finish loading before 74 | // load `sails-hoo-publisher` 75 | var eventsToWaitFor = []; 76 | 77 | if (sails.hooks.orm) { 78 | eventsToWaitFor.push('hook:orm:loaded'); 79 | } 80 | 81 | if (sails.hooks.pubsub) { 82 | eventsToWaitFor.push('hook:pubsub:loaded'); 83 | } 84 | 85 | sails 86 | .after(eventsToWaitFor, function() { 87 | //initialize publisher 88 | publisher = kue.createQueue(config); 89 | 90 | //attach queue 91 | hook.queue = publisher; 92 | 93 | //expose job creation api 94 | hook.create = publisher.create; 95 | hook.createJob = publisher.create; 96 | 97 | //shutdown kue publisher 98 | //and wait for time equal to `shutdownDelay` 99 | //for workers to finalize their jobs 100 | function shutdown() { 101 | publisher 102 | .shutdown(config.shutdownDelay, function(error) { 103 | sails.emit('subscribe:shutdown', error || ''); 104 | 105 | }); 106 | } 107 | 108 | //gracefully shutdown 109 | //publisher 110 | sails.on('lower', shutdown); 111 | sails.on('lowering', shutdown); 112 | 113 | //tell external world we are up 114 | //and running 115 | sails.on('lifted', function() { 116 | sails.log('sails-hook-publisher loaded successfully'); 117 | }); 118 | 119 | // finalize publisher setup 120 | done(); 121 | }); 122 | } 123 | }; 124 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sails-hook-publisher 2 | ==================== 3 | 4 | [![Build Status](https://travis-ci.org/lykmapipo/sails-hook-publisher.svg?branch=master)](https://travis-ci.org/lykmapipo/sails-hook-publisher) 5 | 6 | Kue based job publisher(producer) for sails v0.11.0+. Its a wrapper around [Kue](https://github.com/learnboost/kue) for publishing jobs by using [redis](https://github.com/antirez/redis) as a queue engine. 7 | 8 | ## Installation 9 | ```js 10 | $ npm install --save sails-hook-publisher 11 | ``` 12 | 13 | ## Usage 14 | In sails hooks, `sails.hooks.publisher` will be available for use and it will expose: 15 | 16 | - `queue` : a `kue` based job queue specifially for publish jobs. If you want to consume jobs you may use [sails-hook-subscriber](https://github.com/lykmapipo/sails-hook-subscriber). It is a valid `kue queue` so you can also invoke other `kue` methods from it. [See](https://github.com/LearnBoost/kue#overview) 17 | 18 | - `create` or `createJob` : which is a proxy for `kue.create` and `kue.createJob` respectively. See [kue creating jobs for detailed explanations](https://github.com/LearnBoost/kue#creating-jobs) 19 | 20 | ## Publishing Jobs 21 | Use `sails.hooks.publisher.create` or `sails.hooks.publisher.createJob` to publish job(s) as way you used with `kue`. You can publish jobs in any place within your sails application where `sails.hooks` is accessible and `sails.hook.publisher` is loaded and available. 22 | 23 | Example 24 | ```js 25 | //in AuthController.js 26 | //in register(request,response) method 27 | 28 | register: function(request,response){ 29 | //your codes .... 30 | 31 | //grab publisher 32 | var publisher = sails.hooks.publisher; 33 | 34 | //publish send confirmation email 35 | var job = publisher.create('email', { 36 | title: 'Welcome' 37 | , to: request.email, 38 | , template: 'welcome-email' 39 | }) 40 | .save(); 41 | } 42 | ``` 43 | *Note: The above example demostrate `sails-hook-publisher` usage in controller you can use it in your models and services too* 44 | 45 | ## Queue Events 46 | `sails-hook-publisher` expose `queue` which is the underlying `kue queue` it use for listening for queue events. For you to listen on your job events on the queue, just add listener on the publisher `queue.on`. [see kue queue events for more explanation](https://github.com/LearnBoost/kue#queue-events) 47 | 48 | Example: 49 | ```js 50 | //somewhere in your codes just once 51 | //prefered on config/bootstrap.js 52 | //or custom hook 53 | //or services 54 | var publisher = sails.hooks.publisher; 55 | 56 | //add listener on the queue 57 | publisher 58 | .queue 59 | .on('job complete', function(id, jobResult) { 60 | //your codes here 61 | }); 62 | ``` 63 | 64 | ## Configuration 65 | `sails-hook-publisher` accept application defined configuration by utilizing sails configuration api. In sails `config` directory add `config/publisher.js` and you will be able to override all the defauts configurations. 66 | 67 | Simply, copy the below and add it to your `config/publisher.js` 68 | ```js 69 | module.exports.publisher = { 70 | //control activeness of publisher 71 | //its active by default 72 | active: true, 73 | 74 | //default key prefix for kue in 75 | //redis server 76 | prefix: 'q', 77 | 78 | //default redis configuration 79 | redis: { 80 | //default redis server port 81 | port: 6379, 82 | //default redis server host 83 | host: '127.0.0.1' 84 | }, 85 | //number of milliseconds 86 | //to wait 87 | //before shutdown publisher 88 | shutdownDelay: 5000 89 | } 90 | ``` 91 | 92 | ## Testing 93 | 94 | * Clone this repository 95 | 96 | * Install all development dependencies 97 | 98 | ```sh 99 | $ npm install 100 | ``` 101 | * Then run test 102 | 103 | ```sh 104 | $ npm test 105 | ``` 106 | 107 | ## Contribute 108 | 109 | Fork this repo and push in your ideas. 110 | Do not forget to add a bit of test(s) of what value you adding. 111 | 112 | ## Licence 113 | 114 | Copyright (c) 2015 lykmapipo 115 | 116 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 117 | 118 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 119 | 120 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------------------------------