Topical.js
JavaScript PubSub library.
Quite a few pubsub libraries already exist. As is the usual case, no library suited either my needs or my tastes.
Some libraries force you to adopt a particular data format when sending messages between decoupled components. Others proved limited and/or buggy.
In general, the publish-subscribe pattern is not particularly difficult to implement. Simply expose a method to register subscribers, keep a mapping table of publishers and their respective subscribers, and route messages as they arrive to the appropriate consumers.
The novelty of this module is fourfold:
- Granularity. The library exposes an API which allows subscribing to a particular topic, subscribing to a topic filter, and/or subscribing to global broadcasts.
- Meta-information. The exposed broker is an event-emitter and continuously emits events about its comings and goings. In a dynamic system, this allows a straightforward means of monitoring messaging topology.
- Minimal assumptions. Few assumptions are made about transmitted data. The only hard requirement is that, when publishing or broadcasting, only one data argument is provided. To publish data involving multiple variables, simply bundle all variables into an
object
. - Flexilibity. While exposing a singleton broker, the library also provides a method for dynamically creating new brokers. This is convenient when wanting separate brokers for isolated components.
Irrespective of novelty, the principle aim of this library is to facilitate decoupled application architecture. While tight integration can be beneficial, a loosely coupled architecture enables easier testing, more modular design, and small components which do one thing well. Hopefully this library helps in this regard.
Installation
$ npm install topical
For use in the browser, use browserify.
Usage
To use the module,
var topical = ;
The module exports both a singleton and a constructor. The singleton is convenient for a shared application broker. To create several distinct brokers, use the constructor.
var createBroker = topicalctor;// Create a new broker:var broker = ;
Brokers have the following methods...
topical.topics()
Returns the list of topics.
var topics = topical;console;// returns [...]
topical.add( topic[, options] )
Registers a new topic with the broker. A topic is configurable and has the following options:
- max:
integer
value which specifies the maximum number of subscribers- default:
Number.MAX_VALUE
- default:
- duplicates:
boolean
value indicating whether duplicate subscribers are permitted- default:
false
- default:
To register a new topic,
var opts ='max': 10'duplicates': true;topical;console;// returns [ 'beep' ]
topical.remove( topic )
Removes a topic and all associated subscribers. The topic may be either a string
or a regular expression. For regular expressions, any topics matching the regular expression are removed.
topical;console;// returns []topical;console;// returns [ 'foo' ]
topical.list( clbk )
Subscribes a callback
to public broadcasts. Public broadcasts are not bound to a particular topic; instead, they are dispatched to all publicly listed subscribers (akin to opening a phonebook and calling every publicly listed number).
topical;{console;}
Note: by default, subscribers are unlisted (becoming listed is opt-in:)).
topical.unlist( clbk )
Unsubscribes a callback
from public broadcasts.
topical;
Note: this does not affect a callback
serving as a subscriber to particular topics.
topical.subscribe( topic, clbk )
Subscribes a callback
to a topic. The topic may be either a string
or a regular expression. For regular expressions, a callback
will be subscribed to any topics matching the regular expression.
topical;// Subscribe to a single topic:topical;// Subscribe to any topics matching a pattern:topical;// ...subscribes `bar` to `beep` and `bop` topics{console;}{console;}
Note: a topic must exist before attempting to subscribe. If a topic does not exist, an error
event is emitted (see below).
Additionally, if a topic is oversubscribed
, i.e., reached its maximum subscriber limit,, an error
event is emitted.
topical.unsubscribe( topic, clbk )
Unsubscribes a callback
from a topic. The topic may be either a string
or a regular expression. For regular expressions, a callback
will be unsubscribed from any topics matching the regular expression.
topical;// Subscribe to a single topic:topical;// Subscribe to any topics matching a pattern:topical;// ...subscribes `bar` to `beep`, `bop`, and `biff` topics// Unsubscribe from a single topic:topical;// Unsubscribe from any topics matching a pattern:topical;// ...unsubscribes `bar` from `beep` and `bop`{console;}{console;}
topical.once( topic, clbk )
Subscribes a callback
to a topic and unsubscribes the callback
after its first invocation. This is useful, for example, when you need to perform some initialization the first time an event is published, but not after.
topical;{console;// returns only 'boop'}
topical.publish( topic, value )
Publishes a value
to a specified topic.
topical;{console;// returns `boop`}
topical.broadcast( value )
Broadcasts a value
to all publicly listed subscribers.
topical;{console;// returns 'Beep'}{console;// returns 'Beep'}
Events
All events emit an object (event
) with topic
and data
properties. If a property is not relevant to the event, the property value is null
.
Note: the data
value type is specific to the event. For example, for error
events, the data
value is a string
. For subscribe
/unsubscribe
events, the data
value is a function
. See the appropriate event to determine the return value type.
'add'
Emitted when a new topic is added to the broker.
topical;
'remove'
Emitted when a topic is removed from the broker.
topical;
'list'
Emitted when a subscriber becomes publicly listed (i.e., open to receiving public broadcasts).
topical;topical;{console;}
'unlist'
Emitted when a subscriber unsubscribes from public broadcasts.
topical;topical;{console;}
'subscribe'
Emitted when a topic has a new subscriber.
topical;topical;{console;}
'unsubscribe'
Emitted when a subscriber unsubscribes from a topic.
topical;topical;{console;}
'publish'
Emitted when an event is published to a topic.
topical;topical;
Note: in line with the NSA ;), only meta data is revealed, not the message contents. Sorry...no eavesdropping on published content.
'broadcast'
Emitted when a broadcast is made to all publicly listed subscribers.
topical;topical;
'error'
Emitted whenever an error
is encountered.
- topic is oversubscribed (maximum number of subscribers exceeded)
- attempting to subscribe to an unregistered topic
topical;topical;
Notes
When publishing/broadcasting messages, the value
type is left to the user given her particular use case. Internally, topical
uses a simple object
with two fields: topic
and data
, where data
may assume any value type. What value
type you decide to use is up to you.
Examples
Consider the following toy example. First, suppose we have the following application components...
Component 1:
var topical = ;module {topical;};{console;}
Component 2:
var topical = ;module {topical;};{console;}{console;}
Component 3: collects statistics...
var topical = ;// Initialize a stats object:var stats ='topics': {}'broadcasts': 0'public': 0;// Listeners...topical;topical;topical;topical;topical;topical;topical;topical;{statstopics eventtopic ='numSubscribers': 0'numPublications': 0;}{delete statstopics eventtopic ;}{statstopics eventtopic numSubscribers += 1;}{statstopics eventtopic numSubscribers -= 1;}{statspublic += 1;}{statspublic -= 1;}{statstopics eventtopic numPublications += 1;}{statsbroadcasts += 1;}// EXPORTS //moduleexports = stats;
Let's now create a central broker...
var topical =comp1 =comp2 =stats = ;// Create some topics...var topics ='beep''boop''bap''baz''foo';for var i = 0; i < topicslength; i++topical;// Run the components:;;// Simulate some chatter...var vec = 0 02 04 06 08randidxtopicmsg;for var j = 0; j < 1000; j++rand = Math;if rand > 09topical;continue;for var k = 0; k < veclength; k++if veck > randidx = k-1;break;topic = topics idx ;if rand > 05msg = 'bebop';elsemsg = 'woot';topical;// Output the statistics:console;
To run the example from the top-level application directory,
$ node ./examples/index.js
Tests
Unit
Unit tests use the Mocha test framework with Chai assertions. To run the tests, execute the following command in the top-level application directory:
$ make test
All new feature development should have corresponding unit tests to validate correct functionality.
Test Coverage
This repository uses Istanbul as its code coverage tool. To generate a test coverage report, execute the following command in the top-level application directory:
$ make test-cov
Istanbul creates a ./reports/coverage
directory. To access an HTML version of the report,
$ make view-cov
License
Copyright
Copyright © 2014. Athan Reines.