Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
delivery:
name: Node 20.x
name: Node 24.x
runs-on: ubuntu-latest

steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

strategy:
matrix:
node: [18.x, 20.x]
node: [20.x, 22.x, 24.x]

steps:
- name: ☁️ Check out source code
Expand Down
133 changes: 97 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,28 @@ Simple typesafe cross-platform pubsub communication between different single pag

## Table of Contents

- [Purpose](#purpose)
- [Installation](#installation)
- [Usage](#usage)
- [Advanced Schema](#advanced-schema)
- [API](#api)
- [Register](#register)
- [Unregister](#unregister)
- [Subscribe](#subscribe)
- [Publish](#publish)
- [Get Latest](#get-latest)
- [Get Schema](#get-schema)
- [Event Bus](#event-bus)
- [Table of Contents](#table-of-contents)
- [Purpose](#purpose)
- [Installation](#installation)
- [Usage](#usage)
- [Advanced Schema](#advanced-schema)
- [API](#api)
- [Constructor Options](#constructor-options)
- [Register](#register)
- [Parameters](#parameters)
- [Unregister](#unregister)
- [Parameters](#parameters-1)
- [Subscribe](#subscribe)
- [Parameters](#parameters-2)
- [Publish](#publish)
- [Parameters](#parameters-3)
- [Get Latest](#get-latest)
- [Parameters](#parameters-4)
- [Get Schema](#get-schema)
- [Parameters](#parameters-5)
- [Clear Replay](#clear-replay)
- [Parameters](#parameters-6)

---

Expand Down Expand Up @@ -103,8 +114,8 @@ class PublisherElement extends HTMLElement {
this.render();
this.firstChild && this.firstChild.addEventListener('click', this.send);
}
send() {
eventBus.publish('namespace:eventName', true);
async send() {
await eventBus.publish('namespace:eventName', true);
}
render() {
this.innerHTML = `<button type="button">send</button>`;
Expand All @@ -118,22 +129,35 @@ class PublisherElement extends HTMLElement {
`Fragment Two - React Component`

```typescript
import React, { useState, useEffect } from 'react';
import React from 'react';

function SubscriberComponent() {
const [isFavorite, setFavorite] = useState(false);
// Custom hook for event bus subscriptions
function useEventSubscription<T>(eventName: string, schema: object) {
const [value, setValue] = React.useState<T>();

React.useEffect(() => {
let sub: { unsubscribe(): void };

useEffect(() => {
function handleSubscribe(favorite: boolean) {
setFavorite(favorite);
async function init() {
try {
eventBus.register(eventName, schema);
sub = await eventBus.subscribe<T>(eventName, (event) => setValue(event.payload));
} catch (e) {
console.error(`Failed to subscribe to ${eventName}:`, e);
}
}
eventBus.register('namespace:eventName', { type: 'boolean' });
const sub = eventBus.subscribe<boolean>('namespace:eventName', handleSubscribe);
return function cleanup() {
sub.unsubscribe();

init();
return () => {
if (sub) sub.unsubscribe();
};
}, []);
}, [eventName, schema]);

return value;
}

function SubscriberComponent() {
const isFavorite = useEventSubscription<boolean>('namespace:eventName', { type: 'boolean' });
return isFavorite ? 'This is a favorite' : 'This is not interesting';
}
```
Expand Down Expand Up @@ -205,8 +229,8 @@ export class StoreComponent implements OnInit, OnDestroy {
/* handle new deals ... */
}

onSend() {
eventBus.publish('store:addToCart', {
async onSend() {
await eventBus.publish('store:addToCart', {
name: 'Milk',
amount: '1000 ml',
price: 0.99,
Expand All @@ -228,20 +252,37 @@ export class StoreComponent implements OnInit, OnDestroy {

## API

### Constructor Options

The EventBus constructor accepts an options object with the following properties:

```typescript
interface EventBusOptions {
/**
* The logging level for the event bus
* - 'none': No logging
* - 'error': Only log errors (default)
* - 'warn': Log warnings and errors
* - 'info': Log everything
*/
logLevel?: 'none' | 'error' | 'warn' | 'info';
}
```

### Register

Register a schema for the specified event type and equality checking on subsequent registers. Subsequent registers must use an equal schema or an error will be thrown.

```typescript
register(channel: string, schema: object): boolean;
register(channel: string, schema: Record<string, unknown>): boolean;
```

#### Parameters

| Name | Type | Description |
| ------- | -------- | ---------------------------------------------------- |
| channel | `string` | name of event channel to register schema to |
| schema | `object` | all communication on channel must follow this schema |
| Name | Type | Description |
| ------- | ------------------------- | ---------------------------------------------------- |
| channel | `string` | name of event channel to register schema to |
| schema | `Record<string, unknown>` | all communication on channel must follow this schema |

**Returns** - returns true if event channel already existed of false if a new one was created.

Expand Down Expand Up @@ -272,11 +313,13 @@ with an optional replay of last event at initial subscription.
The channel may be the wildcard `'*'` to subscribe to all channels.

```typescript
subscribe<T>(channel: string, callback: Callback<T>): { unsubscribe(): void };
async subscribe<T>(channel: string, callback: Callback<T>): Promise<{ unsubscribe(): void }>;

subscribe<T>(channel: string, replay: boolean, callback: Callback<T>): { unsubscribe(): void };
async subscribe<T>(channel: string, replay: boolean, callback: Callback<T>): Promise<{ unsubscribe(): void }>;
```

Note: Subscribe is an async function that returns a Promise with the subscription.

Callbacks will be fired when event is published on a subscribed channel with the argument:

```typescript
Expand All @@ -300,10 +343,10 @@ Callbacks will be fired when event is published on a subscribed channel with the

### Publish

Publish to event channel with an optional payload triggering all subscription callbacks.
Publish to event channel with an optional payload triggering all subscription callbacks. Returns a Promise that resolves when all callbacks have completed.

```typescript
publish<T>(channel: string, payload?: T): void;
async publish<T>(channel: string, payload?: T): Promise<void>;
```

#### Parameters
Expand All @@ -313,7 +356,7 @@ publish<T>(channel: string, payload?: T): void;
| channel | `string` | name of event channel to send payload on |
| payload | `any` | payload to be sent |

**Returns** - void
**Returns** - Promise that resolves when all callbacks have completed

---

Expand Down Expand Up @@ -350,3 +393,21 @@ getSchema<T>(channel: string): any | undefined;
| channel | `string` | name of the event channel to fetch the schema from |

**Returns** - the schema or `undefined`

---

### Clear Replay

Clears the replay event for the specified channel.

```typescript
clearReplay(channel: string): boolean;
```

#### Parameters

| Name | Type | Description |
| ------- | -------- | -------------------------------------------------- |
| channel | `string` | name of the event channel to clear the replay from |

**Returns** - returns true if the replay event was cleared, false otherwise.
53 changes: 31 additions & 22 deletions docs/react-component.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
const e = React.createElement;

function ReactComponent() {
const [isFavorite, setFavorite] = React.useState(false);
function useEventSubscription(eventName, schema) {
const [value, setValue] = React.useState(false);

React.useEffect(() => {
function handleSubscribe(favorite) {
setFavorite(favorite);
}
let sub;

eventBus.register('namespace:eventName', { type: 'boolean' });
const sub = eventBus.subscribe('namespace:eventName', handleSubscribe);

try {
// Re-registering again with another schema is not allowed
eventBus.register('namespace:eventName', { type: 'string' });
} catch (e) {
console.warn(e);
console.debug('eventType:', e.eventType);
console.debug('schema:', e.schema);
console.debug('newSchema:', e.newSchema);
async function init() {
try {
eventBus.register(eventName, schema);
sub = await eventBus.subscribe(eventName, (event) => setValue(event.payload));
} catch (e) {
console.warn(`Failed to subscribe to ${eventName}:`, e);
console.debug('channel:', e.channel);
console.debug('schema:', e.schema);
console.debug('newSchema:', e.newSchema);
}
}

return function cleanup() {
sub.unsubscribe();
init();

return () => {
if (sub) sub.unsubscribe();
};
}, []);
}, [eventName, schema]);

return value;
}

function ReactComponent() {
const isFavorite = useEventSubscription('namespace:eventName', { type: 'boolean' });
// Re-registering again with another schema is not allowed
useEventSubscription('namespace:eventName', { type: 'string' });

return e('article', { style: { padding: '1rem', backgroundColor: 'DeepSkyBlue' } },
return e(
'article',
{ style: { padding: '1rem', backgroundColor: 'DeepSkyBlue' } },
e('header', null, 'React Component'),
e('p', null, isFavorite ? 'This is a favorite' : 'This is not interesting')
)
e('p', null, isFavorite ? 'This is a favorite' : 'This is not interesting'),
);
}

const domContainer = document.querySelector('#react-component');
Expand Down
4 changes: 2 additions & 2 deletions docs/web-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class WebComponent extends HTMLElement {
eventBus.publish('namespace:eventName', { nested: true });
} catch (e) {
console.warn(e);
console.debug('eventType:', e.eventType);
console.debug('channel:', e.channel);
console.debug('schema:', e.schema);
console.debug('detail:', e.detail);
console.debug('payload:', e.payload);
}
}

Expand Down
Loading