├── .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 |
50 |
51 |
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 | [](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.
--------------------------------------------------------------------------------