@autologix-engineering/mongo-subs
Version:
Mongo subscription library
128 lines (113 loc) • 3.27 kB
JavaScript
/*
* Copyright 2015 mtap technologies
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
;
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var Promise = require('promise');
var regex = function(pattern) {
pattern = pattern || '*';
pattern = pattern.replace(/[\*]/g, '(.*?)');
return new RegExp('^' + pattern, 'i');
};
var getLastTimestamp = function(coll) {
return new Promise(function(resolve, reject) {
coll.find({}, { projection: { ts: 1 } })
.sort({ $natural: -1 })
.limit(1)
.next()
.then(function(doc) {
if (doc) {
resolve(doc.ts);
} else {
reject(new Error('No doc found while looking for last timestamp'));
}
})
.catch(reject);
});
};
var getOplogCursor = function(mongoContext, options) {
var query = {};
var ns = options.ns;
var dbname = options.db || 'local';
var coll = mongoContext.getDb(dbname).collection(options.coll);
if (!!ns) {
query.ns = { $regex: regex(ns) };
}
return new Promise(function(resolve, reject) {
getLastTimestamp(coll).then(function(ts) {
query.ts = { $gt: ts };
var oplogCursor = coll.find(query, {
tailable: true,
awaitData: true,
oplogReplay: true,
noCursorTimeout: true,
numberOfRetries: Number.MAX_VALUE
});
resolve(oplogCursor);
}).catch(reject);
});
};
var forwardEvents = function(cursor, emitter) {
var stream = emitter.stream = cursor.stream();
stream.on('data', function(doc) {
emitter.emit('op', doc);
emitter.emit(emitter.events[doc.op], doc);
});
stream.on('end', function() {
emitter.emit('end');
});
stream.on('error', function(err) {
if (!err || !err.stack) {
err = new Error('Unknown error: ' + err);
} else {
emitter.emit('error', err);
}
});
};
var OplogReader = function(mongoContext, options) {
EventEmitter.call(this);
this.mongoContext = mongoContext;
this.events = {
i: 'insert',
u: 'update',
d: 'delete'
};
this.options = options || {};
};
util.inherits(OplogReader, EventEmitter);
OplogReader.create = function(mongoContext, options) {
return new OplogReader(mongoContext, options);
};
OplogReader.prototype.stop = function() {
if (!!this.stream) {
this.stream.destroy();
}
return this;
};
OplogReader.prototype.start = function() {
var self = this;
getOplogCursor(this.mongoContext, this.options).then(function(cursor) {
forwardEvents(cursor, self);
}).catch(function(err) {
self.emit('error', err);
});
};
OplogReader.prototype.tail = function() {
this.mongoContext.onready(this.start.bind(this));
return this;
};
module.exports = OplogReader;