lib/remoteServiceManager.js
const path = require('path');
const _ = require('lodash');
module.exports = RemoteServiceManager;
module.exports.RemoteServiceManager = RemoteServiceManager;
/**
* A factory for `ServiceSDK` instances
* (see affiliated [serviser-sdk](https://github.com/lucid-services/serviser-sdk) plugin)
* and {@tutorial 4.SDK-integration}
*
* @param {Object} services
* @param {Object} services.<serviceName> - index name = name of the service
* @param {Object} services.<serviceName>.<scope> - index name not constrained, could be anything... eg.: specific service application name (in case multiple applications are listening under one service) or access level type eg.: "public" or "s2s"
* @param {String} services.<serviceName>.<scope>.npm - npm sdk package name which exports a literal object of versioned ServiceSDKs. (use either `dir` or `npm option)
* @param {String} services.<serviceName>.<scope>.dir - sdk package directory path which exports object that inherits ServiceSDK. (use either `dir` or `npm option)
* @param {String} services.<serviceName>.<scope>.host
* @param {Boolean} services.<serviceName>.<scope>.protocol
*
* @constructor
*/
function RemoteServiceManager(services) {
this.options = services || {};
this.services = {};
/**
* `ServiceSDK` constructor
* @name RemoteServiceManager#ServiceSDK
* @instance
* @type {Function}
*/
let sdk = require('serviser-sdk');
this.ServiceSDK = sdk.ServiceSDKInterface;
}
/**
* registers `ServiceSDK` instance
*
* @public
* @param {String} key - format `<serviceName>:<scope>`
* @param {ServiceSDK} sdk
*
* @return {RemoteServiceManager} - self
*/
RemoteServiceManager.prototype.add = function(key, sdk) {
let keySegments = key.split(':');
if (!(sdk instanceof this.ServiceSDK)) {
throw new Error('sdk must be instanceof ServiceSDK');
}
if (keySegments.length < 2) {
throw new Error('The first argument must be in format: `<serviceName>:<scope>`');
}
_.set(this.services, keySegments.concat([sdk.version]), sdk);
return this;
};
/**
* @public
* @param {String} key - format `<serviceName>:<scope>:<version>`
*
* @return {ServiceSDK}
*/
RemoteServiceManager.prototype.get = function(key) {
let remoteService = _.get(this.services, key.split(':'));
if (!(remoteService instanceof this.ServiceSDK)) {
throw new Error(`${key} remote service not found`);
}
return remoteService;
};
/**
* @public
* @param {String} key - format `<serviceName>:<scope>:<version>` (scope|version key segments are optional)
*
* @return {Boolean}
*/
RemoteServiceManager.prototype.has = function(key) {
let segments = key.split(':');
if (segments.length > 3) {
throw new Error(`${key} should be in format: <serviceName>:<scope>:<version>`);
}
return _.has(this.services, key.split(':'));
};
/**
* constructs specific `ServiceSDK` from configuration provided to the constructor
* the specific `ServiceSDK` is supposed to be auto generated by `serviser-sdk` plugin
* and published as a npm package
*
* @example
* //config.js:
* module.exports = {
* services: {
* user: {
* public: {
* protocol: 'https',
* host: "127.0.0.1:4001",
* npm: "service-name-app-name-sdk"
* }
* }
* }
* }
*
* or
*
* module.exports = {
* services: {
* user: {
* public: {
* protocol: 'https',
* host: "127.0.0.1:4001",
* dir: "/absolute/path/to/my/sdk/directory"
* }
* }
* }
* }
*
* //method usage
* remoteServiceManager.buildRemoteService('user:public:v1.0');
*
* @public
*
* @param {String} key - format: `<serviceName>:<scope>:<version>`
* @param {Object} [options] - `ServiceSDK` constructor options
*
* @throws {Error}
* @return {ServiceSDK}
*/
RemoteServiceManager.prototype.buildRemoteService = function(key, options) {
let keySegments = key.split(':');
let serviceName = keySegments[0];
let scope = keySegments[1];
let sdkPath = keySegments.slice(2);
let version = sdkPath.pop();
if (keySegments.length < 3) {
throw new Error('The first argument must be in format: `<serviceName>:<scope>:<version>`');
}
let confPath = `${serviceName}:${scope}`;
let conf = _.get(this.options, [serviceName, scope]);
if (!conf) {
throw new Error(`Cant find config value of "${confPath}"`);
}
if (!_.isPlainObject(conf) || (!conf.npm && !conf.dir)) {
throw new Error(`buildRemoteService requires "services:${confPath}:npm"
or "services:${confPath}:dir" config option to be set.
Cant connect remote service.`);
}
if (conf.dir && !path.isAbsolute(conf.dir)) {
throw new Error(`"dir" option value is not an absolute path.
Relative paths not supported`);
}
let sdkVersions = module.require(conf.npm || conf.dir);
let sdkContructorPath = sdkPath.concat([version]);
let sdkContructor = _.get(sdkVersions, sdkContructorPath);
if (!sdkContructor) {
throw new Error(`${confPath} service sdk does not have version: ${sdkContructorPath}`);
}
let opt =_.omit(conf, 'protocol', 'host', 'npm');
Object.assign(opt, options || {});
let protocol = conf.protocol;
if (conf.host) {
opt.baseURL = protocol + '://' + conf.host;
}
let sdk = new sdkContructor(opt);
this.add(
`${serviceName}:${scope}${sdkPath.length ? ':' + sdkPath.join(':') : ''}`,
sdk
);
return sdk;
};