hudson-taylor
Version:
Hudson Taylor is a set of libraries for making awesome microservices
339 lines (286 loc) • 8.12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var util = require('util');
var events = require('events');
var async = require('async');
var bluebird = require('bluebird');
var utils = require('ht-utils');
var Service = require('./service');
var LocalTransport = require('./transports').Local;
var Client = function Client(services) {
if (!(this instanceof Client)) {
return new Client(services);
}
this.services = {};
this.connections = {};
this.schemas = {};
this.middleware = {
before: [],
after: []
};
for (var service in services) {
if (!services.hasOwnProperty(service)) {
continue;
}
this.add(service, services[service]);
}
};
util.inherits(Client, events.EventEmitter);
Client.prototype.add = function (name, transport) {
if (this.services[name]) {
throw new Error('Tried adding a service with duplicate name');
}
if (transport instanceof Service) {
var service = transport;
transport = new LocalTransport();
service.addTransport(transport);
}
this.services[name] = transport;
var client = new transport.Client();
this.connections[name] = client;
this.emit('added', name);
};
Client.prototype.getServices = function () {
return Object.keys(this.services);
};
Client.prototype.hasService = function (name) {
return !!~this.getServices().indexOf(name);
};
Client.prototype.addSchema = function (service, method, schema) {
if (!this.schemas[service]) {
this.schemas[service] = {};
}
if (typeof schema.validate !== 'function') {
throw new Error('Schema for ' + method + ' does not have a validate function.');
}
this.schemas[service][method] = schema;
};
Client.prototype.connect = function (done) {
var _this = this;
async.each(Object.keys(this.services), function (name, cb) {
_this.connections[name].connect(function (err) {
if (err) {
return cb(err);
}
_this.emit('connected', name);
cb();
});
}, done);
};
Client.prototype.disconnect = function (done) {
var _this2 = this;
async.each(Object.keys(this.connections), function (name, cb) {
_this2.connections[name].disconnect(function (err) {
if (err) {
return cb(err);
}
delete _this2.connections[name];
_this2.emit('disconnected', name);
cb();
});
}, done);
};
Client.prototype.call = function (service, method, data, callback) {
var _this3 = this;
var opts = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
var context = {
service: service,
method: method
};
var returnPromise = false;
var promise = void 0,
resolve = void 0,
reject = void 0;
// this can be cleaned up
if (!data && !callback) {
data = undefined;
returnPromise = true;
} else if (data && typeof data !== 'function' && !callback) {
returnPromise = true;
} else if (typeof data === 'function') {
callback = data;
data = undefined;
}
if (returnPromise) {
// :(
promise = new bluebird.Promise(function (_resolve, _reject) {
resolve = _resolve;
reject = _reject;
});
}
var _beforeMiddleware = this.middleware.before.filter(function (m) {
if (m.service && m.service !== context.service) return false;
if (m.method && m.method !== context.method) return false;
return true;
});
function returnResult(err, data) {
if (returnPromise) {
if (err) {
return reject(err);
} else {
return resolve(data);
}
}
return callback(err, data);
}
async.eachSeries(_beforeMiddleware, function (middleware, done) {
middleware.fn.call(context, data, function (err, result) {
if (err) {
return done(err);
}
data = result;
done();
});
}, function (err) {
if (err) {
return returnResult(err);
}
var conn = _this3.connections[context.service];
if (!conn) {
return returnResult({ error: 'unknown-service' });
}
conn.call(context.method, data, function (err, data) {
if (err) {
return returnResult(err);
}
var _afterMiddleware = _this3.middleware.after.filter(function (m) {
if (m.service && m.service !== context.service) return false;
if (m.method && m.method !== context.method) return false;
return true;
});
async.eachSeries(_afterMiddleware, function (middleware, done) {
middleware.fn.call(context, data, function (err, result) {
if (err) {
return done(err);
}
data = result;
done();
});
}, function (err) {
if (err) {
return returnResult(err);
}
var finish = function finish(data) {
_this3.emit('called', context.service, context.method);
return returnResult(null, data);
};
if (_this3.schemas[context.service] && _this3.schemas[context.service][context.method]) {
var schema = _this3.schemas[context.service][context.method];
schema.validate(data, function (err, data) {
if (err) {
return returnResult({
$htValidationError: true,
error: err.message
});
}
return finish(data);
});
} else {
return finish(data);
}
});
}, opts);
});
if (returnPromise) {
return promise;
}
};
Client.prototype.before = function (fn) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var service = opts.service,
method = opts.method;
this.middleware.before.push({
service: service,
method: method,
fn: fn
});
};
Client.prototype.after = function (fn) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var service = opts.service,
method = opts.method;
this.middleware.after.push({
service: service,
method: method,
fn: fn
});
};
Client.prototype.prepare = function (service, method, data) {
var _this4 = this;
return function (_data, callback) {
if (typeof _data === 'function') {
callback = _data;
_data = undefined;
}
if (typeof _data === 'undefined') {
_data = data;
}
return _this4.call(service, method, _data, callback);
};
};
Client.prototype.chain = function (service, method, data) {
var client = this;
if (!client.isChain) {
// return new instance of the client
// so we can set values on it
client = new Client();
for (var k in this) {
if (client.hasOwnProperty(k)) {
client[k] = this[k];
}
}
client.isChain = true;
client.chainedMethods = [];
}
client.chainedMethods.push({
service: service,
method: method,
data: data
});
return client;
};
Client.prototype.end = function (callback) {
if (!this.isChain) {
return callback(new Error('Client.end called on a non-chained HT client.'));
}
var tmp = this.chainedMethods.reduce(function (previous, method) {
var last = previous[previous.length - 1];
if (!last || last.service !== method.service) {
previous.push({
service: method.service,
calls: []
});
}
var call = {
method: method.method
};
if (method.data) {
call.data = method.data;
}
previous[previous.length - 1].calls.push(call);
return previous;
}, []);
var methods = tmp.map(function (serviceCall) {
var call = {
service: serviceCall.service,
method: '$htMultiCall',
data: serviceCall.calls
};
if (serviceCall.calls.length === 1) {
// If we're only calling 1 method on the
// service, call it directly and don't
// use $htMultiCall
call = {
service: serviceCall.service,
method: serviceCall.calls[0].method,
data: serviceCall.calls[0].data
};
}
return call;
});
utils.getLastResult.call(this, methods, callback);
};
exports.default = Client;
module.exports = exports['default'];