fh-wfm-mediator
Advanced tools
Comparing version 0.4.0-rc1 to 1.0.0-pre.1
@@ -1,22 +0,27 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var angular = require("angular"); | ||
var mediator = require("../mediator"); | ||
'use strict'; | ||
var mediator = require('../mediator'); | ||
angular.module('wfm.core.mediator', ['ng']) | ||
.factory('mediator', function mediatorService($q) { | ||
var originalRequest = mediator.request; | ||
mediator.request = function () { | ||
var promise = originalRequest.apply(mediator, arguments); | ||
return $q.when(promise); | ||
}; | ||
mediator.subscribeForScope = function (topic, scope, fn) { | ||
var subscriber = mediator.subscribe(topic, fn); | ||
scope.$on('$destroy', function () { | ||
mediator.remove(topic, subscriber.id); | ||
}); | ||
return subscriber; | ||
}; | ||
return mediator; | ||
.factory('mediator', function mediatorService($q) { | ||
var originalRequest = mediator.request; | ||
// monkey patch the request function, wrapping the returned promise as an angular promise | ||
mediator.request = function() { | ||
var promise = originalRequest.apply(mediator, arguments); | ||
return $q.when(promise); | ||
}; | ||
mediator.subscribeForScope = function(topic,scope,fn) { | ||
var subscriber = mediator.subscribe(topic,fn); | ||
scope.$on("$destroy", function() { | ||
mediator.remove(topic, subscriber.id); | ||
}); | ||
}; | ||
return mediator; | ||
}); | ||
exports.default = 'wfm.core.mediator'; | ||
//# sourceMappingURL=mediator-ng.js.map | ||
module.exports = 'wfm.core.mediator'; |
@@ -1,5 +0,437 @@ | ||
"use strict"; | ||
var mediator_1 = require("./mediator"); | ||
var mediator = new mediator_1.default(); | ||
var _ = require('lodash'); | ||
var Promise = require('bluebird'); | ||
const CONSTANTS = require('../constants'); | ||
//Based on https://github.com/ajacksified/Mediator.js | ||
//This change is based on an assumption that the subscribes respond with a promise. | ||
// We'll generate guids for class instances for easy referencing later on. | ||
// Subscriber instances will have an id that can be refernced for quick | ||
// lookups. | ||
function guidGenerator() { | ||
var S4 = function() { | ||
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); | ||
}; | ||
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4()); | ||
} | ||
// Subscribers are instances of Mediator Channel registrations. We generate | ||
// an object instance so that it can be updated later on without having to | ||
// unregister and re-register. Subscribers are constructed with a function | ||
// to be called, options object, and context. | ||
function Subscriber(fn, options, context) { | ||
if (!(this instanceof Subscriber)) { | ||
return new Subscriber(fn, options, context); | ||
} | ||
this.id = guidGenerator(); | ||
this.fn = fn; | ||
this.options = options; | ||
this.context = context; | ||
this.channel = null; | ||
} | ||
// Mediator.update on a subscriber instance can update its function,context, | ||
// or options object. It takes in an object and looks for fn, context, or | ||
// options keys. | ||
Subscriber.prototype.update = function(options) { | ||
if (options) { | ||
this.fn = options.fn || this.fn; | ||
this.context = options.context || this.context; | ||
this.options = options.options || this.options; | ||
if (this.channel && this.options && this.options.priority !== undefined) { | ||
this.channel.setPriority(this.id, this.options.priority); | ||
} | ||
} | ||
}; | ||
function Channel(namespace, parent) { | ||
if (!(this instanceof Channel)) { | ||
return new Channel(namespace); | ||
} | ||
this.namespace = namespace || ""; | ||
this._subscribers = []; | ||
this._channels = {}; | ||
this._parent = parent; | ||
this.stopped = false; | ||
} | ||
// A Mediator channel holds a list of sub-channels and subscribers to be fired | ||
// when Mediator.publish is called on the Mediator instance. It also contains | ||
// some methods to manipulate its lists of data; only setPriority and | ||
// StopPropagation are meant to be used. The other methods should be accessed | ||
// through the Mediator instance. | ||
Channel.prototype.addSubscriber = function(fn, options, context) { | ||
var subscriber = new Subscriber(fn, options, context); | ||
if (options && options.priority !== undefined) { | ||
// Cheap hack to either parse as an int or turn it into 0. Runs faster | ||
// in many browsers than parseInt with the benefit that it won't | ||
// return a NaN. | ||
options.priority = options.priority >> 0; | ||
if (options.priority < 0) { | ||
options.priority = 0; | ||
} | ||
if (options.priority >= this._subscribers.length) { | ||
options.priority = this._subscribers.length - 1; | ||
} | ||
this._subscribers.splice(options.priority, 0, subscriber); | ||
} else { | ||
this._subscribers.push(subscriber); | ||
} | ||
subscriber.channel = this; | ||
return subscriber; | ||
}; | ||
// The channel instance is passed as an argument to the mediator subscriber, | ||
// and further subscriber propagation can be called with | ||
// channel.StopPropagation(). | ||
Channel.prototype.stopPropagation = function() { | ||
this.stopped = true; | ||
}; | ||
Channel.prototype.getSubscriber = function(identifier) { | ||
var x = 0, | ||
y = this._subscribers.length; | ||
for (x, y; x < y; x++) { | ||
if (this._subscribers[x].id === identifier || this._subscribers[x].fn === identifier) { | ||
return this._subscribers[x]; | ||
} | ||
} | ||
}; | ||
// Channel.setPriority is useful in updating the order in which Subscribers | ||
// are called, and takes an identifier (subscriber id or named function) and | ||
// an array index. It will not search recursively through subchannels. | ||
Channel.prototype.setPriority = function(identifier, priority) { | ||
var oldIndex = 0, | ||
x = 0, | ||
sub, firstHalf, lastHalf, y; | ||
for (x = 0, y = this._subscribers.length; x < y; x++) { | ||
if (this._subscribers[x].id === identifier || this._subscribers[x].fn === identifier) { | ||
break; | ||
} | ||
oldIndex++; | ||
} | ||
sub = this._subscribers[oldIndex]; | ||
firstHalf = this._subscribers.slice(0, oldIndex); | ||
lastHalf = this._subscribers.slice(oldIndex + 1); | ||
this._subscribers = firstHalf.concat(lastHalf); | ||
this._subscribers.splice(priority, 0, sub); | ||
}; | ||
Channel.prototype.addChannel = function(channel) { | ||
this._channels[channel] = new Channel((this.namespace ? this.namespace + ':' : '') + channel, this); | ||
}; | ||
Channel.prototype.hasChannel = function(channel) { | ||
return this._channels.hasOwnProperty(channel); | ||
}; | ||
Channel.prototype.returnChannel = function(channel) { | ||
return this._channels[channel]; | ||
}; | ||
Channel.prototype.removeSubscriber = function(identifier) { | ||
var x = this._subscribers.length - 1; | ||
// If we don't pass in an id, we're clearing all | ||
if (!identifier) { | ||
this._subscribers = []; | ||
return; | ||
} | ||
// Going backwards makes splicing a whole lot easier. | ||
for (x; x >= 0; x--) { | ||
if (this._subscribers[x].fn === identifier || this._subscribers[x].id === identifier) { | ||
this._subscribers[x].channel = null; | ||
this._subscribers.splice(x, 1); | ||
} | ||
} | ||
}; | ||
// This will publish arbitrary arguments to a subscriber and then to parent | ||
// channels. | ||
Channel.prototype.publish = function(data) { | ||
var x = 0, | ||
y = this._subscribers.length, | ||
shouldCall = false, | ||
subscriber, | ||
subsBefore, subsAfter; | ||
var self = this; | ||
var promises = []; | ||
// Priority is preserved in the _subscribers index. | ||
for (x, y; x < y; x++) { | ||
// By default set the flag to false | ||
shouldCall = false; | ||
subscriber = this._subscribers[x]; | ||
if (!this.stopped) { | ||
subsBefore = this._subscribers.length; | ||
if (subscriber.options !== undefined && typeof subscriber.options.predicate === "function") { | ||
if (subscriber.options.predicate.apply(subscriber.context, data)) { | ||
// The predicate matches, the callback function should be called | ||
shouldCall = true; | ||
} | ||
} else { | ||
// There is no predicate to match, the callback should always be called | ||
shouldCall = true; | ||
} | ||
} | ||
// Check if the callback should be called | ||
if (shouldCall) { | ||
// Check if the subscriber has options and if this include the calls options | ||
if (subscriber.options && subscriber.options.calls !== undefined) { | ||
// Decrease the number of calls left by one | ||
subscriber.options.calls--; | ||
// Once the number of calls left reaches zero or less we need to remove the subscriber | ||
if (subscriber.options.calls < 1) { | ||
this.removeSubscriber(subscriber.id); | ||
} | ||
} | ||
// Now we call the callback, if this in turns publishes to the same channel it will no longer | ||
// cause the callback to be called as we just removed it as a subscriber | ||
var subscriberResult = subscriber.fn.apply(subscriber.context, data); | ||
//If the result of the subscriber function is a promise, we are interested in the result. | ||
//If it isn't, we are not interested in the result. | ||
subscriberResult = _.isObject(subscriberResult) && _.isFunction(subscriberResult.then) ? subscriberResult : Promise.resolve(); | ||
//Handling the result | ||
subscriberResult = subscriberResult.then(function(result) { | ||
return result === null || result === undefined ? result : { | ||
topic: self.namespace, | ||
result: result | ||
}; | ||
}); | ||
promises.push(subscriberResult); | ||
subsAfter = this._subscribers.length; | ||
y = subsAfter; | ||
if (subsAfter === subsBefore - 1) { | ||
x--; | ||
} | ||
} | ||
} | ||
this.stopped = false; | ||
if (this._parent) { | ||
this._parent.publish(data); | ||
} | ||
//All related promises to this channel have been added. | ||
return _.flatten(promises); | ||
}; | ||
function Mediator() { | ||
if (!(this instanceof Mediator)) { | ||
return new Mediator(); | ||
} | ||
this._channels = new Channel(''); | ||
} | ||
// A Mediator instance is the interface through which events are registered | ||
// and removed from publish channels. | ||
// Returns a channel instance based on namespace, for example | ||
// application:chat:message:received. If readOnly is true we | ||
// will refrain from creating non existing channels. | ||
Mediator.prototype.getChannel = function(namespace, readOnly) { | ||
var channel = this._channels, | ||
namespaceHierarchy = namespace.split(':'), | ||
x = 0, | ||
y = namespaceHierarchy.length; | ||
if (namespace === '') { | ||
return channel; | ||
} | ||
if (namespaceHierarchy.length > 0) { | ||
for (x, y; x < y; x++) { | ||
if (!channel.hasChannel(namespaceHierarchy[x])) { | ||
if (readOnly) { | ||
break; | ||
} else { | ||
channel.addChannel(namespaceHierarchy[x]); | ||
} | ||
} | ||
channel = channel.returnChannel(namespaceHierarchy[x]); | ||
} | ||
} | ||
return channel; | ||
}; | ||
// Pass in a channel namespace, function to be called, options, and context | ||
// to call the function in to Subscribe. It will create a channel if one | ||
// does not exist. Options can include a predicate to determine if it | ||
// should be called (based on the data published to it) and a priority | ||
// index. | ||
Mediator.prototype.subscribe = function(channelName, fn, options, context) { | ||
var channel = this.getChannel(channelName || "", false); | ||
options = options || {}; | ||
context = context || {}; | ||
return channel.addSubscriber(fn, options, context); | ||
}; | ||
// Pass in a channel namespace, function to be called, options, and context | ||
// to call the function in to Subscribe. It will create a channel if one | ||
// does not exist. Options can include a predicate to determine if it | ||
// should be called (based on the data published to it) and a priority | ||
// index. | ||
Mediator.prototype.once = function(channelName, fn, options, context) { | ||
options = options || {}; | ||
options.calls = 1; | ||
return this.subscribe(channelName, fn, options, context); | ||
}; | ||
// Returns a subscriber for a given subscriber id / named function and | ||
// channel namespace | ||
Mediator.prototype.getSubscriber = function(identifier, channelName) { | ||
var channel = this.getChannel(channelName || "", true); | ||
// We have to check if channel within the hierarchy exists and if it is | ||
// an exact match for the requested channel | ||
if (channel.namespace !== channelName) { | ||
return null; | ||
} | ||
return channel.getSubscriber(identifier); | ||
}; | ||
// Remove a subscriber from a given channel namespace recursively based on | ||
// a passed-in subscriber id or named function. | ||
Mediator.prototype.remove = function(channelName, identifier) { | ||
var channel = this.getChannel(channelName || "", true); | ||
if (channel.namespace !== channelName) { | ||
return false; | ||
} | ||
channel.removeSubscriber(identifier); | ||
}; | ||
// Publishes arbitrary data to a given channel namespace. Channels are | ||
// called recursively downwards; a post to application:chat will post to | ||
// application:chat:receive and application:chat:derp:test:beta:bananas. | ||
// Called using Mediator.publish("application:chat", [ args ]); | ||
Mediator.prototype.publish = function(channelName) { | ||
var self = this; | ||
var channel = this.getChannel(channelName || "", true); | ||
if (channel.namespace !== channelName) { | ||
return null; | ||
} | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
args.push(channel); | ||
//All promises must be resolved for the published topic to be complete. | ||
return Promise.all(channel.publish(args)).then(function(results) { | ||
//Removing any un-required results | ||
//NOTE: The subscriber with the highest priority result is resolved. | ||
var topicResponse = _.first(_.compact(_.flatten(results))); | ||
return topicResponse ? topicResponse.result : undefined; | ||
}).then(function(result) { | ||
if (result) { | ||
self.publish(CONSTANTS.DONE_TOPIC_PREFIX + CONSTANTS.TOPIC_SEPERATOR + channel.namespace, result); | ||
} | ||
return result; | ||
}); | ||
}; | ||
Mediator.prototype.promise = function(channel, options, context) { | ||
var self = this; | ||
return new Promise(function(resolve) { | ||
self.once(channel, resolve, options, context); | ||
}); | ||
}; | ||
/** | ||
* Publishes a message on a topic and wait for a response or error | ||
* | ||
* @param {String} topic Channel identifier to publish the initial message | ||
* | ||
* @param {Any} parameters The data to publish to the topic. The unique id used to publish | ||
* the 'return' topic is extracted from this parameter according to | ||
* the following rules: | ||
* - `parameters.id` property, If parameters has this property | ||
* - `parameters[0]` if parameters is an Array | ||
* - `parameters.toString()` otherwise | ||
* | ||
* @param {Object} options Options object | ||
* @param {Number} [options.timeout] - Optional timeout for the request to finish | ||
* | ||
* @return {Promise} A Promise that gets fulfilled with the result of the request | ||
* or rejected with the error from the above topics | ||
*/ | ||
Mediator.prototype.request = function(topic, parameters, options) { | ||
var self = this; | ||
options = options || {}; | ||
if (!options.timeout) { | ||
options.timeout = 60000; | ||
} | ||
var args = [topic]; | ||
if (parameters instanceof Array) { | ||
Array.prototype.push.apply(args, parameters); | ||
} else if (parameters) { | ||
args.push(parameters); | ||
} | ||
var publishPromise = self.publish.apply(mediator, args); | ||
return publishPromise ? publishPromise.timeout(options.timeout, new Error('Mediator request timeout for topic ' + topic)) : Promise.reject(new Error("No subscribers exist for topic " + topic)); | ||
}; | ||
// Alias some common names for easy interop | ||
Mediator.prototype.on = Mediator.prototype.subscribe; | ||
Mediator.prototype.bind = Mediator.prototype.subscribe; | ||
Mediator.prototype.emit = Mediator.prototype.publish; | ||
Mediator.prototype.trigger = Mediator.prototype.publish; | ||
Mediator.prototype.off = Mediator.prototype.remove; | ||
// Finally, expose it all. | ||
Mediator.Channel = Channel; | ||
Mediator.Subscriber = Subscriber; | ||
Mediator.version = "0.9.8"; | ||
var mediator = new Mediator(); | ||
mediator.Mediator = Mediator; | ||
module.exports = mediator; | ||
//# sourceMappingURL=index.js.map |
@@ -1,103 +0,180 @@ | ||
"use strict"; | ||
var Promise = require("bluebird"); | ||
var _ = require("lodash"); | ||
var Topics = (function () { | ||
function Topics(mediator) { | ||
this.mediator = mediator; | ||
this.subscriptions = {}; | ||
const Promise = require('bluebird'); | ||
const _ = require('lodash'); | ||
function Topics(mediator) { | ||
this.mediator = mediator; | ||
this.subscriptions = {}; | ||
} | ||
/** | ||
* Sets the prefix configuration for this instance, which will be part of the name | ||
* of the handled topics. | ||
* @param {String} prefix | ||
* @return {Topics} returns self for chaining | ||
*/ | ||
Topics.prototype.prefix = function(prefix) { | ||
this.prefix = prefix; | ||
return this; | ||
}; | ||
/** | ||
* Sets the entity configuration for this instance, which will be part of the name | ||
* of the handled topics. | ||
* This property is present as a convenience so it can be accessed by handlers via `this.entity` | ||
* | ||
* @param {String} entity | ||
* @return {Topics} returns self for chaining | ||
*/ | ||
Topics.prototype.entity = function(entity) { | ||
this.entity = entity; | ||
return this; | ||
}; | ||
/** | ||
* Internal function to add a subscription to the internal collection | ||
* @param {string} topic topic id | ||
* @param {Function} fn handler for the topic | ||
*/ | ||
Topics.prototype.addSubscription = function(topic, fn) { | ||
this.subscriptions[topic] = this.mediator.subscribe(topic, fn); | ||
}; | ||
/** | ||
* Builds a topic name out of the configured {@link prefix} and {@link entity} | ||
* @param {String} topicName The name of the sub-topic to build | ||
* @param {String} [prefix] An optional prefix to the final topic, i.e. 'done' | ||
* @param {String} [topicUid] An optional unique identifier to append | ||
* @return {String} The complete topic name, | ||
* i.e. {prefix}:{this.prefix}:{this.entity}:{topicName}:{topicUid} | ||
*/ | ||
Topics.prototype.getTopic = function(topicName, prefix, topicUid) { | ||
// create, done => done:wfm:user:create | ||
var parts = _.compact([this.prefix, this.entity, topicName, topicUid]); | ||
if (prefix) { | ||
parts.unshift(prefix); | ||
} | ||
return parts.join(':'); | ||
}; | ||
/** | ||
* Internal function to wrap a `on` handler in a promise that will publish to the | ||
* related 'done:' and 'error:' topics | ||
* @param {Topics} self The instance, receive as a param to avoid exposing this function | ||
* in the prototype | ||
* @param {String} method The base topic to publish results to | ||
* @param {Function} fn Handler to wrap, can return a value or a Promise, will be invoked bound to self | ||
* @return {Function} Wrapped handler | ||
*/ | ||
function wrapInMediatorPromise(self, method, fn) { | ||
function publishDone(result) { | ||
if (_.isUndefined(result)) { | ||
return; | ||
} | ||
Topics.prototype.prefix = function (prefix) { | ||
this._prefix = prefix; | ||
return this; | ||
}; | ||
; | ||
Topics.prototype.entity = function (entity) { | ||
this._entity = entity; | ||
return this; | ||
}; | ||
; | ||
Topics.prototype.addSubscription = function (topic, fn) { | ||
this.subscriptions[topic] = this.mediator.subscribe(topic, fn); | ||
}; | ||
; | ||
Topics.prototype.getTopic = function (topicName, prefix, topicUid) { | ||
var parts = _.compact([this._prefix, this._entity, topicName, topicUid]); | ||
if (prefix) { | ||
parts.unshift(prefix); | ||
} | ||
return parts.join(':'); | ||
}; | ||
; | ||
Topics.prototype.on = function (method, fn) { | ||
var topic = this.getTopic(method); | ||
this.addSubscription(topic, this.wrapInMediatorPromise(method, fn)); | ||
return this; | ||
}; | ||
; | ||
Topics.prototype.onDone = function (method, fn) { | ||
var topic = this.getTopic(method, 'done'); | ||
this.addSubscription(topic, fn.bind(this)); | ||
return this; | ||
}; | ||
; | ||
Topics.prototype.onError = function (method, fn) { | ||
var topic = this.getTopic(method, 'error'); | ||
this.addSubscription(topic, fn.bind(this)); | ||
return this; | ||
}; | ||
; | ||
Topics.prototype.unsubscribeAll = function () { | ||
var subId; | ||
for (var topic in this.subscriptions) { | ||
if (this.subscriptions.hasOwnProperty(topic)) { | ||
subId = this.subscriptions[topic].id; | ||
this.mediator.remove(topic, subId); | ||
} | ||
} | ||
}; | ||
; | ||
Topics.prototype.request = function (topic, params, options) { | ||
return this.mediator.request(this.getTopic(topic), params, options); | ||
}; | ||
; | ||
Topics.prototype.wrapInMediatorPromise = function (method, fn) { | ||
var self = this; | ||
function publishDone(result) { | ||
if (_.isUndefined(result)) { | ||
return; | ||
} | ||
var topic = self.getTopic(method, 'done'); | ||
if (_.has(result, 'id')) { | ||
topic = [topic, result.id].join(':'); | ||
} | ||
else if (typeof result === 'string') { | ||
topic = [topic, result].join(':'); | ||
} | ||
self.mediator.publish(topic, result); | ||
return result; | ||
} | ||
function publishError(error) { | ||
var topic = self.getTopic(method, 'error'); | ||
if (_.has(error, 'id')) { | ||
topic = [topic, error.id].join(':'); | ||
} | ||
self.mediator.publish(topic, error); | ||
} | ||
return function () { | ||
var _prefix = self._prefix, _entity = self._entity, mediator = self.mediator; | ||
var context = { | ||
entity: _entity, | ||
mediator: mediator, | ||
prefix: _prefix, | ||
topic: self.getTopic(method) | ||
}; | ||
return Promise.resolve(fn.apply(context, arguments)) | ||
.then(publishDone) | ||
.catch(publishError); | ||
}; | ||
}; | ||
return Topics; | ||
}()); | ||
; | ||
module.exports = Topics; | ||
//# sourceMappingURL=index.js.map | ||
var topic = self.getTopic(method, 'done'); | ||
if (_.has(result, 'id')) { | ||
topic = [topic, result.id].join(':'); | ||
} else if (typeof result === 'string') { | ||
topic = [topic, result].join(':'); | ||
} | ||
self.mediator.publish(topic, result); | ||
return result; | ||
} | ||
function publishError(error) { | ||
var topic = self.getTopic(method, 'error'); | ||
if (_.has(error, 'id')) { | ||
topic = [topic, error.id].join(':'); | ||
} | ||
self.mediator.publish(topic, error); | ||
} | ||
return function() { | ||
return Promise.resolve(fn.apply(self, arguments)) | ||
.then(publishDone) | ||
.catch(publishError); | ||
}; | ||
} | ||
/** | ||
* Setup a handler for a namespaced topic, if this handler returns a value or throws an Error, | ||
* it will get published to 'done' and 'error'-predixed topics as per convention. | ||
* | ||
* @param {String} method Topic name inside the namespace | ||
* @param {Function} fn Handler that can optionally return a value or a Promise | ||
* that will be treated as the result of a `request` | ||
* @return {Topics} Returns self for chaining | ||
*/ | ||
Topics.prototype.on = function(method, fn) { | ||
var topic = this.getTopic(method); | ||
this.addSubscription(topic, wrapInMediatorPromise(this, method, fn)); | ||
return this; | ||
}; | ||
/** | ||
* Setup a handler for a namespaced topic, with the 'done:' prefix | ||
* @param {String} method Topic name inside the namespace | ||
* @param {Function} fn Handler function for the topic | ||
* @return {Topics} Returns self for chaining | ||
*/ | ||
Topics.prototype.onDone = function(method, fn) { | ||
var topic = this.getTopic(method, 'done'); | ||
this.addSubscription(topic, fn.bind(this)); | ||
return this; | ||
}; | ||
/** | ||
* Setup a handler for a namespaced topic, with the 'done:' prefix | ||
* @param {String} method Topic name inside the namespace | ||
* @param {Function} fn Handler function for the topic | ||
* @return {Topics} Returns self for chaining | ||
*/ | ||
Topics.prototype.onError = function(method, fn) { | ||
var topic = this.getTopic(method, 'error'); | ||
this.addSubscription(topic, fn.bind(this)); | ||
return this; | ||
}; | ||
/** | ||
* Modifies mediator to unsubscribe to all topics configured through this instance | ||
*/ | ||
Topics.prototype.unsubscribeAll = function() { | ||
var subId; | ||
for (var topic in this.subscriptions) { | ||
if (this.subscriptions.hasOwnProperty(topic)) { | ||
subId = this.subscriptions[topic].id; | ||
this.mediator.remove(topic, subId); | ||
} | ||
} | ||
}; | ||
/** | ||
* Does a {@link Mediator.request} in the context of the namespaced topics | ||
* @param {String} topic Base topic inside the configured namespace | ||
* @param {Any} params Data for the `request` | ||
* @param {Object} options Options for the `request` | ||
* @return {Promise} The result of the `request` | ||
*/ | ||
Topics.prototype.request = function(topic, params, options) { | ||
return this.mediator.request(this.getTopic(topic), params, options); | ||
}; | ||
/** | ||
* Does a {@link Mediator.request} in the context of the namespaced topics | ||
* @param {String} topic Base topic inside the configured namespace | ||
* @param {Any} params Data for the `request` | ||
* @param {Object} options Options for the `request` | ||
* @return {Promise} The result of the `request` | ||
*/ | ||
Topics.prototype.publish = function(topic) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
args.unshift(this.getTopic(topic)); | ||
return this.mediator.publish.apply(this.mediator, args); | ||
}; | ||
module.exports = Topics; |
{ | ||
"name": "fh-wfm-mediator", | ||
"version": "0.4.0-rc1", | ||
"version": "1.0.0-pre.1", | ||
"description": "An implementation of the mediator pattern for use with WFM", | ||
"main": "lib/angular/mediator-ng.js", | ||
"types": "lib/angular/mediator-ng.d.ts", | ||
"repository": "https://github.com/feedhenry-raincatcher/raincatcher-mediator", | ||
"scripts": { | ||
"test": "grunt", | ||
"build": "grunt build", | ||
"prepublish": "npm run build" | ||
"test": "grunt" | ||
}, | ||
@@ -22,26 +19,15 @@ "keywords": [ | ||
"lodash": "^4.7.0", | ||
"mediator-js": "^0.9.9", | ||
"@types/bluebird": "^3.0.37", | ||
"@types/angular": "^1.6.3", | ||
"@types/lodash": "^4.14.52" | ||
"shortid": "^2.2.8" | ||
}, | ||
"devDependencies": { | ||
"@types/chai": "^3.4.34", | ||
"@types/mocha": "^2.2.38", | ||
"@types/node": "^7.0.5", | ||
"@types/sinon": "^1.16.34", | ||
"chai": "^3.5.0", | ||
"grunt": "^1.0.1", | ||
"grunt-contrib-clean": "^1.0.0", | ||
"grunt-contrib-copy": "^1.0.0", | ||
"grunt-eslint": "18.0.0", | ||
"grunt-mocha-test": "^0.13.2", | ||
"grunt-ts": "^6.0.0-beta.11", | ||
"grunt-tslint": "^4.0.0", | ||
"load-grunt-tasks": "^3.5.2", | ||
"mocha": "^3.2.0", | ||
"sinon": "^1.17.7", | ||
"ts-node": "^2.0.0", | ||
"tslint": "^4.4.2", | ||
"typescript": "^2.2.1" | ||
"sinon-as-promised": "^4.0.3", | ||
"sinon-chai": "^2.11.0" | ||
} | ||
} |
@@ -14,9 +14,35 @@ # FeedHenry RainCatcher mediator [![Build Status](https://travis-ci.org/feedhenry-raincatcher/raincatcher-mediator.png)](https://travis-ci.org/feedhenry-raincatcher/raincatcher-mediator) | ||
### Subscription Callbacks | ||
When passing a `callback` to a `mediator.subscribe` function, it is necessary to return a `Promise` if the operation is asynchronous or if the response from the subscriber is required. | ||
```javascript | ||
var mediator = require('fh-wfm-mediator'); | ||
var Promise = require('bluebird'); | ||
mediator.subscribe("wfm:topic", function(topicData) { | ||
return new Promise(function(resolve, reject) { | ||
doSomeAyncFunction(topicData, function(err, result){ | ||
err ? reject(err) : resolve(result); | ||
}); | ||
}); | ||
}); | ||
//The published topic will not resolve until all of the asynchronous subscribers have resolved / rejected | ||
//The `result` is the resolved value of the highest priority subscriber. | ||
mediator.publish("wfm:topic").then(function(result) { | ||
console.log("All of the subscribers have resolved.", result); | ||
}).catch(function(err) { | ||
console.error("An error occurred when executing topic wfm:topic", err); | ||
}); | ||
``` | ||
### `Topics` utilities | ||
This module also provides a fluent, promise-based API for subscribing to convention and adhering to the request-response pattern used throughout the RainCatcher modules and available through `mediator#request`. | ||
Namely if a `data:read` topic that is used to provide a feature such as reading data from a remote source asyncronously, the result of the operation is by convention published in the `done:data:read` topic, and if it results in an error, it is published to the `error:data:read` topic. | ||
This utility module helps with enforcing the same namespace for a set of related topics without repeating string literals or constants, and adhering to the convention above. It is available under [`lib/topics`](./lib/topics/index.js) with jsdoc comments. | ||
#### Example | ||
@@ -26,24 +52,49 @@ | ||
var mediator = require('fh-wfm-mediator'); | ||
var Topics = require('fh-wfm-mediator/lib/topics'); | ||
var Topic = require('fh-wfm-mediator/lib/topics'); | ||
var topics = new Topics(mediator) | ||
.withPrefix('wfm') | ||
.withEntity('user') | ||
//A set of topics for saving user data | ||
var userDataTopics = new Topic(mediator) | ||
.prefix('wfm:data') | ||
.entity('user') | ||
.on('read', function(id) { | ||
//asyncReadUser returns a Promise | ||
return asyncReadUser(id); | ||
}).on('update', function(userToUpdate) { | ||
//asyncReadUser returns a Promise | ||
return asyncUpdateUser(userToUpdate); | ||
}); | ||
new Topic(mediator) | ||
.prefix('wfm') | ||
.entity('user') | ||
// This will subscribe to wfm:user:read | ||
// and publish results to done:wfm:user:read:{id} | ||
// and errors to error:wfm:user:read:{id} | ||
.on('read', function(id) { | ||
// will request to 'data:user:read' | ||
return this.mediator.request(['data', this.entity, 'read'].join(':'), id); | ||
// will publish to 'wfm:user:data:read', which returns a Promise. | ||
return userDataTopics.publish('read', id).then(function(user) { | ||
//We have retrieved the user, we can apply any additional asynchronous operations we need when the resolving the user | ||
return readUserGroupInformation(user.id).then(function(groupInformation) { | ||
user.groupName = groupInformation.name; | ||
return user; | ||
}); | ||
}); | ||
}) | ||
// If you do not return a Promise from the handler function, | ||
// you must manually publish the result to another topic so it can be consumed | ||
.on('delete', function(id) { | ||
var self = this; | ||
this.mediator.request(this.entity + ':delete', id).then(function() { | ||
self.mediator.publish('done:ui:user:deleted:' + id); | ||
}).catch(function(e) { | ||
self.mediator.publish('error:ui:user:deleted:' + id, e); | ||
.on('update_location', function(id, location) { | ||
//If we don't want to wait for the subscribers to resolve, just return null. | ||
userDataTopics.publish('read', id).then(function(user) { | ||
//We have retrieved the user, we can apply any additional asynchronous operations we need when the resolving the user | ||
user.location = location; | ||
userDataTopics.publish('update', user); | ||
}); | ||
return null; | ||
}); | ||
mediator.publish('wfm:user:read', "userid1234").then(function(user) { | ||
//All of the subscribers have resolved. | ||
console.log("User read with id " + user.id + " and group name " + user.groupName); | ||
}).catch(function(err) { | ||
console.log("Error reading user information", err); | ||
}); | ||
``` | ||
@@ -77,3 +128,3 @@ | ||
... | ||
}) | ||
} | ||
``` | ||
@@ -87,5 +138,1 @@ | ||
``` | ||
# NPM Publishing | ||
This module is written in TypeScript, in order to publish it to npm as compiled JavaScript files along with TypeScript typings and source maps, run `npm run build` before `npm publish`. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
75481
3
9
1685
135
18
+ Addedshortid@^2.2.8
+ Addednanoid@2.1.11(transitive)
+ Addedshortid@2.2.16(transitive)
- Removed@types/angular@^1.6.3
- Removed@types/bluebird@^3.0.37
- Removed@types/lodash@^4.14.52
- Removedmediator-js@^0.9.9
- Removed@types/angular@1.8.9(transitive)
- Removed@types/bluebird@3.5.42(transitive)
- Removed@types/lodash@4.17.12(transitive)
- Removedmediator-js@0.9.9(transitive)