Communicationv0.1.8
@tekir/emitter
Typed event emitter with wildcard listeners and async dispatch.
Installation
$
bun add @tekir/emitterFeatures
- 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.xdependency 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(orconsole.error) and continues to the remaining handlers, including wildcardonAnyhandlers, which previously swallowed errors silently. Behavior change: with noonErrorset,emitno 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 abreakout offor awaitcannot 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 (0disables 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/core0.1.16:await import('@tekir/core')ran beforecaptureCallerFile, 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 forcaptureCallerFile/loadDirEntrieskeep the capture synchronous on registerDir entry. Pair with@tekir/core0.1.16+.
v0.1.3May 4, 2026
- **Breaking**:
emitter.registerDir(...)now resolves a relative path against the caller's own directory, notprocess.cwd(). Matches the AST inliner's build-time behavior soawait emitter.registerDir('./listeners')fromapi/index.tslands atapi/listenersregardless of cwd, and thecd <root> && tekir serve --dev --entry api/index.tsmonorepo dev pattern works as-is. Passoptions.from = process.cwd()for the old cwd-relative behavior on a specific call site.
v0.1.2May 4, 2026
registerDirwarning now names the source file:[emitter.registerDir] core/listeners/typo.ts: skipped (unrecognized export shape: object). Replaces a genericSkipping ...log line that did not say which file dropped out.- Single
No modules loadedwarning with an inliner hint whenregisterDirmatches 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@OnEventpattern with__listenersmetadata) go throughemitter.register, functional registrars (export default (emitter) => emitter.on(...)) are invoked with the emitter, and classes with aregister(emitter)method are constructed and called.
v0.1.0April 1, 2026
- Initial release