tekir
All Packages
Communicationv0.1.8

@tekir/emitter

Typed event emitter with wildcard listeners and async dispatch.

Installation

$bun add @tekir/emitter

Features

  • Typed events with generics
  • on(), once(), off() listener management
  • onAny() wildcard listener for all events
  • Async emit() and sync emitSync()
  • wait() to await a specific event with timeout
  • FakeEmitter for testing

Quick Example

TypeScript
import { Emitter } from '@tekir/emitter'

type Events = { 'user:created': { id: number } }
const emitter = new Emitter<Events>()

emitter.on('user:created', (data) => {
  console.log('New user:', data.id)
})

await emitter.emit('user:created', { id: 1 })

Changelog

v0.1.8LatestSeptember 16, 2026
  • Published output now uses the shared Node-targeted ESM bundle pipeline with external dependencies and generated TypeScript declarations, while Bun consumers keep the native source export.
v0.1.7July 23, 2026
  • Package metadata now follows the shared compatible 0.1.x dependency range used by this coordinated Tekir release.
v0.1.6July 16, 2026
  • Async listener dispatch now awaits Promise-compatible thenables and cleans abort listeners without leaking wait subscriptions.
v0.1.5June 13, 2026
  • A handler that throws no longer aborts dispatch or rejects the emit. A shared error path routes the error to onError (or console.error) and continues to the remaining handlers, including wildcard onAny handlers, which previously swallowed errors silently. Behavior change: with no onError set, emit no longer rejects on a handler error (handlers are isolated by default).
  • wait() and the async-iterator helpers no longer leak listeners. Timeout, abort, and normal completion all remove the handler and clear timers, so repeated timeouts or a break out of for await cannot accumulate listeners.
  • The async-iterator buffer is now bounded by maxBufferSize (default 1024, drop-oldest), so a fast producer with a slow or absent consumer cannot grow memory without bound.
  • Added a per-event listener threshold (default 100) with a one-time possible-memory-leak warning and a setMaxListeners(n) API (0 disables it).
  • Found and fixed with Fable.
v0.1.4May 4, 2026
  • Fixes 0.1.3's caller capture in emitter.registerDir. Same root cause as @tekir/core 0.1.16: await import('@tekir/core') ran before captureCallerFile, so the user's frame was already gone by the time the stack was inspected and the warning printed (resolved against native). Static top-level imports for captureCallerFile/loadDirEntries keep the capture synchronous on registerDir entry. Pair with @tekir/core 0.1.16+.
v0.1.3May 4, 2026
  • **Breaking**: emitter.registerDir(...) now resolves a relative path against the caller's own directory, not process.cwd(). Matches the AST inliner's build-time behavior so await emitter.registerDir('./listeners') from api/index.ts lands at api/listeners regardless of cwd, and the cd <root> && tekir serve --dev --entry api/index.ts monorepo dev pattern works as-is. Pass options.from = process.cwd() for the old cwd-relative behavior on a specific call site.
v0.1.2May 4, 2026
  • registerDir warning now names the source file: [emitter.registerDir] core/listeners/typo.ts: skipped (unrecognized export shape: object). Replaces a generic Skipping ... log line that did not say which file dropped out.
  • Single No modules loaded warning with an inliner hint when registerDir matches zero modules. Replaces the previous silent failure where a misconfigured production bundle would just have no listeners attached with no log line explaining why.
v0.1.1May 3, 2026
  • emitter.registerDir(path) loads every file in a folder and binds whatever each module exports as a listener: decorator classes (the @OnEvent pattern with __listeners metadata) go through emitter.register, functional registrars (export default (emitter) => emitter.on(...)) are invoked with the emitter, and classes with a register(emitter) method are constructed and called.
v0.1.0April 1, 2026
  • Initial release

Other Communication packages