├── examples
├── coffee
│ ├── server.coffee
│ ├── config.coffee
│ └── queue.coffee
├── basic
│ ├── app.js
│ ├── talker.js
│ └── listener.js
├── hello
│ ├── app.js
│ ├── greeter.js
│ └── talker.js
└── common
│ └── child.js
├── .gitignore
├── lib
├── lib.js
├── errors.js
├── eventer.js
├── logger.js
├── QueueLogger.js
├── options.js
├── QueueMonitor.js
├── MongoMQ.js
└── MongoConnection.js
├── .project
├── .gitattributes
├── package.json
├── license.txt
├── bin
└── MongoMQ.js
└── readme.md
/examples/coffee/server.coffee:
--------------------------------------------------------------------------------
1 | require './queue'
--------------------------------------------------------------------------------
/examples/coffee/config.coffee:
--------------------------------------------------------------------------------
1 | exports.db =
2 | host: 'localhost'
3 | name: 'tests'
4 |
5 | exports.db.url = "mongodb://#{exports.db.host}/#{exports.db.name}"
--------------------------------------------------------------------------------
/examples/basic/app.js:
--------------------------------------------------------------------------------
1 | var Children = require('../common/child');
2 | var talker = Children.startChild('./talker');
3 | var listener = Children.startChild('./listener');
4 |
--------------------------------------------------------------------------------
/examples/hello/app.js:
--------------------------------------------------------------------------------
1 | var Children = require('../common/child');
2 | var talker = Children.startChild('./talker');
3 | var listener = Children.startChild('./greeter');
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## npm/Node.js
3 | #################
4 |
5 | node_modules
6 |
7 | ############
8 | ## Windows
9 | ############
10 |
11 | # Windows image file caches
12 | Thumbs.db
13 |
14 | # Folder config file
15 | Desktop.ini
16 |
17 | #Editor metadata
18 | .idea
--------------------------------------------------------------------------------
/lib/lib.js:
--------------------------------------------------------------------------------
1 | exports.MongoMQ = require('./MongoMQ');
2 | exports.Eventer = require('./eventer');
3 | exports.Logger = require('./logger');
4 | exports.MongoConnection = require('./MongoConnection');
5 | exports.options = require('./options');
6 | exports.QueueMonitor = require('./QueueMonitor');
7 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | MongoMQ
4 |
5 |
6 |
7 |
8 |
9 |
10 | com.aptana.projects.webnature
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/coffee/queue.coffee:
--------------------------------------------------------------------------------
1 | config = require './config'
2 | MongoMQ = (require '../../lib/lib').MongoMQ
3 |
4 | queue = new MongoMQ
5 | autoStart: true
6 | host: config.db.host
7 | collectionName: 'capped_collection'
8 | database: config.db.name
9 |
10 | #queue.start()
11 |
12 | exports.queue = queue
--------------------------------------------------------------------------------
/lib/errors.js:
--------------------------------------------------------------------------------
1 | var errors = module.exports = {
2 | E_CONNCLOSED: 'No active server connection!',
3 | E_NODB: 'No database name provided!',
4 | E_INVALIDFILTER: 'Invalid or no filter provided!',
5 | E_NOCALLBACK: 'No callback provided, callback is required!',
6 | E_INVALIDINDEX: 'Invalid or no index provided!',
7 | E_INVALIDCURSORCOLLECTION: 'Supplied collection is not capped, tailed cursors only work on capped collections!'
8 | };
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": {
3 | "name": "Jeremy Darling",
4 | "email": "jeremy.darling@gmail.com"
5 | },
6 | "name": "mongomq",
7 | "version": "0.3.9",
8 | "repository": {
9 | "type": "git",
10 | "url": "git://github.com/jdarling/MongoMQ.git"
11 | },
12 | "main": "./lib/lib",
13 | "license": "MIT",
14 | "contributors": [
15 | {
16 | "name": "mfrobben"
17 | },
18 | {
19 | "name": "joscha"
20 | },
21 | {
22 | "name": "nickpalmer"
23 | }
24 | ],
25 | "engines": {
26 | "node": ">= v0.8.2"
27 | },
28 | "dependencies": {
29 | "mongodb": "^2.2.16",
30 | "node-uuid": "^1.4.7"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/examples/basic/talker.js:
--------------------------------------------------------------------------------
1 | var MC = require('../../lib/lib').MongoConnection;
2 | var MQ = require('../../lib/lib').MongoMQ;
3 |
4 | var options = {databaseName: 'tests', queueCollection: 'capped_collection', autoStart: false};
5 | //var options = {servers: ['ndcsrvcdep601', 'ndcsrvcdep602'], databaseName: 'tests', queueCollection: 'capped_collection', autoStart: false};
6 |
7 | var mq = module.exports = new MQ(options);
8 |
9 | var recordNumber = 0;
10 | var putRecord = function(){
11 | console.log('Emitting: '+recordNumber);
12 | mq.emit('test', recordNumber);
13 | recordNumber++;
14 | setTimeout(putRecord, 5);
15 | };
16 |
17 | (function(){
18 | var logger = new MC(options);
19 | logger.open(function(err, mc){
20 | if(err){
21 | console.log('ERROR: ', err);
22 | }else{
23 | mc.collection('log', function(err, loggingCollection){
24 | loggingCollection.remove({}, function(){
25 | mq.start(function(err){
26 | putRecord();
27 | });
28 | });
29 | });
30 | }
31 | });
32 | })();
33 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c)
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/hello/greeter.js:
--------------------------------------------------------------------------------
1 | var MC = require('../../lib/lib').MongoConnection;
2 | var MQ = require('../../lib/lib').MongoMQ;
3 |
4 | var options = {host: 'localhost', databaseName: 'tests', queueCollection: 'capped_collection', autoStart: true,
5 | serverOptions: {
6 | socketOptions: {
7 | connectTimeoutMS: 15000,
8 | socketTimeoutMS: 15000
9 | }
10 | }
11 | };
12 |
13 | var mq = module.exports = new MQ(options);
14 |
15 | var log;
16 |
17 | var handleRecord = function(err, data, next){
18 | if(!err){
19 | log.insert({handled: data}, {w:0});
20 | next('Hello '+(data||'world')+'!');
21 | }else{
22 | console.log('err: ', err);
23 | next();
24 | }
25 | };
26 |
27 | mq.on('greet', handleRecord);
28 |
29 | (function(){
30 | var logger = new MC(options);
31 | logger.open(function(err, mc){
32 | if(err){
33 | console.log('ERROR: ', err);
34 | }else{
35 | mc.collection('log', function(err, loggingCollection){
36 | log = loggingCollection;
37 | if(!options.autoStart){
38 | mq.start(function(err){
39 | if(err){
40 | console.log(err);
41 | }
42 | });
43 | }
44 | });
45 | }
46 | });
47 | })();
48 |
--------------------------------------------------------------------------------
/examples/hello/talker.js:
--------------------------------------------------------------------------------
1 | var MC = require('../../lib/lib').MongoConnection;
2 | var MQ = require('../../lib/lib').MongoMQ;
3 |
4 | var options = {host: 'localhost', databaseName: 'tests', queueCollection: 'capped_collection', autoStart: true};
5 |
6 | var mq = module.exports = new MQ(options);
7 |
8 | var recordNumber = 0;
9 | var emitRecord = function(){
10 | console.log('emitting '+recordNumber);
11 | mq.emit('greet', recordNumber, function(err, data){
12 | console.log('Response:');
13 | console.log(' err>', err);
14 | console.log(' dat>', data);
15 | emitRecord();
16 | });
17 | recordNumber++;
18 | };
19 |
20 | mq.ready(function(){
21 | console.log('ready');
22 | });
23 |
24 | var putRecord = function(){
25 | setTimeout(function(){
26 | emitRecord();
27 | putRecord();
28 | }, 100);
29 | };
30 |
31 | (function(){
32 | var logger = new MC(options);
33 | logger.open(function(err, mc){
34 | if(err){
35 | console.log('ERROR: ', err);
36 | }else{
37 | mc.collection('log', function(err, loggingCollection){
38 | loggingCollection.remove({}, function(){
39 | if(!options.autoStart){
40 | /*
41 | mq.start(function(err){
42 | putRecord();
43 | });
44 | */
45 | }
46 | });
47 | });
48 | }
49 | });
50 | })();
51 |
52 | emitRecord();
53 |
--------------------------------------------------------------------------------
/examples/common/child.js:
--------------------------------------------------------------------------------
1 | var spawn = require('child_process').spawn;
2 |
3 | var _children = [];
4 |
5 | var Children = function(){
6 | };
7 |
8 | Children.prototype.startChild = function(fileName){
9 | var args = Array.prototype.slice.call(arguments);
10 | var child = spawn('node', args);
11 | child.processFile = fileName;
12 | child.arguments = args;
13 | child.on('exit', (function(aChild){
14 | return function(code, signal){
15 | var idx = _children.indexOf(aChild);
16 | if(idx>-1&&_children[idx]) console.log(('Child ('+_children[idx].processFile+') '+aChild.pid+' is dying!').yellow);
17 | else console.log(('Child ('+aChild.pid+') is dying!').yellow);
18 | if(idx>-1){
19 | _children.splice(idx, 1);
20 | console.log('Child killed successfully!'.green);
21 | }
22 | };
23 | })(child));
24 | child.on('uncaughtException', (function(aChild){
25 | return function(err){
26 | console.error('EXCEPTION: ', err);
27 | };
28 | })(child));
29 | child.stdout.on('data', (function(aChild){
30 | return function(data){
31 | console.log(data.toString());
32 | };
33 | })(child));
34 | child.stderr.on('data', (function(aChild){
35 | return function(data){
36 | console.error('ERROR: ', data.toString());
37 | };
38 | })(child));
39 | _children.push(child);
40 | return child;
41 | };
42 |
43 | Children.prototype.children = function(){
44 | return _children;
45 | };
46 |
47 | module.exports = new Children();
48 |
--------------------------------------------------------------------------------
/examples/basic/listener.js:
--------------------------------------------------------------------------------
1 | var MC = require('../../lib/lib').MongoConnection;
2 | var MQ = require('../../lib/lib').MongoMQ;
3 |
4 | var options = {databaseName: 'tests', queueCollection: 'capped_collection', autoStart: false};
5 | //var options = {servers: ['ndcsrvcdep601', 'ndcsrvcdep602'], databaseName: 'tests', queueCollection: 'capped_collection', autoStart: false};
6 |
7 | //options.listenerType = 'streams';
8 | //options.listenerType = 'nextObject';
9 |
10 | //Streams are great for broadcast event listeners, they are BAD for things that require processing and response
11 | // as they can allow for node saturation and they are greedy. The default listenerType is 'nextObject', you
12 | // can also set the listener type on the listener itself using:
13 | // MQ.on('event', {listenerType: ''}, callback) or
14 | // MQ.once('event', {listenerType: ''}, callback)
15 |
16 | var mq = module.exports = new MQ(options);
17 |
18 | var log;
19 |
20 | var handleRecord = function(err, data, next){
21 | var w = Math.floor(Math.random()*100);
22 | if(!err){
23 | console.log('data: ', data, 'wait: ', w);
24 | log.insert({handled: data}, {w:0});
25 | }else{
26 | console.log('err: ', err, 'wait: ', w);
27 | }
28 | next();
29 | };
30 |
31 | mq.on('test', handleRecord);
32 |
33 | (function(){
34 | var logger = new MC(options);
35 | logger.open(function(err, mc){
36 | if(err){
37 | console.log('ERROR: ', err);
38 | }else{
39 | mc.collection('log', function(err, loggingCollection){
40 | log = loggingCollection;
41 | mq.start(function(err){
42 | if(err){
43 | console.log(err);
44 | }
45 | });
46 | });
47 | }
48 | });
49 | })();
50 |
--------------------------------------------------------------------------------
/lib/eventer.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var EventEmitter = require('events').EventEmitter;
3 |
4 | var defaultError = function () {};
5 |
6 | var Eventer = module.exports = function(hosting){
7 | var self = this;
8 | self.hosting = hosting;
9 | self.on('error', defaultError);
10 | };
11 |
12 | util.inherits(Eventer, EventEmitter);
13 |
14 | Eventer.description = 'A wrapper around EventEmitter to provide some high level functionality and eased useage pattern within MongoMQ.';
15 |
16 | Eventer.prototype.call = function(eventName, err, response){
17 | var self = this;
18 | self.emit(eventName, err, response||self.hosting);
19 | };
20 | Eventer.prototype.call.description = 'Calls an event.';
21 |
22 | Eventer.prototype.registerTo = function(what){
23 | var self = this, keys = Object.keys(self), i, l=keys.length, key, value;
24 | for(i=0; i0){
47 | self.emit('error', err, self.hosting);
48 | }
49 | if(hasCallback){
50 | callback(err);
51 | }
52 | if((handlerCount===0)&&(!noThrow)&&(!hasCallback)){
53 | throw err.toString();
54 | }
55 | return false;
56 | };
57 | Eventer.prototype.checkNoError.description = 'Checks to see if there is an error, and if their is handles it appropriatly.';
58 |
59 | Eventer.prototype.surface = function(what){
60 | what = what instanceof Array?what:[what];
61 | var self = this, i, l = what.length, event;
62 | for(i=0; i0) val = tmp.join('=');
14 | else val = true;
15 | tmp = opts;
16 | names = name.split('.');
17 | while(names.length>1){
18 | name = names.shift();
19 | name = alias[name]||name;
20 | tmp = tmp[name]=tmp[name]||{};
21 | }
22 | name = names.shift();
23 | name = alias[name]||name;
24 | tmp[name]=val;
25 | }
26 |
27 | var queue = new MongoMQ(opts);
28 |
29 | queue.ready(function(){
30 | console.log('Connected');
31 | });
32 |
33 | queue.stopped(function(){
34 | console.log('Disconnected');
35 | });
36 |
37 | queue.error(function(err){
38 | console.log(err);
39 | });
40 |
41 | var r = repl.start({
42 | prompt: "MongoMQ>"
43 | });
44 |
45 | r.on('exit', function(){
46 | queue.stop(); // force a close
47 | });
48 |
49 | var funcName, value, funcs = [], ftmp;
50 | for(funcName in queue){
51 | value = queue[funcName];
52 | if(typeof(value)=='function'){
53 | ftmp = r.context[funcName]||(function(f){
54 | return function(){
55 | f.apply(queue, arguments);
56 | };
57 | })(value);
58 | funcs.push({name: funcName, description: value.description, f: ftmp, a: value});
59 | if(!r.context[funcName]){
60 | r.context[funcName] = ftmp;
61 | }
62 | }
63 | }
64 |
65 | r.context.connect = function(connectionString, databaseName, collectionName){
66 | queue.close(function(){
67 | queue.options.connectionString = connectionString||queue.options.connectionString;
68 | queue.options.databaseName = databaseName||queue.options.databaseName;
69 | queue.options.queueCollection = collectionName||queue.options.queueCollection;
70 | queue.start(function(err){
71 | if(err){
72 | console.log(err);
73 | }
74 | });
75 | });
76 | };
77 | r.context.connect.description = 'Connect to a specific MongoMQ instance to work with';
78 |
79 | r.context.status = function(callback){
80 | callback = callback || function(err, results, info){
81 | results = results || [];
82 | var i, l=results.length;
83 | console.log('\r\n-=[Queue Status]=-');
84 | for(i=0; i '+funcs[i].description+'\r\n';
102 | }
103 | return result;
104 | };
105 | if(methodName){
106 | var i = false, l = funcs.length, index = false;
107 | for(i = 0; (i"
145 | });
146 | r.on('exit', function(){
147 | queue.stop();
148 | });
149 |
150 | var msgidx = 0;
151 | r.context.send = function(){
152 | queue.emit('test', msgidx);
153 | msgidx++;
154 | };
155 |
156 | r.context.load = function(){
157 | for(var i = 0; i<100; i++){
158 | queue.emit('test', msgidx);
159 | msgidx++;
160 | }
161 | };
162 |
163 | var logMsg = function(err, data, next){
164 | console.log('LOG: ', data);
165 | next();
166 | };
167 | var eatTest = function(err, data, next){
168 | console.log('eat: ', data);
169 | next();
170 | };
171 |
172 | r.context.logAny = function(){
173 | queue.onAny(logMsg);
174 | };
175 |
176 | r.context.listen = function(){
177 | queue.on('test', eatTest);
178 | };
179 |
180 | r.context.start = function(cb){
181 | queue.start(cb);
182 | };
183 |
184 | r.context.stop = function(){
185 | queue.stop();
186 | };
187 |
188 | r.context.help = function(){
189 | console.log('Built in test methods:\r\n'+
190 | ' help() - shows this message\r\n'+
191 | ' logAny() - logs any message to the console\r\n'+
192 | ' eatTest() - consumes next available "test" message from the queue\r\n'+
193 | ' send() - places a "test" message on the queue\r\n'+
194 | ' load() - places 100 "test" messages on the queue\r\n'+
195 | ' start() - start the queue listener\r\n'+
196 | ' stop() - stop the queue listener\r\n'+
197 | '\r\nInstance Data\r\n'+
198 | ' queue - the global MongoMQ instance\r\n'
199 | );
200 | return '';
201 | };
202 |
203 | /*
204 | queue.start(function(){
205 | r.context.eatTest();
206 | });
207 | */
208 |
209 | r.context.queue = queue;
210 |
211 | r.context.help();
212 | ```
213 |
214 | How Events are stored
215 | =====================
216 |
217 | ```javascript
218 | {
219 | _id: ObjectId(), // for internal use only
220 | pkt_ver: 3, // Packet version that this message is being sent in
221 | event: event, // string that represents what type of event this is
222 | data: message, // Contains the actual message contents
223 | handled: false, // states if the message has been handled or not
224 | localTime: dt, // Local Date Time of when the message was put on the queue
225 | globalTime: new Date(dt-self.serverTimeOffset), // Date Time offset to server time of when the message was put on the queue
226 | pickedTime: new Date(dt-self.serverTimeOffset), // Date Time offset to server time of when the message was picked up from the queue
227 | host: string, // Contains the host name of the machine that initiated the event
228 | [response_id: string] // optional if the event expects response(s) this will be the conversation identifier used to track those responses
229 | }
230 | ```
231 |
232 | Update History
233 | ==============
234 |
235 | v0.3 Update History
236 | -------------------
237 |
238 | v0.3.4
239 | * Fix QueueMonitor.js call from options to self.options in handleResponse
240 |
241 | v0.3.3
242 | * Fixed package.json and upreved version to pickup changes.
243 |
244 | v0.3.2
245 | * Upgraded to latest (1.3.6) version of Mongo Node Native
246 | * Fixed typo in lib.js for inclusion of Logging (changed Logging to logging)
247 | * Locked dependency versions so breaking shouldn't happen again when the dependency chain changes
248 |
249 | v0.3.1
250 | * Added setTimeout to nextTick on startup to give Mongo a chance to get connceted to
251 | * Minor bug fix due to EventEmitter treating 'error' events specially
252 | * Tweak to once listeners to call next if it exists. Shouldn't change anything but it is good practice.
253 |
254 | v0.3.0
255 | * Initial release of v0.3.x, includes many new features and functionality along with many bug fixes.
256 |
257 | v0.2 Update History
258 | -------------------
259 |
260 | v0.2.10&v0.2.11
261 | * Workaround for Mongo Native Driver not supporting tailed cursor auto-reconnects when Mongo server goes away.
262 |
263 | v0.2.9
264 | * Change SafeDBDriver default value from false to true, this fixes the issue with multiple listeners picking up the same message since Mongo doesn't perform record locking on updates if this isn't true.
265 | * Fix autoStart
266 | * Resolves #9 and #10
267 |
268 | v0.2.8
269 | * Upgraded code for new MongoDB Native Drivers (thanks mfrobben for starting points)
270 | * Readme cleanup (thanks ttezel for pointing this out and fixing it)
271 | * Resolves #7 and #6
272 |
273 | v0.2.7
274 | * Fixed a cursor leak when using passive callbacks
275 |
276 | v0.2.6
277 | * Bug fix related to relplica set configuration loading from config.json files
278 |
279 | v0.2.5
280 | * General code cleanup and optimizations
281 | * Examples cleanup and fixes
282 |
283 | v0.2.4
284 | * Examples added
285 |
286 | v0.2.3
287 | * Minor bug fix related to passive listeners where a fromDT was not passed in the options
288 | * Added hostName to messages for better tracking/logging
289 | * Modified passive callback to pass the actual message as the "this" argument, you can now use this.event to get the actual event that was responded to
290 | * Updated the on() method to accept strings or regular expressions to filter events on
291 |
292 | v0.2.2
293 | * Completed code to allow for callbacks and partial callbacks to be issued back to emit statements
294 | * Complteed refactoring of code to properly seperate functionality into objects
295 |
296 | v0.2.1
297 | * Majorly refactored code
298 | * Added autoIndexId: true to queue collection creation
299 | * Better MongoMQ application with help()
300 | * Updated test application
301 | * Added an exception to emit() when you try to emit before start() has been called
302 | * fix to onAny so it will restart listeners after a close() and start() re-issue
303 | * Added remove*() methods
304 | * Changed close() to stop()
305 | * hereOnOut options - allows listeners to only pay attention to messages posted after they have been started up
306 | * Added ability to register listeners (via on and onAny) when queue is not started
307 |
308 | v0.1.1
309 | * Bug fixes to on event
310 | * Added in new onAny register
311 | * Migrated code to retain cursor
312 |
313 | v0.1.0
314 | * Initial release
315 | * More of a proof of concept
--------------------------------------------------------------------------------
/lib/MongoMQ.js:
--------------------------------------------------------------------------------
1 | var MC = require('./MongoConnection');
2 | var UUID = require('node-uuid');
3 | var util = require('util');
4 | var Options = require('./options');
5 | var hostName = require('os').hostname();
6 | var QueueMonitor = require('./QueueMonitor');
7 | var errors = require('./errors');
8 |
9 | var defaults = {
10 | autoStart: true,
11 | queueCollection: 'queue',
12 | databaseName: 'mongomq'
13 | };
14 |
15 | var MongoMQ = module.exports = function(options, callback){
16 | var self = this, mcOptions;
17 | if(typeof(options)==='function'){
18 | callback = options;
19 | options = {};
20 | }
21 | callback=callback||function(){};
22 | options = options||{};
23 | options.databaseName=options.databaseName||options.database;
24 | options.queueCollection=options.queueCollection||options.collectionName;
25 | options = Options.ensure(options, defaults);
26 |
27 | mcOptions = Options.ensure(mcOptions, options);
28 | mcOptions.autoStart = false;
29 |
30 | MC.call(self, options);
31 |
32 | self.monitors={};
33 | self.emitter.surface(['ready', 'stopped']);
34 |
35 | if(options.autoStart){
36 | self.start(callback);
37 | }else{
38 | callback(null, self);
39 | }
40 | };
41 |
42 | util.inherits(MongoMQ, MC);
43 |
44 | MongoMQ.options = defaults;
45 |
46 | MongoMQ.prototype.checkConnection = function(callback){
47 | var self = this;
48 | var waitForStarted = function(){
49 | process.nextTick(function(){
50 | setTimeout(function(){
51 | if(self.isopen){
52 | callback(null, self);
53 | }else{
54 | waitForStarted();
55 | }
56 | }, 100);
57 | });
58 | };
59 | if(self.isopen){
60 | callback(null, self);
61 | }else if(self.options.autoStart){
62 | waitForStarted();
63 | }else{
64 | callback(new Error(errors.E_CONNCLOSED));
65 | }
66 | };
67 | MongoMQ.prototype.checkConnection.description = 'Checks to see if MongoMQ is connected to a server or not.';
68 |
69 | MongoMQ.prototype.start = function(callback){
70 | var self = this;
71 | if(self.isopen){
72 | (callback||function(){})(null, self);
73 | }else{
74 | self.open(function(err){
75 | if(err){
76 | self.close();
77 | self._open = false;
78 | (callback||function(){})(err);
79 | self.emitter.call('error', err);
80 | }else{
81 | self.ensureCapped(self.options.queueCollection, function(err, collection){
82 | if(!self.emitter.checkNoError(err, callback)){
83 | self.close();
84 | self._open = false;
85 | }else{
86 | self.startListeners(function(){
87 | var startTime = new Date();
88 | self.serverStatus(function(err, status){
89 | if(self.emitter.checkNoError(err, callback)){
90 | self.serverTimeOffset = status.localTime - startTime;
91 | (callback||function(){})(err, self);
92 | self.emitter.call('ready', err);
93 | }
94 | });
95 | });
96 | }
97 | });
98 | }
99 | });
100 | }
101 | };
102 | MongoMQ.prototype.start.description = 'Starts the Mongo Queue system.';
103 |
104 | MongoMQ.prototype.stop = function(callback){
105 | var self = this;
106 | if(self.isopen){
107 | self.stopListeners(function(){
108 | self.close(function(){
109 | self.emitter.call('stopped');
110 | self._open = false;
111 | (callback||function(){})(null, self);
112 | });
113 | });
114 | }else{
115 | (callback||function(){})(null, self);
116 | self.emitter.call('stopped');
117 | }
118 | };
119 | MongoMQ.prototype.start.description = 'Stops the Mongo Queue system.';
120 |
121 | MongoMQ.prototype.emit = function(event, message, callback){
122 | var self = this, hasCallback = typeof(callback)==='function';
123 | self.checkConnection(function(err){
124 | if(err){
125 | self.emitter.call('error', err);
126 | if(typeof(callback)==='function'){
127 | return callback(err);
128 | }else{
129 | throw err;
130 | }
131 | }
132 | self.collection(self.options.queueCollection, function(err, collection){
133 | var dt = new Date(),
134 | pkt = {
135 | pkt_ver: 3,
136 | event: event,
137 | data: message,
138 | handled: false,
139 | localTime: dt,
140 | globalTime: new Date(dt-self.serverTimeOffset),
141 | pickedTime: new Date(dt-self.serverTimeOffset),
142 | host: hostName
143 | };
144 | if(hasCallback){
145 | pkt.response_id = UUID.v4(); // new way
146 | if(self.options.support_v2){
147 | pkt.conversationId = UUID.v4(); // old way
148 | }
149 | }
150 | collection.insert(pkt, {w: 1}, function(err, details){
151 | self.emitter.call('sent', details);
152 | if(!err){
153 | if(hasCallback){
154 | self.once(pkt.response_id, {listenerType: 'responseListener'}, callback);
155 | }
156 | }else{
157 | //err = err instanceof Error?err:new Error(err);
158 | self.emitter.call('error', err);
159 | if(hasCallback){
160 | callback(err);
161 | }else{
162 | throw err;
163 | }
164 | }
165 | });
166 | });
167 | });
168 | };
169 | MongoMQ.prototype.emit.description = 'Puts a message on the queue.';
170 |
171 | MongoMQ.prototype.broadcast = function(event, message){
172 | var self = this;
173 | self.checkConnection(function(err){
174 | if(err){
175 | self.emitter.call('error', err);
176 | throw err;
177 | }
178 | self.collection(self.options.queueCollection, function(err, collection){
179 | var pkt = {
180 | event: event,
181 | data: message,
182 | localTime: new Date(),
183 | host: hostName
184 | };
185 | collection.insert(pkt, {w: 0});
186 | });
187 | });
188 | };
189 | MongoMQ.prototype.broadcast.description = 'Broadcasts a message out across all subscribed listeners.';
190 |
191 | MongoMQ.prototype.status = function(callback){
192 | var self = this;
193 | if(self.isopen){
194 | self.checkConnection(function(err){
195 | if(err){
196 | self.emitter.call('error', err);
197 | if(typeof(callback)==='function'){
198 | return callback(err);
199 | }else{
200 | throw err;
201 | }
202 | }
203 | var map = function(){
204 | emit(this.event, 1);
205 | };
206 | var reduce = function(key, values){
207 | var reduced = 0;
208 | values.forEach(function(val){
209 | reduced += val;
210 | });
211 | return reduced;
212 | };
213 | self.collection(self.options.queueCollection, function(err, collection){
214 | if(err){
215 | self.emitter.call('error', err);
216 | callback(err);
217 | }else{
218 | collection.mapReduce(map, reduce, {
219 | query : { "handled" : false },
220 | out : { inline : 1 }
221 | }, callback);
222 | }
223 | });
224 | });
225 | }else{
226 | callback(null, false);
227 | }
228 | };
229 | MongoMQ.prototype.status.description = 'Retrieves the MongoMQ queue stati and their depth.';
230 |
231 | MongoMQ.prototype.startListeners = function(callback){
232 | var self = this, eventNames = Object.keys(self.monitors), i, l=eventNames.length, detailItems, j, k;
233 | for(i=0; i-1; i--){
349 | listener = list[i].stop();
350 | }
351 | self.monitors[event]=[];
352 | };
353 | MongoMQ.prototype.removeAllListeners.description = 'Stops and removes all listeners for a specific event.';
354 |
355 | MongoMQ.prototype.setMaxListeners = false;
356 |
357 | MongoMQ.prototype.listeners = function(event){
358 | var self = this;
359 | var list = self.monitors[event]=self.monitors[event]||[];
360 | return list;
361 | };
362 | MongoMQ.prototype.listeners.description = 'Returns a listing of listeners and their options for a given event.';
363 |
--------------------------------------------------------------------------------
/lib/MongoConnection.js:
--------------------------------------------------------------------------------
1 | var Mongo = require('mongodb');
2 | var Mongo = require('mongodb');
3 | var MongoClient = Mongo.MongoClient;
4 | var Options = require('./options');
5 | var GridStore = Mongo.GridStore;
6 | var Eventer = require('./eventer');
7 | var errors = require('./errors');
8 |
9 | var defaults = {
10 | CollectionOptions: {safe: false},
11 | ServerHost: 'localhost',
12 | ServerOptions: {
13 | auto_reconnect: false,
14 | //*
15 | poolSize: 1,
16 | socketOptions: {
17 | connectTimeoutMS: 1000,
18 | socketTimeoutMS: 1000
19 | }
20 | //*/
21 | },
22 | CappedCollectionSize: 104857600,
23 | NativeParser : false,
24 | logger: null // No logger by default. If providied, this needs to have the same interface as the MongoDB logger.
25 | //logger: new require('./logger')()
26 | };
27 |
28 | var ensureMongoConnectionDetails = function(options){
29 | if(options.connectionString){
30 | return;
31 | }else if(options.servers||options.host){
32 | Options.ensure(options, {port: 27017});
33 | return;
34 | }else{
35 | options.connectionString = 'mongodb://localhost:27017';
36 | }
37 | };
38 |
39 | var MongoConnection = module.exports = function(options, callback){
40 | var self = this, dbName;
41 | callback=callback||function(){};
42 | options = Options.ensure(options||{}, {autoStart: true});
43 | ensureMongoConnectionDetails(options);
44 | self.__defineGetter__('options', function(){
45 | return options;
46 | });
47 | self.__defineGetter__('active', function(){
48 | return (!!this.db)&&(this.db.openCalled);
49 | });
50 | self.__defineGetter__('isopen', function(){
51 | return this._open;
52 | });
53 | self.__defineGetter__('databaseName', function(){
54 | return (this.db||{}).databaseName||'default';
55 | });
56 | self.__defineSetter__('databaseName', function(value){
57 | this.use(value);
58 | });
59 | self.emitter = new Eventer(self);
60 | self.emitter.surface(['error', 'opened', 'closed']);
61 | self.emitter.registerTo(self);
62 | };
63 |
64 | MongoConnection.ERROR_CODES = errors;
65 |
66 | var connect = function(self, connection, callback){
67 | var connected = function(err, database){
68 | if(self.emitter.checkNoError(err, callback)){
69 | self.db = database;
70 | self._open = true;
71 | if(self.options.username&&self.options.password){
72 | if(self.options.authenticateAgainstDb){
73 | database.authenticate(self.options.username, self.options.password, function(err, result){
74 | if(typeof(callback)=='function'){
75 | self.emitter.call('opened', err);
76 | callback(null, self);
77 | }
78 | });
79 | }else{
80 | database.admin(function(err, adminDb){
81 | if(self.emitter.checkNoError(err, callback)){
82 | adminDb.authenticate(self.options.username, self.options.password, function(err, result){
83 | if(self.emitter.checkNoError(err, callback)){
84 | self.emitter.call('opened', err);
85 | callback(null, self);
86 | }
87 | });
88 | }
89 | });
90 | }
91 | }else{
92 | self.emitter.call('opened', err);
93 | callback(null, self);
94 | }
95 | }
96 | };
97 | if(!connection){
98 | var connectOptions = {server: Options.ensure(self.options.serverOptions||{}, defaults.ServerOptions)};
99 | if(self.options.logger||defaults.logger){
100 | connectOptions.server.logger=self.options.logger||defaults.logger;
101 | connectOptions.server.logger.log('Logger setup');
102 | }
103 | connectOptions.server.auto_reconnect = null;
104 | MongoClient.connect(self.options.connectionString, connectOptions, connected);
105 | }else{
106 | self.mongoClient = new MongoClient(connection);
107 | self.mongoClient.open(connected);
108 | }
109 | };
110 |
111 | var connectReplSet = function(self, options, callback){
112 | var l = options.servers.length, server, serverConfig, serverConnection;
113 | var servers = [], serverOptions = Options.ensure(options.serverOptions||{}, defaults.ServerOptions);
114 | for(var i = 0; i-1){
266 | return self.createCollection(collectionName, collOptions, callback);
267 | }
268 | if(self.emitter.checkNoError(err, callback)){
269 | if(!!capped){
270 | if(typeof(callback)==='function'){
271 | callback(null, collection);
272 | }
273 | }else{
274 | collection.insert({workaround: 'This works around a bug with capping empty collections.'}, {safe:true}, function(){
275 | self.db.command({"convertToCapped": collectionName, size: collOptions.size}, function(err, result){
276 | if(self.emitter.checkNoError(err, callback)){
277 | if(typeof(callback)==='function'){
278 | if(self.emitter.checkNoError(err, callback)){
279 | if (result.ok===0){
280 | self.emitter.checkNoError(result.errmsg, callback);
281 | }else{
282 | self.collection(collectionName, callback);
283 | }
284 | }
285 | }
286 | }
287 | });
288 | });
289 | }
290 | }
291 | });
292 | }
293 | }
294 | });
295 | }
296 | });
297 | };
298 | MongoConnection.prototype.ensureCapped.description = 'Ensures that the provided collection is capped.';
299 |
300 | MongoConnection.prototype.tailedCursorStreamable = function(collectionName, filter, sort, callback){
301 | var self = this;
302 | self.checkConnection(function(err){
303 | if(self.emitter.checkNoError(err, callback)){
304 | if(typeof(sort)==='function'){
305 | callback = sort;
306 | sort = false;
307 | }
308 | if(typeof(callback)!=='function'){
309 | throw new Error(errors.E_NOCALLBACK);
310 | }else if(typeof(filter)!=='object'){
311 | return callback(new Error(errors.E_INVALIDFILTER));
312 | }else{
313 | self.collection(collectionName, function(err, collection){
314 | if(self.emitter.checkNoError(err, callback)){
315 | collection.isCapped(function(err, capped){
316 | if(self.emitter.checkNoError(err, callback)){
317 | if(capped){
318 | var cursorOptions = {tailable: true};
319 | if(sort) cursorOptions.sort = sort;
320 | callback(null, collection.find(filter, cursorOptions));
321 | }else{
322 | self.emitter.checkNoError(errors.E_INVALIDCURSORCOLLECTION, callback);
323 | }
324 | }
325 | });
326 | }
327 | });
328 | }
329 | }
330 | });
331 | };
332 | MongoConnection.prototype.tailedCursorStreamable.description = 'Returns a tailed cursor that can be used to create a stream that can be used to monitor the provided collection.';
333 |
334 | MongoConnection.prototype.tailedCursorStream = function(collectionName, filter, sort, callback){
335 | var self = this;
336 | self.checkConnection(function(err){
337 | if(self.emitter.checkNoError(err, callback)){
338 | if(typeof(sort)==='function'){
339 | callback = sort;
340 | sort = false;
341 | }
342 | if(typeof(callback)!=='function'){
343 | self.emitter.checkNoError(errors.E_NOCALLBACK);
344 | }else if(typeof(filter)!=='object'){
345 | self.emitter.checkNoError(new Error(errors.E_INVALIDFILTER), callback);
346 | }else{
347 | self.collection(collectionName, function(err, collection){
348 | if(self.emitter.checkNoError(err, callback)){
349 | collection.isCapped(function(err, capped){
350 | if(self.emitter.checkNoError(err, callback)){
351 | if(capped){
352 | var cursorOptions = {tailable: true};
353 | if(sort) cursorOptions.sort = sort;
354 | var cursor = collection.find(filter, cursorOptions);
355 | var stream = cursor.stream();
356 | stream.cursor = cursor;
357 | callback(null, stream);
358 | }else{
359 | self.emitter.checkNoError(errors.E_INVALIDCURSORCOLLECTION, callback);
360 | }
361 | }
362 | });
363 | }
364 | });
365 | }
366 | }
367 | });
368 | };
369 | MongoConnection.prototype.tailedCursorStream.description = 'Returns a tailed stream that can be used to monitor the provided collection.';
370 |
371 | MongoConnection.prototype.tailedCursor = function(collectionName, filter, sort, callback){
372 | var self = this;
373 | self.checkConnection(function(err){
374 | if(self.emitter.checkNoError(err, callback)){
375 | if(typeof(sort)==='function'){
376 | callback = sort;
377 | sort = false;
378 | }
379 | if(typeof(callback)!=='function'){
380 | self.emitter.checkNoError(errors.E_NOCALLBACK);
381 | }else if(typeof(filter)!=='object'){
382 | self.emitter.checkNoError(errors.E_INVALIDFILTER, callback);
383 | }else{
384 | self.collection(collectionName, function(err, collection){
385 | if(self.emitter.checkNoError(err, callback)){
386 | collection.isCapped(function(err, capped){
387 | if(self.emitter.checkNoError(err, callback)){
388 | if(capped){
389 | var cursorOptions = {tailable: true};
390 | if(sort) cursorOptions.sort = sort;
391 | collection.find(filter, cursorOptions, callback);
392 | }else{
393 | self.emitter.checkNoError(errors.E_INVALIDCURSORCOLLECTION, callback);
394 | }
395 | }
396 | });
397 | }
398 | });
399 | }
400 | }
401 | });
402 | };
403 | MongoConnection.prototype.tailedCursor.description = 'Returns cursor that can be used with nextObject to monitor a collection.';
404 |
405 | MongoConnection.prototype.writeGridFS = function(fileName, data, options, callback){
406 | var self = this;
407 | if(typeof(options)==='function'){
408 | callback = options;
409 | options = {};
410 | }
411 | self.checkConnection(function(err){
412 | if(self.emitter.checkNoError(err, callback)){
413 | }
414 | });
415 | };
416 |
417 | MongoConnection.prototype.readGridFS = function(fileName, options, callback){
418 | var self = this;
419 | if(typeof(options)==='function'){
420 | callback = options;
421 | options = {};
422 | }
423 | self.checkConnection(function(err){
424 | if(self.emitter.checkNoError(err, callback)){
425 | GridStore.exist(self.db, fileName, function(err, exists){
426 | if(self.emitter.checkNoError(err, callback)){
427 | if(exists===false){
428 | callback(null, false);
429 | }else{
430 | var gridStore = new GridStore(self.db, fileName, 'r');
431 | gridStore.open(function(err, gridStore) {
432 | if(self.emitter.checkNoError(err, callback)){
433 | gridStore.read(function(err){
434 | if(self.emitter.checkNoError(err, callback)){
435 | callback.apply(self, arguments);
436 | gridStore.close(function(){});
437 | }
438 | });
439 | }
440 | });
441 | }
442 | }
443 | });
444 | }
445 | });
446 | };
447 | MongoConnection.prototype.readGridFS.description = 'Retrieves the requested file from GridFS.';
448 |
449 | MongoConnection.prototype.streamGridFS = function(fileName, callback){
450 | var self = this;
451 | self.checkConnection(function(err){
452 | if(self.emitter.checkNoError(err, callback)){
453 | GridStore.exist(self.db, fileName, function(err, exists){
454 | if(self.emitter.checkNoError(err, callback)){
455 | if(!exists){
456 | callback(null, false);
457 | }else{
458 | var gridStore = new GridStore(self.db, fileName, 'r');
459 | var doput = function(done){
460 | return function(data){
461 | if(data){
462 | callback(null, data.toString());
463 | }
464 | if(done){
465 | callback(null, null);
466 | }
467 | };
468 | };
469 | var doerror = function(done){
470 | return function(err){
471 | self.emitter.checkNoError(err, callback);
472 | };
473 | };
474 | gridStore.open(function(err, gridStore) {
475 | var stream = gridStore.stream(true);
476 | stream.on('data', doput(false));
477 | stream.on('error', doerror(true));
478 | stream.on('end', doput(true));
479 | });
480 | }
481 | }
482 | });
483 | }
484 | });
485 | };
486 | MongoConnection.prototype.streamGridFS.description = 'Retrieves the requested file from GridFS using streams to lower overhead.';
487 |
--------------------------------------------------------------------------------