CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-sails

API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)

Pending
Overview
Eval results
Files

events.mddocs/

Events System

Sails.js inherits from Node.js EventEmitter and provides a comprehensive event system for managing application lifecycle, routing events, and custom functionality. The event system enables loose coupling between components and supports both built-in framework events and custom application events.

EventEmitter Inheritance

Sails applications inherit from Node.js EventEmitter, providing full event functionality:

// Sails extends EventEmitter
class Sails extends EventEmitter { ... }

// Event listener methods
sails.on(event: String, listener: Function): Sails
sails.once(event: String, listener: Function): Sails  
sails.off(event: String, listener?: Function): Sails
sails.emit(event: String, ...args): Boolean
sails.removeAllListeners(event?: String): Sails
sails.listenerCount(event: String): Number
sails.listeners(event: String): Function[]

Basic Event Usage:

const sails = require('sails');

// Listen for events
sails.on('lifted', () => {
  console.log('Sails app is ready!');
});

// Listen once
sails.once('ready', () => {
  console.log('App components loaded');
});

// Remove listener
const handler = () => console.log('Event fired');
sails.on('custom-event', handler);
sails.off('custom-event', handler);

// Emit custom events
sails.emit('user-registered', { userId: 123, email: 'user@example.com' });

Core Lifecycle Events

Application Lifecycle Events

// Application successfully lifted
sails.on('lifted', callback: Function): void

// Application shutdown initiated
sails.on('lower', callback: Function): void

// Application components loaded (internal)
sails.on('ready', callback: Function): void

Lifecycle Event Examples:

// Application startup complete
sails.on('lifted', () => {
  console.log(`Sails app lifted on port ${sails.config.port}`);
  console.log(`Environment: ${sails.config.environment}`);
  
  // Start background jobs
  startBackgroundTasks();
  
  // Register with service discovery
  registerWithLoadBalancer();
});

// Application shutdown initiated  
sails.on('lower', () => {
  console.log('Sails app is shutting down...');
  
  // Cleanup background jobs
  stopBackgroundTasks();
  
  // Close external connections
  closeExternalConnections();
});

// Internal ready state (before HTTP server starts)
sails.on('ready', () => {
  console.log('App components loaded, starting HTTP server...');
  
  // Perform pre-startup tasks
  validateConfiguration();
  initializeServices();
});

Startup Sequence Events

The application startup follows a specific event sequence:

// 1. Configuration loaded
sails.on('hook:userconfig:loaded', () => {
  console.log('User configuration loaded');
});

// 2. Hooks initialized  
sails.on('hook:*:loaded', (hookName) => {
  console.log(`Hook loaded: ${hookName}`);
});

// 3. Components ready
sails.on('ready', () => {
  console.log('All components ready');
});

// 4. Application lifted
sails.on('lifted', () => {
  console.log('Application fully operational');
});

Router Events

Route Binding Events

// Before static routes are bound
sails.on('router:before', callback: Function): void

// After static routes are bound  
sails.on('router:after', callback: Function): void

// Router reset event
sails.on('router:reset', callback: Function): void

Router Event Examples:

// Before route binding - modify routes
sails.on('router:before', () => {
  console.log('Binding routes...');
  
  // Add dynamic routes
  sails.get('/health', (req, res) => {
    return res.json({ status: 'ok', timestamp: Date.now() });
  });
  
  // Modify existing routes
  modifyApiRoutes();
});

// After route binding - log route information
sails.on('router:after', () => {
  console.log('Routes bound successfully');
  
  const routes = sails.router.getSortedRouteAddresses();
  console.log(`Total routes: ${routes.length}`);
  
  // Log all routes in development
  if (sails.config.environment === 'development') {
    routes.forEach(route => console.log(`  ${route}`));
  }
});

// Router reset - cleanup custom routes
sails.on('router:reset', () => {
  console.log('Router reset, clearing custom routes');
  clearCustomRoutes();
});

Request Handling Events

// Virtual request routing
sails.on('router:request', (req, res): void

// Server error handling  
sails.on('router:request:500', (req, res, err): void

// Not found handling
sails.on('router:request:404', (req, res): void

Request Event Examples:

// Log all virtual requests
sails.on('router:request', (req, res) => {
  console.log(`Virtual request: ${req.method} ${req.url}`);
});

// Custom 500 error handling
sails.on('router:request:500', (req, res, err) => {
  console.error('Server error:', err);
  
  // Log error details
  console.error(`URL: ${req.url}`);
  console.error(`Method: ${req.method}`);
  console.error(`User: ${req.session?.userId || 'anonymous'}`);
  
  // Send custom error response
  if (!res.headersSent) {
    return res.status(500).json({
      error: 'Internal server error',
      requestId: req.id,
      timestamp: Date.now()
    });
  }
});

// Custom 404 handling
sails.on('router:request:404', (req, res) => {
  console.log(`404 Not Found: ${req.method} ${req.url}`);
  
  // Track 404s for analytics
  trackNotFoundRequest(req);
  
  // Send custom 404 response
  if (!res.headersSent) {
    return res.status(404).json({
      error: 'Resource not found',
      path: req.url,
      suggestion: 'Check the API documentation'
    });
  }
});

Hook Events

Hooks can define and emit custom events during their lifecycle:

Hook Lifecycle Events

// Hook-specific events
sails.on('hook:${hookName}:loaded', callback: Function): void
sails.on('hook:${hookName}:ready', callback: Function): void

Hook Event Examples:

// Listen for specific hook events
sails.on('hook:orm:loaded', () => {
  console.log('ORM hook loaded, models available');
  
  // Perform database initialization
  initializeDatabase();
});

sails.on('hook:sockets:loaded', () => {
  console.log('Sockets hook loaded, WebSocket support available');
  
  // Configure real-time features
  setupRealtimeFeatures();
});

// Listen for all hook load events
sails.on('hook:*:loaded', (hookName) => {
  console.log(`Hook loaded: ${hookName}`);
  
  // Track loading progress
  trackHookLoadingProgress(hookName);
});

Custom Hook Events

Custom hooks can emit their own events:

// api/hooks/my-hook/index.js
module.exports = function(sails) {
  return {
    initialize: function(cb) {
      // Emit custom hook event
      sails.emit('hook:my-hook:initialized', {
        message: 'My hook is ready',
        features: ['feature1', 'feature2']
      });
      
      // Set up periodic events
      setInterval(() => {
        sails.emit('hook:my-hook:heartbeat', { timestamp: Date.now() });
      }, 30000);
      
      return cb();
    }
  };
};

// Listen for custom hook events
sails.on('hook:my-hook:initialized', (data) => {
  console.log('Custom hook initialized:', data.message);
  console.log('Available features:', data.features);
});

sails.on('hook:my-hook:heartbeat', (data) => {
  console.log('Hook heartbeat:', new Date(data.timestamp));
});

Custom Application Events

Applications can define and emit custom events for business logic:

User Events

// User registration event
sails.on('user:registered', (userData) => {
  console.log('New user registered:', userData.email);
  
  // Send welcome email
  EmailService.sendWelcomeEmail(userData.email, userData.name);
  
  // Create user profile
  UserProfileService.createProfile(userData.id);
  
  // Track registration
  AnalyticsService.track('user_registered', {
    userId: userData.id,
    source: userData.registrationSource
  });
});

// User login event
sails.on('user:login', (loginData) => {
  console.log('User logged in:', loginData.userId);
  
  // Update last login timestamp
  User.updateOne(loginData.userId).set({ lastLoginAt: Date.now() });
  
  // Log security events
  SecurityService.logLogin(loginData);
});

// Emit user events
const newUser = await User.create({
  email: 'user@example.com',
  name: 'John Doe'
}).fetch();

sails.emit('user:registered', {
  id: newUser.id,
  email: newUser.email,
  name: newUser.name,
  registrationSource: 'web'
});

Business Logic Events

// Order events
sails.on('order:created', async (orderData) => {
  console.log('New order created:', orderData.id);
  
  // Send confirmation email
  await EmailService.sendOrderConfirmation(orderData);
  
  // Update inventory
  await InventoryService.reserveItems(orderData.items);
  
  // Process payment
  await PaymentService.processPayment(orderData.paymentInfo);
});

sails.on('order:shipped', (orderData) => {
  console.log('Order shipped:', orderData.id);
  
  // Send shipping notification
  NotificationService.sendShippingNotification(orderData);
  
  // Update tracking
  TrackingService.createTrackingRecord(orderData);
});

// Payment events
sails.on('payment:successful', (paymentData) => {
  console.log('Payment successful:', paymentData.transactionId);
  
  // Fulfill order
  OrderService.fulfillOrder(paymentData.orderId);
  
  // Send receipt
  EmailService.sendReceipt(paymentData);
});

sails.on('payment:failed', (paymentData) => {
  console.error('Payment failed:', paymentData.error);
  
  // Cancel order
  OrderService.cancelOrder(paymentData.orderId);
  
  // Notify customer
  NotificationService.sendPaymentFailureNotification(paymentData);
});

Event-Driven Architecture Patterns

Event Aggregation

// Collect multiple related events
const EventAggregator = {
  events: [],
  
  collect(eventName, eventData) {
    this.events.push({ name: eventName, data: eventData, timestamp: Date.now() });
  },
  
  flush() {
    const events = this.events.slice();
    this.events = [];
    return events;
  }
};

// Listen to multiple events
['user:registered', 'user:login', 'user:logout'].forEach(eventName => {
  sails.on(eventName, (data) => {
    EventAggregator.collect(eventName, data);
  });
});

// Periodic processing
setInterval(() => {
  const events = EventAggregator.flush();
  if (events.length > 0) {
    AnalyticsService.batchProcess(events);
  }
}, 60000); // Every minute

Event Middleware

// Event middleware pattern
const EventMiddleware = {
  use(eventName, middleware) {
    const originalEmit = sails.emit;
    
    sails.emit = function(event, ...args) {
      if (event === eventName) {
        // Run middleware before emitting
        middleware(event, args, () => {
          originalEmit.call(this, event, ...args);
        });
      } else {
        originalEmit.call(this, event, ...args);
      }
    };
  }
};

// Use middleware
EventMiddleware.use('user:registered', (event, args, next) => {
  console.log('Middleware: Processing user registration');
  
  // Validate event data
  if (!args[0].email) {
    console.error('Invalid user registration data');
    return; // Don't proceed
  }
  
  // Add metadata
  args[0].processedAt = Date.now();
  
  next();
});

Event Sourcing Pattern

// Simple event sourcing implementation
const EventStore = {
  events: [],
  
  append(event) {
    this.events.push({
      ...event,
      id: require('uuid').v4(),
      timestamp: Date.now()
    });
  },
  
  getEvents(aggregateId) {
    return this.events.filter(e => e.aggregateId === aggregateId);
  },
  
  replay(aggregateId) {
    const events = this.getEvents(aggregateId);
    return events.reduce((state, event) => {
      return applyEvent(state, event);
    }, {});
  }
};

// Store all user events
sails.on('user:*', (eventData) => {
  EventStore.append({
    type: 'user_event',
    aggregateId: eventData.userId,
    data: eventData
  });
});

Error Handling with Events

Event Error Handling

// Handle event errors
sails.on('error', (err) => {
  console.error('Unhandled event error:', err);
  
  // Log to external service
  ErrorReportingService.reportError(err);
});

// Safe event emission
function safeEmit(eventName, eventData) {
  try {
    sails.emit(eventName, eventData);
  } catch (err) {
    console.error(`Error emitting event ${eventName}:`, err);
    sails.emit('event:error', { eventName, eventData, error: err });
  }
}

// Error recovery
sails.on('event:error', ({ eventName, eventData, error }) => {
  console.log(`Attempting to recover from event error: ${eventName}`);
  
  // Retry logic
  setTimeout(() => {
    safeEmit(eventName, eventData);
  }, 5000);
});

Event Performance and Monitoring

Event Metrics

// Event performance monitoring
const EventMetrics = {
  counts: new Map(),
  timings: new Map(),
  
  track(eventName) {
    this.counts.set(eventName, (this.counts.get(eventName) || 0) + 1);
  },
  
  time(eventName, duration) {
    if (!this.timings.has(eventName)) {
      this.timings.set(eventName, []);
    }
    this.timings.get(eventName).push(duration);
  },
  
  getStats() {
    const stats = {};
    
    for (const [event, count] of this.counts) {
      stats[event] = { count };
      
      if (this.timings.has(event)) {
        const times = this.timings.get(event);
        stats[event].avgTime = times.reduce((a, b) => a + b, 0) / times.length;
      }
    }
    
    return stats;
  }
};

// Monitor all events
const originalEmit = sails.emit;
sails.emit = function(event, ...args) {
  const start = Date.now();
  EventMetrics.track(event);
  
  const result = originalEmit.call(this, event, ...args);
  
  EventMetrics.time(event, Date.now() - start);
  return result;
};

// Log metrics periodically
setInterval(() => {
  console.log('Event Statistics:', EventMetrics.getStats());
}, 300000); // Every 5 minutes

The Sails.js event system provides a robust foundation for building event-driven applications with comprehensive lifecycle management, routing events, and custom business logic events, supporting modern architectural patterns and real-time functionality.

Install with Tessl CLI

npx tessl i tessl/npm-sails

docs

actions.md

application-lifecycle.md

cli.md

configuration.md

events.md

hooks.md

index.md

routing.md

tile.json