From harness-claude
Guides use of Node.js EventEmitter for typed pub-sub communication, memory leak prevention, and event-driven patterns in decoupled modules and TypeScript apps.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Use Node.js EventEmitter for typed pub-sub communication with memory leak prevention
Implements in-process TypeScript pub/sub with typed topics, wildcards, pattern matching, and fan-out delivery for decoupling publishers/subscribers. Use for domain events or notifications without guaranteed delivery.
Builds event-driven APIs with webhooks, Server-Sent Events, message brokers, event schemas, subscribers, retries, and dead-letter queues.
Provides Node.js best practices with TypeScript via type stripping (Node 22+), covering async patterns, error handling, streams, modules, testing, performance, caching, logging. For native TS setups, graceful shutdown, flaky tests, and env config.
Share bugs, ideas, or general feedback.
Use Node.js EventEmitter for typed pub-sub communication with memory leak prevention
import { EventEmitter } from 'node:events';
const emitter = new EventEmitter();
emitter.on('user:created', (user: { id: string; name: string }) => {
console.log(`User created: ${user.name}`);
});
emitter.emit('user:created', { id: '1', name: 'Alice' });
import { EventEmitter } from 'node:events';
interface AppEvents {
'user:created': [user: { id: string; name: string }];
'user:deleted': [userId: string];
error: [error: Error];
}
class AppEmitter extends EventEmitter<AppEvents> {}
const emitter = new AppEmitter();
emitter.on('user:created', (user) => {
// user is typed as { id: string; name: string }
console.log(user.name);
});
once:emitter.once('ready', () => {
console.log('System initialized');
});
const handler = (data: string) => console.log(data);
emitter.on('data', handler);
// Later: remove the specific listener
emitter.off('data', handler);
// Or remove all listeners for an event
emitter.removeAllListeners('data');
emitter.setMaxListeners(20); // Default is 10
// Or globally
EventEmitter.defaultMaxListeners = 20;
once as a Promise:import { once } from 'node:events';
const [data] = await once(emitter, 'data');
console.log(data);
'error' events:emitter.on('error', (err) => {
console.error('Emitter error:', err);
});
// Without an error listener, emitting 'error' throws and crashes the process
const ac = new AbortController();
emitter.on('data', handler, { signal: ac.signal });
// Automatically removes the listener
ac.abort();
EventEmitter is Node.js's built-in pub-sub mechanism. It is synchronous by default — emit() calls listeners in registration order and blocks until all complete.
Synchronous emission: emitter.emit('event', data) runs all listeners synchronously in the current tick. Long-running listeners block the event loop. Use setImmediate or queueMicrotask inside listeners for async work.
Memory leak warning: If more than maxListeners are registered for a single event, Node.js prints a warning. This usually indicates listeners being added in a loop without removal.
Event ordering: Listeners fire in registration order. prependListener adds to the front of the queue.
Trade-offs:
once as Promise is convenient — but only captures the first emissionhttps://nodejs.org/api/events.html