Gathering detailed insights and metrics for postal
Gathering detailed insights and metrics for postal
JavaScript pub/sub library supporting advanced subscription features, and several helpful add-ons.
npm install postal
Typescript
Module System
Min. Node Version
Node Version
NPM Version
96.9
Supply Chain
99.1
Quality
80
Maintenance
100
Vulnerability
99.6
License
JavaScript (95.88%)
HTML (3.9%)
CSS (0.22%)
Love this project? Help keep it running — sponsor us today! 🚀
Total Downloads
10,490,242
Last Day
4,983
Last Week
22,995
Last Month
98,459
Last Year
972,120
MIT License
2,832 Stars
369 Commits
191 Forks
92 Watchers
7 Branches
24 Contributors
Updated on Feb 16, 2025
Latest Version
2.0.6
Package Id
postal@2.0.6
Size
19.61 kB
NPM Version
6.7.0
Node Version
11.15.0
Published on
Jul 30, 2021
Cumulative downloads
Total Downloads
Last Day
7.8%
4,983
Compared to previous day
Last Week
-1.8%
22,995
Compared to previous week
Last Month
31.5%
98,459
Compared to previous month
Last Year
-7.1%
972,120
Compared to previous year
See the changelog for information on if the current version of postal has breaking changes compared to any older version(s) you might be using. Version 0.11+ removed the dependency on ConduitJS and significantly improved publishing performance. Version 1.0+ added an optional embedded-and-customized-lodash build.
Postal.js is an in-memory message bus - very loosely inspired by AMQP - written in JavaScript. Postal.js runs in the browser, or on the server using node.js. It takes the familiar "eventing-style" paradigm (of which most JavaScript developers are familiar) and extends it by providing "broker" and subscriber implementations which are more sophisticated than what you typically find in simple event emitting/aggregation.
If you want to subscribe to a message, you tell postal what channel and topic to listen on (channel is optional, as postal provides a default one if you don't), and a callback to be invoked when a message arrives:
1var subscription = postal.subscribe({ 2 channel: "orders", 3 topic: "item.add", 4 callback: function(data, envelope) { 5 // `data` is the data published by the publisher. 6 // `envelope` is a wrapper around the data & contains 7 // metadata about the message like the channel, topic, 8 // timestamp and any other data which might have been 9 // added by the sender. 10 } 11});
The publisher might do something similar to this:
1postal.publish({ 2 channel: "orders", 3 topic: "item.add", 4 data: { 5 sku: "AZDTF4346", 6 qty: 21 7 } 8});
A channel is a logical partition of topics. Conceptually, it's like a dedicated highway for a specific set of communication. At first glance it might seem like that's overkill for an environment that runs in an event loop, but it actually proves to be quite useful. Every library has architectural opinions that it either imposes or nudges you toward. Channel-oriented messaging nudges you to separate your communication by bounded context, and enables the kind of fine-tuned visibility you need into the interactions between components as your application grows.
While the above code snippets work just fine, it's possible to get a more terse API if you want to hang onto an ChannelDefinition
instance - which is really a convenience wrapper around publishing and subscribing on a specific channel (instead of having to specify it each time):
1var channel = postal.channel("orders"); 2 3var subscription = channel.subscribe("item.add", function(data, envelope) { 4 /*do stuff with data */ 5}); 6 7channel.publish("item.add", { 8 sku: "AZDTF4346", 9 qty: 21 10});
Using a local message bus can enable to you de-couple your web application's components in a way not possible with other 'eventing' approaches. In addition, strategically adopting messaging at the 'seams' of your application (e.g. - between modules, at entry/exit points for browser data and storage) can not only help enforce better overall architectural design, but also insulate you from the risks of tightly coupling your application to 3rd party libraries. For example:
SubscriptionDefinition
object.Dang - you've read this far! AMAZING! If I had postal t-shirts, I'd send you one!
These four concepts are central to postal:
Most eventing libraries focus on providing Observer Pattern utilities to an instance (i.e. - creating an event emitter), OR they take the idea of an event emitter and turn it into an event aggregator (so a single channel, stand alone emitter that acts as a go-between for publishers and subscribers). I'm a big fan of the Observer Pattern, but its downside is that it requires a direct reference to the subject in order to listen to events. This can become a big source of tight coupling in your app, and once you go down that road, your abstractions tend to leak, if not hemorrhage.
postal is not intended to replace Observer pattern scenarios. Inside a module, where it makes sense for an observer to have a direct reference to the subject, by all means, use the Observer pattern. However - when it comes to inter-module communication (view-to-view, for example), inter-library communication, cross frame communication, or even hierarchical state change notifications with libraries like ReactJS, postal is the glue to help your components communicate without glueing them with tight coupling.
In my experience, seeing publish and subscribe calls all over application logic is usually a strong code smell. Ideally, the majority of message-bus integration should be concealed within application infrastructure. Having a hierarchical-wildcard-bindable topic system makes it very easy to keep things concise (especially subscribe calls!). For example, if you have a module that needs to listen to every message published on the ShoppingCart channel, you'd simply subscribe to "#", and never have to worry about additional subscribes on that channel again - even if you add new messages in the future. If you need to capture all messages with ".validation" at the end of the topic, you'd simply subscribe to "#.validation". If you needed to target all messages with topics that started with "Customer.", ended with ".validation" and had only one period-delimited segment in between, you'd subscribe to "Customer.*.validation" (thus your subscription would capture Customer.address.validation and Customer.email.validation").
Here are four examples of using Postal. All of these examples - AND MORE! - can run live here. Be sure to check out the wiki for API documentation and conceptual walk-throughs.
1// This gets you a handle to the default postal channel... 2// For grins, you can get a named channel instead like this: 3// var channel = postal.channel( "DoctorWho" ); 4var channel = postal.channel(); 5 6// subscribe to 'name.change' topics 7var subscription = channel.subscribe( "name.change", function ( data ) { 8 $( "#example1" ).html( "Name: " + data.name ); 9} ); 10 11// And someone publishes a name change: 12channel.publish( "name.change", { name : "Dr. Who" } ); 13 14// To unsubscribe, you: 15subscription.unsubscribe(); 16 17// postal also provides a top-level ability to subscribe/publish 18// used primarily when you don't need to hang onto a channel instance: 19var anotherSub = postal.subscribe({ 20 channel : "MyChannel", 21 topic : "name.change", 22 callback : function(data, envelope) { 23 $( "#example1" ).html( "Name: " + data.name ); 24 } 25}); 26 27postal.publish({ 28 channel : "MyChannel", 29 topic : "name.change", 30 data : { 31 name : "Dr. Who" 32 } 33});
The *
symbol represents "one word" in a topic (i.e - the text between two periods of a topic). By subscribing to "*.changed"
, the binding will match name.changed
& location.changed
but not changed.companion
.
1var chgSubscription = channel.subscribe( "*.changed", function ( data ) { 2 $( "<li>" + data.type + " changed: " + data.value + "</li>" ).appendTo( "#example2" ); 3} ); 4channel.publish( "name.changed", { type : "Name", value : "John Smith" } ); 5channel.publish( "location.changed", { type : "Location", value : "Early 20th Century England" } ); 6chgSubscription.unsubscribe();
The #
symbol represents 0-n number of characters/words in a topic string. By subscribing to "DrWho.#.Changed"
, the binding will match DrWho.NinthDoctor.Companion.Changed
& DrWho.Location.Changed
but not Changed
.
1var starSubscription = channel.subscribe( "DrWho.#.Changed", function ( data ) { 2 $( "<li>" + data.type + " Changed: " + data.value + "</li>" ).appendTo( "#example3" ); 3} ); 4channel.publish( "DrWho.NinthDoctor.Companion.Changed", { type : "Companion Name", value : "Rose" } ); 5channel.publish( "DrWho.TenthDoctor.Companion.Changed", { type : "Companion Name", value : "Martha" } ); 6channel.publish( "DrWho.Eleventh.Companion.Changed", { type : "Companion Name", value : "Amy" } ); 7channel.publish( "DrWho.Location.Changed", { type : "Location", value : "The Library" } ); 8channel.publish( "TheMaster.DrumBeat.Changed", { type : "DrumBeat", value : "This won't trigger any subscriptions" } ); 9channel.publish( "Changed", { type : "Useless", value : "This won't trigger any subscriptions either" } ); 10starSubscription.unsubscribe();
1var dupChannel = postal.channel( "Blink" ), 2 dupSubscription = dupChannel.subscribe( "WeepingAngel.#", function( data ) { 3 $( '<li>' + data.value + '</li>' ).appendTo( "#example4" ); 4 }).distinctUntilChanged(); 5// demonstrating multiple channels per topic being used 6// You can do it this way if you like, but the example above has nicer syntax (and *much* less overhead) 7dupChannel.publish( "WeepingAngel.DontBlink", { value:"Don't Blink" } ); 8dupChannel.publish( "WeepingAngel.DontBlink", { value:"Don't Blink" } ); 9dupChannel.publish( "WeepingAngel.DontEvenBlink", { value:"Don't Even Blink" } ); 10dupChannel.publish( "WeepingAngel.DontBlink", { value:"Don't Close Your Eyes" } ); 11dupChannel.publish( "WeepingAngel.DontBlink", { value:"Don't Blink" } ); 12dupChannel.publish( "WeepingAngel.DontBlink", { value:"Don't Blink" } ); 13dupSubscription.unsubscribe();
Please visit the postal.js wiki for API documentation, discussion of concepts and links to blogs/articles on postal.js.
There are four main ways you can extend Postal:
ChannelDefinition
and SubscriptionDefinition
prototypes - see postal.request-response for an example of this.bindingResolver
matches subscriptions to message topics being published. You may not care for the AMQP-style bindings functionality. No problem! Write your own resolver object that implements a compare
and reset
method and swap the core version out with your implementation by calling: postal.configuration.resolver = myWayBetterResolver
.It's also possible to extend the monitoring of messages passing through Postal by adding a "wire tap". A wire tap is a callback that will get invoked for any published message (even if no actual subscriptions would bind to the message's topic). Wire taps should not be used in lieu of an actual subscription - but instead should be used for diagnostics, logging, forwarding (to a websocket publisher or a local storage wrapper, for example) or other concerns that fall along those lines. This repository used to include a console logging wiretap called postal.diagnostics.js - you can now find it here in it's own repo. This diagnostics wiretap can be configured with filters to limit the firehose of message data to specific channels/topics and more.
lib/postal.js
) is what you would normally use if you're already using lodash in your app.lib/postal.lodash.js
build output might be of interest to you if these things are true:
npm install
(to install all deps)bower install
(yep, we're using at least one thing only found on bower in the local project runner)npm run build
(this is just an alias for gulp
at the moment, which you can also use) - then check the lib folder for the outputnpm test
(or npm run test-lodash
to test the custom lodash build output)npm start
npm run coverage
and gulp report
(respectively) in order to generate them, as they are not stored with the repo.Please - by all means! While I hope the API is relatively stable, I'm open to pull requests. (Hint - if you want a feature implemented, a pull request gives it a much higher probability of being included than simply asking me.) As I said, pull requests are most certainly welcome - but please include tests for your additions. Otherwise, it will disappear into the ether.
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
Found 3/26 approved changesets -- score normalized to 1
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
61 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-02-10
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More