Skip to content

EventEmitter API Reference

The EventEmitter class provides a publish-subscribe event system for lifecycle hooks and custom events.


Table of Contents


Overview

The EventEmitter enables:

  • Event Subscription - Listen to events with on()
  • Event Emission - Trigger events with emit()
  • One-Time Listeners - Subscribe with once()
  • Unsubscription - Remove listeners with off()
  • Lifecycle Hooks - Built-in DataSource events
  • Custom Events - Create your own events
typescript
import { EventEmitter } from 'bindra';

Constructor

typescript
constructor()

Creates a new EventEmitter instance.

Example:

typescript
const emitter = new EventEmitter();

Note: You typically don't create EventEmitter instances directly. DataSource extends EventEmitter, so all DataSource instances have event capabilities.


Methods

on()

Subscribe to an event.

typescript
on(event: string, callback: (...args: any[]) => void): void

Parameters

  • event (string) - Event name to listen for
  • callback (Function) - Function called when event is emitted

Example

typescript
emitter.on('update', (data) => {
  console.log('Update event:', data);
});

emitter.emit('update', { id: 1, name: 'Alice' });
// Logs: "Update event: { id: 1, name: 'Alice' }"

off()

Unsubscribe from an event.

typescript
off(event: string, callback: (...args: any[]) => void): void

Parameters

  • event (string) - Event name
  • callback (Function) - The exact callback to remove

Example

typescript
const handleUpdate = (data) => {
  console.log('Updated:', data);
};

// Subscribe
emitter.on('update', handleUpdate);

// Later... unsubscribe
emitter.off('update', handleUpdate);

emitter.emit('update', { id: 1 });
// Nothing logged - unsubscribed

Important: You must pass the exact same function reference to off().


once()

Subscribe to an event once (auto-unsubscribes after first call).

typescript
once(event: string, callback: (...args: any[]) => void): void

Parameters

  • event (string) - Event name
  • callback (Function) - Function called once

Example

typescript
emitter.once('ready', () => {
  console.log('Ready!');
});

emitter.emit('ready'); // Logs: "Ready!"
emitter.emit('ready'); // Nothing logged (already fired)

emit()

Emit an event to all subscribers.

typescript
emit(event: string, ...args: any[]): void

Parameters

  • event (string) - Event name to emit
  • ...args - Arguments passed to callbacks

Example

typescript
// No arguments
emitter.emit('start');

// One argument
emitter.emit('update', { id: 1 });

// Multiple arguments
emitter.emit('change', 'name', 'Alice', 'Bob');

Event Patterns

DataSource Lifecycle Events

DataSource emits these events during its lifecycle:

CRUD Events

beforeFetch - Before fetching data

typescript
ds.on('beforeFetch', (query) => {
  console.log('Fetching with query:', query);
});

afterFetch - After fetching data

typescript
ds.on('afterFetch', (data) => {
  console.log('Fetched data:', data);
});

beforeCreate - Before creating a record

typescript
ds.on('beforeCreate', (record) => {
  console.log('Creating:', record);
});

afterCreate - After creating a record

typescript
ds.on('afterCreate', (record) => {
  console.log('Created:', record);
});

beforeUpdate - Before updating a record

typescript
ds.on('beforeUpdate', ({ id, changes }) => {
  console.log('Updating record', id, 'with', changes);
});

afterUpdate - After updating a record

typescript
ds.on('afterUpdate', (record) => {
  console.log('Updated:', record);
});

beforeDelete - Before deleting a record

typescript
ds.on('beforeDelete', (id) => {
  console.log('Deleting:', id);
});

afterDelete - After deleting a record

typescript
ds.on('afterDelete', (id) => {
  console.log('Deleted:', id);
});

navigate - When current record changes

typescript
ds.on('navigate', (index, record) => {
  console.log('Navigated to index', index, ':', record);
});

Error Events

error - When an error occurs

typescript
ds.on('error', (error) => {
  console.error('Error:', error);
});

Real-time Events

realtime:connected - WebSocket connected

typescript
ds.on('realtime:connected', () => {
  console.log('WebSocket connected');
});

realtime:disconnected - WebSocket disconnected

typescript
ds.on('realtime:disconnected', () => {
  console.log('WebSocket disconnected');
});

realtime:message - WebSocket message received

typescript
ds.on('realtime:message', (message) => {
  console.log('Received:', message);
});

Usage Examples

Basic Event Handling

typescript
import { EventEmitter } from 'bindra';

const emitter = new EventEmitter();

// Subscribe to event
emitter.on('message', (text) => {
  console.log('Message:', text);
});

// Emit event
emitter.emit('message', 'Hello, World!');
// Logs: "Message: Hello, World!"

DataSource Events

typescript
import { DataSource } from 'bindra';

interface User {
  id: number;
  name: string;
}

const users = new DataSource<User>({
  url: '/api/users'
});

// Listen to fetch events
users.on('beforeFetch', () => {
  console.log('Loading users...');
  showLoadingSpinner();
});

users.on('afterFetch', (data) => {
  console.log('Loaded', data.length, 'users');
  hideLoadingSpinner();
});

// Listen to create events
users.on('afterCreate', (user) => {
  console.log('New user created:', user.name);
  showNotification(`Welcome, ${user.name}!`);
});

// Fetch data
await users.fetch();

Error Handling

typescript
const users = new DataSource<User>({ url: '/api/users' });

// Global error handler
users.on('error', (error) => {
  console.error('DataSource error:', error);
  
  if (error instanceof NetworkError) {
    showErrorMessage('Network error. Please check your connection.');
  } else if (error instanceof ValidationError) {
    showErrorMessage('Validation failed: ' + error.message);
  } else {
    showErrorMessage('An unexpected error occurred.');
  }
});

// Try to create invalid user
try {
  await users.create({ name: '' }); // Validation error
} catch (error) {
  // Error also emitted via 'error' event
}

Multiple Listeners

typescript
const users = new DataSource<User>({ url: '/api/users' });

// Multiple listeners for same event
users.on('afterCreate', (user) => {
  console.log('User created');
});

users.on('afterCreate', (user) => {
  updateUserList(user);
});

users.on('afterCreate', (user) => {
  logToAnalytics('user_created', { id: user.id });
});

users.on('afterCreate', (user) => {
  sendWelcomeEmail(user.email);
});

// All four listeners fire when user is created
await users.create({ name: 'Alice', email: 'alice@example.com' });

One-Time Events

typescript
const users = new DataSource<User>({ url: '/api/users' });

// Listen for first fetch only
users.once('afterFetch', (data) => {
  console.log('Initial data loaded:', data.length, 'users');
  initializeUI(data);
});

await users.fetch(); // Logs initial data
await users.fetch(); // Nothing logged (already fired)

Cleanup / Unsubscribe

typescript
const users = new DataSource<User>({ url: '/api/users' });

// Create handler
const handleUpdate = (user: User) => {
  console.log('User updated:', user);
  updateUI(user);
};

// Subscribe
users.on('afterUpdate', handleUpdate);

// Later... cleanup
users.off('afterUpdate', handleUpdate);

// Or store reference for cleanup
function setupListeners() {
  const handlers = {
    fetch: (data: User[]) => console.log('Fetched:', data.length),
    create: (user: User) => console.log('Created:', user.name),
    update: (user: User) => console.log('Updated:', user.name)
  };
  
  users.on('afterFetch', handlers.fetch);
  users.on('afterCreate', handlers.create);
  users.on('afterUpdate', handlers.update);
  
  return () => {
    users.off('afterFetch', handlers.fetch);
    users.off('afterCreate', handlers.create);
    users.off('afterUpdate', handlers.update);
  };
}

const cleanup = setupListeners();
// Later...
cleanup();

React Integration

typescript
import { useEffect } from 'react';
import { DataSource } from 'bindra';

function UserList() {
  const [users, setUsers] = useState<User[]>([]);
  
  useEffect(() => {
    const ds = new DataSource<User>({ url: '/api/users' });
    
    // Subscribe to data changes
    const handleFetch = (data: User[]) => setUsers(data);
    ds.on('afterFetch', handleFetch);
    
    // Fetch initial data
    ds.fetch();
    
    // Cleanup
    return () => {
      ds.off('afterFetch', handleFetch);
    };
  }, []);
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Middleware with Events

typescript
const users = new DataSource<User>({ url: '/api/users' });

// Log all operations
users.on('beforeFetch', () => console.log('[FETCH] Starting...'));
users.on('afterFetch', (data) => console.log('[FETCH] Complete:', data.length));

users.on('beforeCreate', (record) => console.log('[CREATE] Starting...', record));
users.on('afterCreate', (record) => console.log('[CREATE] Complete:', record));

users.on('beforeUpdate', ({ id, changes }) => 
  console.log('[UPDATE] Starting...', id, changes)
);
users.on('afterUpdate', (record) => console.log('[UPDATE] Complete:', record));

users.on('beforeDelete', (id) => console.log('[DELETE] Starting...', id));
users.on('afterDelete', (id) => console.log('[DELETE] Complete:', id));

// All operations are now logged
await users.fetch();
await users.create({ name: 'Alice' });
await users.update(1, { name: 'Bob' });
await users.delete(1);

Real-time Events

typescript
const chat = new DataSource<Message>({
  url: '/api/messages',
  realtime: {
    enabled: true,
    url: 'wss://api.example.com/ws'
  }
});

// Connection status
chat.on('realtime:connected', () => {
  console.log('Connected to WebSocket');
  showOnlineIndicator();
});

chat.on('realtime:disconnected', () => {
  console.log('Disconnected from WebSocket');
  showOfflineIndicator();
});

// Incoming messages
chat.on('realtime:message', (message) => {
  console.log('New message:', message);
  
  if (message.type === 'message.created') {
    addMessageToUI(message.data);
    playNotificationSound();
  }
});

// Error handling
chat.on('error', (error) => {
  console.error('Chat error:', error);
  showErrorNotification(error.message);
});

Custom Events

typescript
class MyDataSource<T> extends DataSource<T> {
  async customOperation(data: T) {
    // Emit custom event before
    this.emit('beforeCustomOp', data);
    
    try {
      // Do something
      const result = await this.processData(data);
      
      // Emit custom event after
      this.emit('afterCustomOp', result);
      
      return result;
    } catch (error) {
      this.emit('error', error);
      throw error;
    }
  }
}

const ds = new MyDataSource({ url: '/api/data' });

// Listen to custom events
ds.on('beforeCustomOp', (data) => {
  console.log('Custom operation starting...');
});

ds.on('afterCustomOp', (result) => {
  console.log('Custom operation complete:', result);
});

Best Practices

1. Always Clean Up Listeners

typescript
// ✅ Good - Clean up in useEffect
useEffect(() => {
  const handler = (data) => setData(data);
  ds.on('afterFetch', handler);
  
  return () => ds.off('afterFetch', handler);
}, []);

// ❌ Bad - Memory leak
useEffect(() => {
  ds.on('afterFetch', (data) => setData(data));
  // Never cleaned up
}, []);

2. Use Named Functions for Handlers

typescript
// ✅ Good - Can be removed
const handleUpdate = (user) => console.log(user);
ds.on('afterUpdate', handleUpdate);
ds.off('afterUpdate', handleUpdate);

// ❌ Bad - Can't be removed
ds.on('afterUpdate', (user) => console.log(user));
// No reference to remove

3. Use once() for Initialization

typescript
// ✅ Good - Fires once
ds.once('afterFetch', (data) => {
  initializeApp(data);
});

// ❌ Bad - Fires every time
ds.on('afterFetch', (data) => {
  initializeApp(data); // Called repeatedly
});

4. Handle Errors Globally

typescript
// ✅ Good - Global error handler
ds.on('error', (error) => {
  logError(error);
  showErrorToUser(error.message);
});

// ❌ Bad - No error handling
// Errors go unhandled

5. Keep Event Names Consistent

typescript
// ✅ Good - Consistent naming
ds.on('beforeFetch', handler);
ds.on('afterFetch', handler);
ds.on('beforeCreate', handler);
ds.on('afterCreate', handler);

// ❌ Bad - Inconsistent
ds.on('fetch-start', handler);
ds.on('fetchComplete', handler);
ds.on('creating', handler);
ds.on('created', handler);


← Back to API Reference

Released under the MIT License.