Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@collidor/event

collidor227MIT4.1.2TypeScript support: included

A small library to create, register and listen to events.

event, event bus, event handler, event emitter, event listener, event publisher

readme

@collidor/event

Codecov npm version License: MIT

A lightweight event system with cross-context communication. Perfect for modern web apps, workers, and distributed systems.


Installation

npm install @collidor/event

Core Features

  • Class-based event system with strict typing
  • Cross-context communication (Window ↔ Worker ↔ Tab)
  • Built-in support for BroadcastChannel/SharedWorker
  • Context propagation & AbortController integration

Basic Usage

`ts [Event Definition] import { Event } from "@collidor/event";

class UserUpdate extends Event<{ id: string }> {} class SystemAlert extends Event<string> {}


```ts [Event Bus]
import { EventBus } from "@collidor/event";

const bus = new EventBus();

// Subscribe
bus.on(UserUpdate, (data) => {
  console.log(`User ${data.id} updated`);
});

// Publish
bus.emit(new UserUpdate({ id: "123" }));

Cross-Context Patterns

The PortChannel is the main exported channel, it accepts MessagePortLike ports, which have a postMessage method, and can set and use a onmessage property callback. This means that the browsers APIs like BroadcastChannel, Worker and SharedWorker will work by default.

My plan is to add adapters later so other things can behave like ports, like Websockets, WebRTC, Http+SEE and Datagrams.

The PortChannel will not emit events to ports that didn't send a message first telling that it has a subscriber for such event.

1. BroadcastChannel (Tab-to-Tab)

`ts [Tab A] import { PortChannel, EventBus } from "@collidor/event";

const channel = new PortChannel(); const bus = new EventBus({ channel }); channel.addPort(new BroadcastChannel("app-channel"));

class TabMessage extends Event<string> {} bus.emit(new TabMessage("Hello from Tab A!"));


```ts
import { PortChannel, EventBus } from "@collidor/event";

const channel = new PortChannel();
const bus = new EventBus({ channel });
channel.addPort(new BroadcastChannel("app-channel"));

bus.on(TabMessage, (msg) => {
  console.log("Received:", msg); // "Hello from Tab A!"
});

2. SharedWorker (Multi-Context)

import { PortChannel, EventBus } from "@collidor/event";

const channel = new PortChannel();
const bus = new EventBus({ channel });
const worker = new SharedWorker("worker.js");

channel.addPort(worker.port);

class TaskEvent extends Event<{ id: string }> {}
bus.emit(new TaskEvent({ id: "shared-task" }));
import { PortChannel, EventBus } from "@collidor/event";

const channel = new PortChannel();
const bus = new EventBus({ channel });

// Handle new connections
self.onconnect = (event) => {
  const port = event.ports[0];
  channel.addPort(port);
};

self.start();

bus.on(TaskEvent, ({ id }) => {
  console.log("Processing shared task:", id);
});

Advanced Patterns

Context Propagation

const bus = new EventBus({
  context: { requestId: "123" }
});

bus.on(DataEvent, (data, ctx) => {
  console.log(ctx.requestId); // "123"
});

// Override per emission
bus.emit(new DataEvent(), { requestId: "temp" });

API Overview

EventBus

  • on(event, handler, signal?)
  • off(event, handler)
  • emit(event, context?)
  • emitByName(name, data, context?)

PortChannel

  • addPort(port)
  • removePort(port)
  • publish(event)
  • subscribe(name, callback)
  • unsubscribe(name, callback)

PortChannel sequence diagram

sequenceDiagram
    participant S as Server PortChannel
    participant MP as MessagePort
    participant C as Client PortChannel

    %% Server adds a MessagePort (acts as connecting port)
    S->>MP: addPort(port)
    Note over S,MP: Server attaches MessagePort

    %% MessagePort forwards startEvent to the Client
    MP->>C: onmessage({type: "startEvent"})
    Note over C: Client receives startEvent and sets up message handlers

    %% Client subscribes to "TestEvent"
    C->>MP: postMessage({type:"subscribeEvent", name:"TestEvent"})
    Note over C: Client tells that it has a subscription for "TestEvent"

    %% MessagePort delivers subscribeEvent to Server
    MP->>S: onmessage({type:"subscribeEvent", name:"TestEvent"})
    Note over S: Server calls addPortSubscription("TestEvent")

    %% If the Server had buffered events for "TestEvent", flush them:
    alt Buffered events exist for "TestEvent"
      S->>MP: postMessage(buffered dataEvent for "TestEvent")
      MP->>C: onmessage(buffered dataEvent for "TestEvent")
    else
      Note over S: No buffered events to flush
    end

    %% Now, the Server publishes a new event "TestEvent"
    S->>S: publish("TestEvent", data)
    alt Subscriber exists
      S->>MP: postMessage({type:"dataEvent", name:"TestEvent", data})
      MP->>C: onmessage({type:"dataEvent", name:"TestEvent", data})
      Note over C: Client processes the dataEvent
    else
      S->>S: Buffer event "TestEvent" with timeout
      Note over S: Event buffered until a subscriber appears
    end

Development

`bash [Commands]

Install dependencies

npm install

Run tests (requires Deno)

npm test

Build project

npm run build `


MIT License | © 2024 Alykam Burdzaki Report Issues