install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/node-event-emitter" ~/.claude/skills/intense-visions-harness-engineering-node-event-emitter-dec95f && rm -rf "$T"
manifest:
agents/skills/codex/node-event-emitter/SKILL.mdsource content
Node.js EventEmitter
Use Node.js EventEmitter for typed pub-sub communication with memory leak prevention
When to Use
- Implementing decoupled communication between modules
- Building plugin systems or extensible architectures
- Replacing callback chains with event-driven patterns
- Creating typed event systems in TypeScript applications
Instructions
- Basic EventEmitter usage:
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' });
- Typed EventEmitter in TypeScript:
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); });
- One-time listeners with
:once
emitter.once('ready', () => { console.log('System initialized'); });
- Remove listeners to prevent memory leaks:
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');
- Set max listeners to catch leaks early:
emitter.setMaxListeners(20); // Default is 10 // Or globally EventEmitter.defaultMaxListeners = 20;
- Async event handling with
as a Promise:once
import { once } from 'node:events'; const [data] = await once(emitter, 'data'); console.log(data);
- Error handling — always listen for
events:'error'
emitter.on('error', (err) => { console.error('Emitter error:', err); }); // Without an error listener, emitting 'error' throws and crashes the process
- AbortController for cleanup:
const ac = new AbortController(); emitter.on('data', handler, { signal: ac.signal }); // Automatically removes the listener ac.abort();
Details
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:
- EventEmitter is built-in and zero-dependency — but is synchronous by default
- Typed events in TypeScript catch event name typos — but require maintaining a type map
- Decoupled communication is flexible — but makes control flow harder to trace
as Promise is convenient — but only captures the first emissiononce
Source
https://nodejs.org/api/events.html
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.