|
| 1 | + |
| 2 | + |
| 3 | +[](./docs/publisher.md) |
| 4 | + |
| 5 | + |
| 6 | +# @alphanull/publisher |
| 7 | + |
| 8 | +Publisher is a JavaScript Publish/Subscribe (Pub/Sub) library crafted to handle event-driven communication. Provides pub/sub functionality with extensive wildcard support, async/sync publishing, priority and invocation options, content based filtering & more. |
| 9 | + |
| 10 | +The heart of Publisher lies in its uniquely optimized hierarchical data structure, providing fast subscriber matching even with extensive subscription sets. Unlike traditional flat Pub/Sub systems, Publisher allows you to easily organize events into structured topics and leverage wildcard subscriptions for additional flexibility. |
| 11 | + |
| 12 | +Whether you're building scalable web applications or complex front-end architectures, Publisher ensures your events and notifications are handled gracefully and reliably, delivering ease of use combined with powerful features. |
| 13 | + |
| 14 | +## Features |
| 15 | + |
| 16 | +- **Topic Hierarchy & Wildcards**: Manage event complexity with a structured hierarchy and wildcard topic matching. |
| 17 | +- **Persisted Messages**: Ensure subscribers never miss critical events, delivering persistent messages immediately upon subscribing. |
| 18 | +- **Priority & Invocations**: Gain fine-grained control over execution order and limit subscription triggers, improving predictability and efficiency. |
| 19 | +- **Async & Exception Handling**: Dispatch events asynchronously or synchronously with built-in exception handling. |
| 20 | +- **Conditional Execution**: Execute subscriptions only when specific conditions are met. |
| 21 | +- **Global Configuration**: Configure default behavior globally for asynchronous dispatch, error handling, and unsubscribing behavior. |
| 22 | + |
| 23 | +--- |
| 24 | + |
| 25 | +## Installation |
| 26 | + |
| 27 | + |
| 28 | +### via NPM |
| 29 | + |
| 30 | +**ATTN: Package is not on npm yet due to namespace clearance!** |
| 31 | + |
| 32 | +```bash |
| 33 | +npm install @alphanull/publisher <<< not here yet! |
| 34 | +``` |
| 35 | + |
| 36 | +### via CDN |
| 37 | + |
| 38 | +**Also, no CDN (yet)** |
| 39 | + |
| 40 | +[Download latest version](https://??????) from ???????? |
| 41 | + |
| 42 | +### via GitHub |
| 43 | + |
| 44 | +[Download release](https://github.com/alphanull/publisher/releases) from GitHub |
| 45 | + |
| 46 | +------ |
| 47 | + |
| 48 | +## Usage |
| 49 | + |
| 50 | +### 1. Initialization |
| 51 | + |
| 52 | +publisher can be used as ES6 module (recommended) but also via `require` in NodeJS or with direct access to a global variable: |
| 53 | + |
| 54 | +## ES6 |
| 55 | + |
| 56 | +```javascript |
| 57 | +import { publish, subscribe, unsubscribe } from '@alphanull/publisher'; |
| 58 | +``` |
| 59 | + |
| 60 | +## CommonJS |
| 61 | + |
| 62 | +```javascript |
| 63 | +const { publish, subscribe, unsubscribe } = require('@alphanull/publisher'); |
| 64 | +``` |
| 65 | + |
| 66 | +## Global Variable |
| 67 | + |
| 68 | +```html |
| 69 | +<script src="path/to/publisher.min.cjs"></script> |
| 70 | +``` |
| 71 | + |
| 72 | +```javascript |
| 73 | +const { publish, subscribe, unsubscribe } = publisher; |
| 74 | +``` |
| 75 | + |
| 76 | + |
| 77 | +### 2. Basic Usage |
| 78 | + |
| 79 | +Quickly set up a simple Pub/Sub interaction: |
| 80 | + |
| 81 | +```javascript |
| 82 | +import { publish, subscribe, unsubscribe } from '@alphanull/publisher'; |
| 83 | + |
| 84 | +const handler = data => { |
| 85 | + console.log(`User logged in: ${data.username}`); |
| 86 | +} |
| 87 | + |
| 88 | +// Receiver: subscribe to a specific topic |
| 89 | +const token = subscribe('login', handler); |
| 90 | + |
| 91 | +// Sender: publish an event |
| 92 | +publish('login', { username: 'Alice' }); |
| 93 | + |
| 94 | +// Receiver: unsubscribe using the token (recommended) |
| 95 | +unsubscribe(token); |
| 96 | + |
| 97 | +// Receiver: alternatively, unsubscribe using topic and handler |
| 98 | +unsubscribe('login', handler); |
| 99 | +``` |
| 100 | + |
| 101 | +--- |
| 102 | + |
| 103 | +### 3. Hierarchy and Wildcards |
| 104 | + |
| 105 | +By utilizing topic hierarchies and wildcards, you can subscribe to multiple events. A hierachy is created by using the `/` delimiter to create topic segments, while a `*` is used to match any topic segment: |
| 106 | + |
| 107 | +```javascript |
| 108 | +// Subscribe to ALL topics |
| 109 | +subscribe('*', (data, topic) => { |
| 110 | + console.log(`Event ${topic} received:`, data); |
| 111 | +}); |
| 112 | + |
| 113 | +// Subscribe to all "user"-related topics, INCLUDING "user" |
| 114 | +subscribe('user', (data, topic) => { |
| 115 | + console.log(`Event ${topic} received:`, data); |
| 116 | +}); |
| 117 | + |
| 118 | +// Subscribe to all "user"-related topics, EXCLUDING "user" |
| 119 | +subscribe('user/*', (data, topic) => { |
| 120 | + console.log(`Event ${topic} received:`, data); |
| 121 | +}); |
| 122 | + |
| 123 | +// Matching multiple topics with wildcards |
| 124 | +subscribe('app/*/update', (data, topic) => { |
| 125 | + console.log(`Update from ${topic}:`, data); |
| 126 | +}); |
| 127 | + |
| 128 | +publish('user/logout', { username: 'Bob' }); // triggers first, second & third subscriber |
| 129 | +publish('app/profile/update', { username: 'Charlie' }); // triggers first and fourth subscribers |
| 130 | +publish('app/settings/update', { theme: 'dark' }); // triggers first and fourth subscribers |
| 131 | +``` |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +### 4. Advanced Unsubscribe: Multiple Tokens, Lenient Unsubscribe |
| 136 | + |
| 137 | +You can also use an array of tokens to quickly unsubscribe multiple handlers. In addition, adding `true` to the second argument (or the third, in case you use topic/handler for unsubscribe) does not fail with an error if the matching token was not found. |
| 138 | + |
| 139 | +```javascript |
| 140 | +const tokens = [ |
| 141 | + subscribe('topic/1', handler), |
| 142 | + subscribe('topic/2', handler) |
| 143 | +]; |
| 144 | + |
| 145 | +// Batch unsubscribe |
| 146 | +unsubscribe(tokens); |
| 147 | + |
| 148 | +// Lenient unsubscribe, silently ignores non-existing tokens |
| 149 | +unsubscribe(9999, true); |
| 150 | +``` |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +### 5. Async and Sync Usage, Cancellation |
| 155 | + |
| 156 | +By default, all events are sent asynchronously. You can override this behavior globally (see 10.) or with individual `publish` actions by using `async: false` as an option. In addition, when using synchronous `publish`, any subscriber is able to cancel an event, so that subsequent subscribers are not notified anymore. So basically, this works similar to the cancellation of DOM Events. |
| 157 | + |
| 158 | +```javascript |
| 159 | +// return false in a handler cancels the chain |
| 160 | +subscribe('sync/event', () => false); |
| 161 | + |
| 162 | +// Synchronous event publishing can be canceled |
| 163 | +publish('sync/event', {}, { async: false, cancelable: true }); |
| 164 | +``` |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +### 6. Priority |
| 169 | + |
| 170 | +Usually, subscribers are notifed in the order they subscribed, i.e. the first subscriber is receiving the first message. You can change this behavior adding a `priority` option, where higher numbers are executed first, with `0` being the default. |
| 171 | + |
| 172 | +```javascript |
| 173 | +// Control subscriber order with priorities |
| 174 | +subscribe('priority/event', () => console.log('second'), { priority: 1 }); |
| 175 | +subscribe('priority/event', () => console.log('first'), { priority: 2 }); |
| 176 | + |
| 177 | +publish('priority/event'); |
| 178 | +``` |
| 179 | + |
| 180 | +--- |
| 181 | + |
| 182 | +### 7. Invocations |
| 183 | + |
| 184 | +It is also possible to limit the number of handler invocations by adding the `invocations` option, this being a positive number counting down when the handler is called. Once the counter reaches `0` the handler is automatically unsubscribed. For example, the following code executes the handler only on the first `publish` occurence: |
| 185 | + |
| 186 | +```javascript |
| 187 | +// Limit subscription invocations |
| 188 | +subscribe('limited/event', () => console.log('I only execute once'), { invocations: 1 }); |
| 189 | + |
| 190 | +publish('limited/event'); // triggers handler |
| 191 | +publish('limited/event'); // not triggered anymore, handler was unsubscribed |
| 192 | +``` |
| 193 | + |
| 194 | +### 8. Conditional Execution |
| 195 | + |
| 196 | +Run subscriptions based on conditional logic, so that the handler is only invoked if the function specified by the `condition` option returns true: |
| 197 | + |
| 198 | +```javascript |
| 199 | +subscribe('data/event', data => { |
| 200 | + console.log('Condition met:', data); |
| 201 | +}, { |
| 202 | + condition: data => data.status === 'success' |
| 203 | +}); |
| 204 | + |
| 205 | +publish('data/event', { status: 'success' }); // triggers subscriber |
| 206 | +publish('data/event', { status: 'error' }); // ignored |
| 207 | +``` |
| 208 | + |
| 209 | +--- |
| 210 | + |
| 211 | +### 9. Persistency |
| 212 | + |
| 213 | +Ensure certain messages are received even when the subscription is done after the actual message was already sent. For this to happen, _both_ `publish` and `subscribe` have to use the `persist: true` option. It is also possible to remove a perssitent message later on using `removePersistentMessage`. |
| 214 | + |
| 215 | +```javascript |
| 216 | +import { publish, subscribe, removePersistentMessage } from './publisher.js'; |
| 217 | + |
| 218 | +// make message persistent |
| 219 | +publish('app/ready', { status: 'ready' }, { persist: true }); |
| 220 | + |
| 221 | +// Subscribers immediately receive persistent messages upon subscription |
| 222 | +subscribe('app/ready', data => console.log('Persistently received:', data), { persist: true }); |
| 223 | + |
| 224 | +// after removing, later subscriber don't receive the event anymore |
| 225 | +removePersistentMessage('app/ready'); |
| 226 | +``` |
| 227 | + |
| 228 | +--- |
| 229 | + |
| 230 | +### 10. Error Handling |
| 231 | + |
| 232 | +By default, if a handler throws an Error, it is caught by the publisher so that subsequent subscribers are still being executed. Instead the error is output to the console (if possible). This behavior can be changed globally, or per `publish` so that exceptions are not caught anymore. |
| 233 | + |
| 234 | +```javascript |
| 235 | +subscribe('error/event', () => { |
| 236 | + throw new Error('Subscriber error!'); |
| 237 | +}); |
| 238 | + |
| 239 | +subscribe('error/event', () => { |
| 240 | + console.log('I still might be executed'); |
| 241 | +}); |
| 242 | + |
| 243 | +// Errors caught internally, other subscribers remain unaffected |
| 244 | +publish('error/event', data , { handleExceptions: true }); |
| 245 | + |
| 246 | +// Throws an error, publishing is halted |
| 247 | +publish('error/event', data , { handleExceptions: false }); |
| 248 | +``` |
| 249 | + |
| 250 | +--- |
| 251 | + |
| 252 | +### 11. Global Configuration |
| 253 | + |
| 254 | +Configure Publisher.js globally to tailor its behavior. All subsequent actions will use the newly set option(s), unless locally overidden. |
| 255 | + |
| 256 | +```javascript |
| 257 | +import { configure } from './publisher.js'; |
| 258 | + |
| 259 | +// Equivalent to the default configuration |
| 260 | +configure({ |
| 261 | + async: true, // Global async dispatch |
| 262 | + handleExceptions: true, // Global error handling |
| 263 | + lenientUnsubscribe: true // No errors on unsubscribing non-existent subscribers |
| 264 | +}); |
| 265 | +``` |
| 266 | + |
| 267 | +--- |
| 268 | + |
| 269 | +## Docs |
| 270 | + |
| 271 | +For more detailled docs, see [JSDoc Documentation](docs/publisher.md) |
| 272 | + |
| 273 | +## License |
| 274 | + |
| 275 | +[MIT](https://opensource.org/license/MIT) |
| 276 | + |
| 277 | +Copyright © 2015-present Frank Kudermann @ alphanull.de |
0 commit comments