# EventBus

Source: /tuil/docs/reference/packages/events/api/event-bus
Locale: en

class exported by @mwillbanks/tuil-events.



{/* Generated by tooling/docs/generate-reference.ts. */}

## class [#class]

Public class exported by `@mwillbanks/tuil-events`.

```ts
export class EventBus<TEvents extends EventMap = EventMap> {
  readonly #definitions: Record<string, EventDefinition<unknown>>;
  readonly #subscriptions = new Set<Subscription>();
  readonly #observers = new Set<(event: ObservedEvent) => void>();
  readonly #history: ObservedEvent[] = [];
  #disposed = false;

  constructor(definitions: Partial<EventDefinitions<TEvents>> = {}) {
    this.#definitions = {
      ...(definitions as Record<string, EventDefinition<unknown>>),
    };
  }

  register<TType extends keyof TEvents & string>(
    type: TType,
    definition: EventDefinition<TEvents[TType]>,
  ): () => void {
    this.#assertActive();
    if (type in this.#definitions) {
      throw new Error(`Event "${type}" is already declared`);
    }
    const registered = definition as EventDefinition<unknown>;
    this.#definitions[type] = registered;
    let active = true;
    return () => {
      if (!active) return;
      active = false;
      if (this.#definitions[type] === registered) {
        delete this.#definitions[type];
      }
    };
  }

  on<TType extends keyof TEvents & string>(
    type: TType,
    listener: EventListener<TEvents[TType]>,
    options: EventSubscriptionOptions = {},
  ): () => void {
    this.#assertActive();
    const subscription: Subscription = {
      type,
      listener: listener as EventListener<unknown>,
      phase: options.phase ?? "direct",
      target: options.target,
      priority: options.priority ?? 0,
    };
    this.#subscriptions.add(subscription);
    let active = true;
    const signal = options.signal;
    const dispose = () => {
      if (!active) return;
      active = false;
      this.#subscriptions.delete(subscription);
      signal?.removeEventListener("abort", dispose);
    };
    subscription.dispose = dispose;
    if (signal) {
      if (signal.aborted) {
        dispose();
      } else {
        signal.addEventListener("abort", dispose, { once: true });
      }
    }
    return dispose;
  }

  observe(observer: (event: ObservedEvent) => void): () => void {
    this.#assertActive();
    this.#observers.add(observer);
    return () => this.#observers.delete(observer);
  }

  history(): readonly ObservedEvent[] {
    return Object.freeze([...this.#history]);
  }

  async emit<TType extends keyof TEvents & string>(
    type: TType,
    payload: TEvents[TType],
    options: EventEmitOptions = {},
  ): Promise<TuilEvent<TType, TEvents[TType]>> {
    this.#assertActive();
    if (!(type in this.#definitions)) {
      throw new Error(`Event "${type}" has not been declared`);
    }
    const priority = options.priority ?? "normal";
    if (schedulingDelay[priority] > 0) {
      await new Promise((resolve) =>
        setTimeout(resolve, schedulingDelay[priority]),
      );
    }
    const emitted = new MutableTuilEvent(
      crypto.randomUUID(),
      type,
      payload,
      Date.now(),
      options.source,
      options.target,
      Object.freeze({ ...options.metadata }),
      priority,
    );
    if (options.path && options.path.length > 0) {
      await this.#dispatchRouted(emitted, options.path);
    } else {
      await this.#dispatch(emitted, "direct");
    }
    const definition = this.#definitions[type];
    const observedPayload = definition?.redact
      ? definition.redact(payload)
      : payload;
    const observed: ObservedEvent = Object.freeze({
      id: emitted.id,
      type,
      payload: observedPayload,
      timestamp: emitted.timestamp,
      source: emitted.source,
      target: emitted.target,
      metadata: emitted.metadata,
      priority,
      defaultPrevented: emitted.defaultPrevented,
    });
    this.#history.push(observed);
    if (this.#history.length > 200) this.#history.shift();
    for (const observer of this.#observers) {
      observer(observed);
    }
    return emitted;
  }

  dispose(): void {
    this.#disposed = true;
    for (const subscription of this.#subscriptions) {
      subscription.dispose?.();
    }
    this.#subscriptions.clear();
    this.#observers.clear();
    this.#history.length = 0;
  }

  async #dispatchRouted(
    event: MutableTuilEvent<string, unknown>,
    path: readonly string[],
  ): Promise<void> {
    const target = path.at(-1);
    for (const currentTarget of path.slice(0, -1)) {
      if (event.propagationStopped) {
        return;
      }
      await this.#dispatch(event, "capture", currentTarget);
    }
    if (!event.propagationStopped && target) {
      await this.#dispatch(event, "target", target);
    }
    for (const currentTarget of path.slice(0, -1).reverse()) {
      if (event.propagationStopped) {
        return;
      }
      await this.#dispatch(event, "bubble", currentTarget);
    }
  }

  async #dispatch(
    event: MutableTuilEvent<string, unknown>,
    phase: EventPhase,
    currentTarget?: string,
  ): Promise<void> {
    event.phase = phase;
    event.currentTarget = currentTarget;
    const subscriptions = [...this.#subscriptions]
      .filter(
        (subscription) =>
          subscription.type === event.type &&
          subscription.phase === phase &&
          (subscription.target === undefined ||
            subscription.target === currentTarget),
      )
      .sort((left, right) => right.priority - left.priority);
    for (const subscription of subscriptions) {
      await subscription.listener(event);
      if (event.propagationStopped) {
        return;
      }
    }
  }

  #assertActive(): void {
    if (this.#disposed) {
      throw new Error("Event bus is disposed");
    }
  }
}
```

## Members [#members]

| Member            | Type                                                                                                                                                    | Required | Description                                                                                                                                                                                  | Related types                                                                                                                               |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `#definitions`    | `Record<string, EventDefinition<unknown>>`                                                                                                              | Yes      | The `#definitions` member uses the `Record<string, EventDefinition<unknown>>` contract.                                                                                                      | [`EventDefinition`](/tuil/docs/reference/packages/events/api/event-definition)                                                                   |
| `#subscriptions`  | `Set<Subscription>`                                                                                                                                     | Yes      | The `#subscriptions` member uses the `Set<Subscription>` contract.                                                                                                                           | —                                                                                                                                           |
| `#observers`      | `Set<(event: ObservedEvent) => void>`                                                                                                                   | Yes      | The `#observers` member uses the `Set<(event: ObservedEvent) => void>` contract.                                                                                                             | [`ObservedEvent`](/tuil/docs/reference/packages/events/api/observed-event)                                                                       |
| `#history`        | `ObservedEvent[]`                                                                                                                                       | Yes      | The `#history` member uses the `ObservedEvent[]` contract.                                                                                                                                   | [`ObservedEvent`](/tuil/docs/reference/packages/events/api/observed-event)                                                                       |
| `#disposed`       | `boolean`                                                                                                                                               | Yes      | The `#disposed` member uses the `boolean` contract.                                                                                                                                          | —                                                                                                                                           |
| `__constructor`   | `any`                                                                                                                                                   | Yes      | The `__constructor` member uses the `any` contract.                                                                                                                                          | —                                                                                                                                           |
| `register`        | `<TType extends keyof TEvents & string>(type: TType, definition: EventDefinition<TEvents[TType]>) => () => void`                                        | Yes      | The `register` member uses the `<TType extends keyof TEvents & string>(type: TType, definition: EventDefinition<TEvents[TType]>) => () => void` contract.                                    | [`EventDefinition`](/tuil/docs/reference/packages/events/api/event-definition)                                                                   |
| `on`              | `<TType extends keyof TEvents & string>(type: TType, listener: EventListener<TEvents[TType]>, options?: EventSubscriptionOptions) => () => void`        | Yes      | The `on` member uses the `<TType extends keyof TEvents & string>(type: TType, listener: EventListener<TEvents[TType]>, options?: EventSubscriptionOptions) => () => void` contract.          | [`EventSubscriptionOptions`](/tuil/docs/reference/packages/events/api/event-subscription-options)                                                |
| `observe`         | `(observer: (event: ObservedEvent) => void) => () => void`                                                                                              | Yes      | The `observe` member uses the `(observer: (event: ObservedEvent) => void) => () => void` contract.                                                                                           | [`ObservedEvent`](/tuil/docs/reference/packages/events/api/observed-event)                                                                       |
| `history`         | `() => readonly ObservedEvent[]`                                                                                                                        | Yes      | The `history` member uses the `() => readonly ObservedEvent[]` contract.                                                                                                                     | [`ObservedEvent`](/tuil/docs/reference/packages/events/api/observed-event)                                                                       |
| `emit`            | `<TType extends keyof TEvents & string>(type: TType, payload: TEvents[TType], options?: EventEmitOptions) => Promise<TuilEvent<TType, TEvents[TType]>>` | Yes      | The `emit` member uses the `<TType extends keyof TEvents & string>(type: TType, payload: TEvents[TType], options?: EventEmitOptions) => Promise<TuilEvent<TType, TEvents[TType]>>` contract. | [`EventEmitOptions`](/tuil/docs/reference/packages/events/api/event-emit-options), [`TuilEvent`](/tuil/docs/reference/packages/events/api/tuil-event) |
| `dispose`         | `() => void`                                                                                                                                            | Yes      | The `dispose` member uses the `() => void` contract.                                                                                                                                         | —                                                                                                                                           |
| `#dispatchRouted` | `(event: MutableTuilEvent<string, unknown>, path: readonly string[]) => Promise<void>`                                                                  | Yes      | The `#dispatchRouted` member uses the `(event: MutableTuilEvent<string, unknown>, path: readonly string[]) => Promise<void>` contract.                                                       | —                                                                                                                                           |
| `#dispatch`       | `(event: MutableTuilEvent<string, unknown>, phase: EventPhase, currentTarget?: string) => Promise<void>`                                                | Yes      | The `#dispatch` member uses the `(event: MutableTuilEvent<string, unknown>, phase: EventPhase, currentTarget?: string) => Promise<void>` contract.                                           | [`EventPhase`](/tuil/docs/reference/packages/events/api/event-phase)                                                                             |
| `#assertActive`   | `() => void`                                                                                                                                            | Yes      | The `#assertActive` member uses the `() => void` contract.                                                                                                                                   | —                                                                                                                                           |

## Parameters [#parameters]

This declaration has no public members.

## Returns [#returns]

This declaration does not return a value.

## Throws [#throws]

No thrown errors are documented for this declaration.

## Related types [#related-types]

* [`EventDefinition`](/tuil/docs/reference/packages/events/api/event-definition)
* [`EventDefinitions`](/tuil/docs/reference/packages/events/api/event-definitions)
* [`EventEmitOptions`](/tuil/docs/reference/packages/events/api/event-emit-options)
* [`EventMap`](/tuil/docs/reference/packages/events/api/event-map)
* [`EventPhase`](/tuil/docs/reference/packages/events/api/event-phase)
* [`EventSubscriptionOptions`](/tuil/docs/reference/packages/events/api/event-subscription-options)
* [`ObservedEvent`](/tuil/docs/reference/packages/events/api/observed-event)
* [`TuilEvent`](/tuil/docs/reference/packages/events/api/tuil-event)

## Source [#source]

[View the secondary source reference](https://github.com/mwillbanks/tuil/blob/main/packages/events/src/index.ts)

## Package [#package]

[@mwillbanks/tuil-events](/tuil/docs/reference/packages/events)
