Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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('eventType:', e.eventType);
Comment thread
Swiftwork marked this conversation as resolved.
Outdated
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
75 changes: 46 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"name": "@trutoo/event-bus",
"version": "1.0.0",
"version": "0.0.0",
Comment thread
Swiftwork marked this conversation as resolved.
"description": "Typesafe cross-platform pubsub event bus ensuring reliable communication between fragments and micro frontends.",
"type": "module",
"packageManager": "yarn@4.6.0",
"keywords": [
"event bus",
"communication",
Expand All @@ -28,10 +29,26 @@
],
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"browser": "dist/index.umd.min.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js",
"types": "./dist/index.d.ts"
}
},
"sideEffects": false,
"engines": {
"node": ">=16.0.0"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"files": [
"dist/"
"dist/",
"README.md",
"LICENSE"
],
"husky": {
"hooks": {
Expand All @@ -47,36 +64,36 @@
"release": "semantic-release"
},
"dependencies": {
"jsonschema": "1.4.1"
"fast-equals": "^5.2.2",
"jsonschema": "^1.5.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "25.0.8",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-commonjs": "28.0.6",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "16.0.1",
"@rollup/plugin-terser": "0.4.4",
"@rollup/plugin-typescript": "^11.1.6",
"@rollup/plugin-typescript": "12.1.3",
"@semantic-release/changelog": "6.0.3",
"@semantic-release/github": "10.0.5",
"@typescript-eslint/eslint-plugin": "7.11.0",
"@typescript-eslint/parser": "7.11.0",
"@semantic-release/github": "11.0.3",
"@types/jest": "30.0.0",
"@typescript-eslint/eslint-plugin": "8.34.1",
"@typescript-eslint/parser": "8.34.1",
"conventional-changelog-angular": "8.0.0",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-simple-import-sort": "12.1.0",
"eslint-plugin-unused-imports": "3.2.0",
"husky": "9.0.11",
"jest": "29.7.0",
"prettier": "3.3.0",
"rimraf": "5.0.7",
"rollup": "4.18.0",
"eslint": "9.29.0",
"eslint-config-prettier": "10.1.5",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-prettier": "5.5.0",
"eslint-plugin-simple-import-sort": "12.1.1",
"eslint-plugin-unused-imports": "4.1.4",
"husky": "9.1.7",
"jest": "30.0.2",
"prettier": "3.5.3",
"rimraf": "6.0.1",
"rollup": "4.44.0",
"rollup-plugin-node-polyfills": "0.2.1",
"semantic-release": "24.0.0",
"ts-jest": "29.1.4",
"typescript": "5.4.5"
},
"optionalDependencies": {
"@types/jest": "29.5.12"
},
"packageManager": "yarn@4.2.2"
"semantic-release": "24.2.5",
"ts-jest": "29.4.0",
"tslib": "^2.8.1",
"typescript": "5.8.3"
}
}
Loading