Skip to main content

emitter

Type-only event-system shapes — drive an emitter from an EventMap.

import type {
  EventMap, Listener, Unsubscribe,
  EventPayload, EventNames, EmitArgs,
  Emitter, PubSub, Handlers, EventAction,
} from '@gentleduck/ttest/emitter'

Type-level scaffolding for typed emitters. Define an EventMap; the interfaces here constrain method signatures automatically.

EventMap

type EventMap = Record<string, unknown>

Map of event name → payload type.

type AppEvents = {
  'user.login': { id: string }
  'user.logout': void
}

Listener / Unsubscribe

type Listener<P = unknown> = (payload: P) => void
type Unsubscribe = () => void

EventPayload / EventNames

type EventPayload<M extends EventMap, E extends keyof M> = M[E]
type EventNames<M extends EventMap> = keyof M & string
type Login = EventPayload<AppEvents, 'user.login'>  // { id: string }
type Names = EventNames<AppEvents>                  // 'user.login' | 'user.logout'

EmitArgs

type EmitArgs<M extends EventMap, E extends keyof M> = M[E] extends void ? [] : [payload: M[E]]

Variadic arg shape — [] for void payloads, [payload] otherwise.

Emitter

interface Emitter<M extends EventMap> {
  on<E extends keyof M>(event: E, listener: Listener<M[E]>): Unsubscribe
  off<E extends keyof M>(event: E, listener: Listener<M[E]>): void
  once<E extends keyof M>(event: E, listener: Listener<M[E]>): Unsubscribe
  emit<E extends keyof M>(event: E, ...args: EmitArgs<M, E>): void
}

Drop-in shape for typed emitter classes.

PubSub

interface PubSub<M extends EventMap> {
  publish<E extends keyof M>(event: E, ...args: EmitArgs<M, E>): void
  subscribe<E extends keyof M>(event: E, listener: Listener<M[E]>): Unsubscribe
  subscribeAll(listener: <E extends keyof M>(event: E, payload: M[E]) => void): Unsubscribe
}

Publish/subscribe variant — subscribeAll receives every event with its name.

Handlers

type Handlers<M extends EventMap, R = void> = { [E in keyof M]: (payload: M[E]) => R }

Handler record — one function per event.

const handlers: Handlers<AppEvents> = {
  'user.login':  ({ id }) => console.log(`login ${id}`),
  'user.logout': ()       => console.log('bye'),
}

EventAction

type EventAction<M extends EventMap, Tag extends string = 'type', Data extends string = 'payload'>

Union of { [Tag]: E; [Data]: M[E] } actions for redux-style reducers.

type Action = EventAction<AppEvents>
// | { type: 'user.login'; payload: { id: string } }
// | { type: 'user.logout'; payload: void }