# How the Event Emitter Pattern Works in LINEJS: TypedEventEmitter Explained

> Explore the TypedEventEmitter in LINEJS. Learn how this strictly-typed event emitter uses TypeScript generics for compile-time validation of event names and listener arguments.

- Repository: [Evex  Developers/linejs](https://github.com/evex-dev/linejs)
- Tags: internals
- Published: 2026-03-01

---

**LINEJS implements a strictly-typed event emitter using TypeScript generics that enforces compile-time validation of event names and listener arguments through the `TypedEventEmitter` class in [`packages/linejs/base/core/typed-event-emitter/index.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/typed-event-emitter/index.ts).**

The evex-dev/linejs repository provides a LINE messenger SDK built on a custom **typed event emitter** that eliminates runtime type errors. This pattern powers real-time message handling across the `Client`, `SquareChat`, and `Group` classes by binding event names to specific function signatures at compile time.

## Core Implementation of the Event Emitter Pattern

The `TypedEventEmitter` class serves as the foundation for all event-driven functionality in LINEJS. Located in [`packages/linejs/base/core/typed-event-emitter/index.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/typed-event-emitter/index.ts), this generic class accepts two type parameters: **`T`**, which maps event names to their listener function signatures, and **`E`**, which constrains the allowable event names to the keys of `T`.

### Generic Type Architecture

The class signature `export class TypedEventEmitter<T extends Record<string, (...args: any[]) => any>, E extends keyof T = keyof T>` ensures that every event name passed to the emitter must exist as a key in the provided event map. This architecture prevents typos in event names and guarantees that listeners receive arguments of the correct type without runtime checks.

### Listener Storage and Validation

Internally, the emitter stores registered callbacks in a `Map<E, T[E][]>` structure (see line 9 of the source). When adding listeners via the `on()` method, the implementation validates that each listener is a function (lines 17-19), throwing immediately if non-callable values are passed.

## Event Emitter Methods and Signatures

The LINEJS event emitter pattern exposes four primary methods that mirror the Node.js EventEmitter API while maintaining strict type safety.

### Registering Listeners with on()

The `on()` method accepts an event name and one or more listener functions. Its signature `on<E2 extends E>(event: E2, ...listeners: T[E2][]): this` uses a generic method parameter `E2` to ensure that listeners match the exact signature defined in the event map for that specific event.

### Removing Listeners with off()

Correspondingly, the `off()` method uses the signature `off<E2 extends E>(event: E2, ...listeners: T[E2][]): this` to remove specific previously-registered listeners. Both methods return `this` to enable method chaining.

### Emitting Events with emit()

The `emit()` method synchronously invokes all registered listeners with the signature `emit<E2 extends E>(event: E2, ...args: Parameters<T[E2]>): this`. TypeScript extracts the parameter types from the event map using `Parameters<T[E2]>`, ensuring that emitted arguments match the expected listener signature exactly.

### One-Shot Promises with waitFor()

For asynchronous workflows, the `waitFor()` method returns a `Promise` that resolves the first time a specified event fires. Its signature `waitFor<E2 extends E, P = Parameters<T[E2]>>(event: E2): Promise<P>` captures the event arguments as the promise resolution value. After resolution, the temporary listener automatically deregisters (lines 55-62), preventing memory leaks.

## Real-World Usage in LINEJS Components

The event emitter pattern permeates the LINEJS architecture, from the high-level `Client` API to specific feature implementations.

### Client Events and Message Handling

The `Client` class in [`packages/linejs/client/client.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/client/client.ts) extends `TypedEventEmitter<ClientEvents>`, exposing type-safe hooks for `"message"`, `"square:message"`, `"ready"`, and `"event"`. When internal polling logic receives a new LINE message, it constructs a high-level `TalkMessage` or `SquareMessage` object and calls `this.emit("message", message)`, triggering all registered listeners synchronously.

### Feature-Specific Emitters

Individual feature classes like `SquareChat`, `User`, and `Group` each extend `TypedEventEmitter` with their own event maps. For example, `SquareChat` might define events like `"member:joined"` and `"member:left"`, while `User` tracks `"update:profile"` changes. This granularity ensures that event payloads remain strongly typed across different domains of the LINE protocol.

### Base Client Infrastructure

Low-level connection events—such as `"login"` and `"update:profile"`—are defined in [`packages/linejs/base/core/utils/events.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/utils/events.ts) and exposed through the `BaseClient` class (referenced in [`packages/linejs/base/core/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/mod.ts)). This separation allows the base layer to handle protocol-level state changes while the high-level `Client` manages user-facing message events.

## Code Examples

### Basic TypedEventEmitter Usage

```typescript
import { TypedEventEmitter } from "https://deno.land/x/linejs/packages/linejs/base/core/typed-event-emitter/index.ts";

type MyEvents = {
  greet: (name: string) => void;
  data: (payload: { id: number; value: string }) => void;
};

const emitter = new TypedEventEmitter<MyEvents>();

// TypeScript validates these signatures at compile time
emitter.on("greet", (name) => console.log(`Hello, ${name}!`));
emitter.on("data", (p) => console.log(`Received ${p.id}: ${p.value}`));

// Emitting with correct argument types
emitter.emit("greet", "Alice");
emitter.emit("data", { id: 42, value: "answer" });

// Promise-based one-shot listener
const [name] = await emitter.waitFor("greet");

```

### Listening to LINEJS Client Events

```typescript
import { Client } from "https://deno.land/x/linejs/client.ts";

const client = new Client();

// Register typed handlers for specific LINE events
client.on("message", (msg) => {
  console.log("New talk message:", msg.text);
});

client.on("square:message", (msg) => {
  console.log("Square notification:", msg.content);
});

// Wait for ready state before starting polling
await client.waitFor("ready");
client.listen({ talk: true, square: true });

```

### Creating Custom Feature Classes

```typescript
import { TypedEventEmitter } from "../base/core/typed-event-emitter/index.ts";

type SquareChatEvents = {
  "member:joined": (mid: string, displayName: string) => void;
  "member:left": (mid: string) => void;
};

export class SquareChat extends TypedEventEmitter<SquareChatEvents> {
  handleMembershipChange(rawEvent: any) {
    // Internal logic validates and emits type-safe events
    if (rawEvent.type === "join") {
      this.emit("member:joined", rawEvent.mid, rawEvent.name);
    } else {
      this.emit("member:left", rawEvent.mid);
    }
  }
}

```

## Summary

- The **event emitter pattern in LINEJS** centers on the `TypedEventEmitter` class located in [`packages/linejs/base/core/typed-event-emitter/index.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/typed-event-emitter/index.ts).
- **Generic type parameters** `T` and `E` enforce compile-time validation of event names and listener signatures, preventing runtime type errors.
- The emitter provides **`on`**, **`off`**, **`emit`**, and **`waitFor`** methods, with `waitFor` offering automatic cleanup of one-shot promise listeners.
- Real-time messaging relies on this pattern through the `Client` class ([`packages/linejs/client/client.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/client/client.ts)) and specialized feature classes like `SquareChat` and `User`.
- Event definitions in [`packages/linejs/base/core/utils/events.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/utils/events.ts) separate protocol-level events from high-level message handling.

## Frequently Asked Questions

### How does LINEJS ensure type safety in its event emitter?

LINEJS enforces type safety through TypeScript generics in the `TypedEventEmitter` class. The `T` parameter maps event names to function signatures, while `E` constrains valid event keys. When calling `on()` or `emit()`, TypeScript checks that event names exist in the map and that arguments match the defined listener parameters, catching errors at compile time rather than runtime.

### What is the difference between on() and waitFor() in LINEJS?

The `on()` method registers a persistent listener that remains active until explicitly removed with `off()`, while `waitFor()` creates a temporary one-shot listener that returns a Promise. After the event fires once, `waitFor()` automatically deregisters its internal listener (as implemented in lines 55-62 of [`packages/linejs/base/core/typed-event-emitter/index.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/typed-event-emitter/index.ts)), making it ideal for awaiting specific initialization events like `"ready"`.

### Where are the event types defined for the LINEJS Client?

The `Client` class in [`packages/linejs/client/client.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/client/client.ts) inherits from `TypedEventEmitter<ClientEvents>`, where `ClientEvents` is a type mapping event names like `"message"` and `"square:message"` to their respective handler signatures. Base client events such as `"login"` and `"update:profile"` are defined separately in [`packages/linejs/base/core/utils/events.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/utils/events.ts) and exposed through the `BaseClient` class.

### Can I use TypedEventEmitter outside of the LINEJS SDK?

Yes. The `TypedEventEmitter` class is exported independently in [`packages/linejs/base/core/typed-event-emitter/index.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/typed-event-emitter/index.ts) and can be imported for any Deno or TypeScript project requiring strictly-typed event handling. The implementation has no external dependencies on LINE protocol specifics, making it suitable for general-purpose event-driven architectures.