🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@ixo/oracles-events

Package Overview
Dependencies
Maintainers
2
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ixo/oracles-events

Events for QiForge

latest
npmnpm
Version
1.0.5
Version published
Maintainers
2
Created
Source

@ixo/oracles-events

Real-time event system for QiForge. Handles streaming communication between your oracle (server) and client applications via Server-Sent Events (SSE) and WebSocket (WS) connections.

Table of Contents

  • Overview
  • Communication Channels
  • Event Types
  • Installation
  • Creating a New Event
  • Advanced Usage
  • Testing
  • Contributing

Overview

The @ixo/oracles-events package enables real-time communication between your oracle and client applications. While your LLM processes conversations using LangGraph, events stream live updates to clients showing tool execution, rendering custom UI components, and enabling browser-side interactions.

Key Features:

  • Dual Communication: SSE for one-way streaming, WebSocket for bidirectional communication
  • Type-Safe Events: Full TypeScript support with payload validation
  • Session Management: Automatic session isolation and request tracking
  • Client Integration: Seamless integration with @ixo/oracles-client-sdk
  • Browser Tools: Reverse tool calls that execute in the user's browser

Communication Channels

Server-Sent Events (SSE)

Use Case: One-way streaming from server to client
Best For: Tool call updates, component rendering, status notifications
Endpoint: /sse/events?sessionId={sessionId}

WebSocket (WS)

Use Case: Bidirectional real-time communication
Best For: Browser tool calls, interactive features, immediate responses
Implementation: Socket.IO with session-based rooms

Event Types

1. ToolCallEvent

Purpose: Notifies clients when LLM executes tools on the server
Use Cases: API calls, database queries, long-running operations

// Start tool execution
new ToolCallEvent({
  sessionId: 'user-session-123',
  requestId: 'request-456',
  toolName: 'get_weather',
  args: { location: 'New York' },
  status: 'isRunning',
}).emit();

// Tool completes
new ToolCallEvent({
  sessionId: 'user-session-123',
  requestId: 'request-456',
  toolName: 'get_weather',
  status: 'done',
  output: 'Temperature: 72°F, Sunny',
}).emit();

2. RenderComponentEvent

Purpose: Instructs client to render custom UI components
Use Cases: Dynamic lists, charts, forms, interactive elements

new RenderComponentEvent({
  sessionId: 'user-session-123',
  requestId: 'request-456',
  componentName: 'ProductList',
  args: {
    products: [
      { name: 'Product A', price: 29.99 },
      { name: 'Product B', price: 39.99 },
    ],
  },
  status: 'done',
}).emit();

3. BrowserToolCallEvent

Purpose: Enables LLM to execute tools directly in user's browser
Use Cases: UI navigation, DOM manipulation, browser-specific operations

// Server-side: Call browser tool
import { callBrowserTool } from '@ixo/common';

const result = await callBrowserTool({
  sessionId: 'user-session-123',
  toolCallId: 'tool-call-789',
  toolName: 'navigateToPage',
  args: { url: '/products' },
});

4. MessageCacheInvalidationEvent

Purpose: Notifies clients to refresh message cache
Use Cases: New messages, conversation state changes

5. RouterEvent

Purpose: Manages conversation flow and navigation state
Use Cases: Multi-step conversations, wizard flows

Installation

pnpm install @ixo/oracles-events

Creating a New Event

1. Define Event Interface & Class

Create your event by extending BaseEvent:

// src/events/my-custom-event/types.ts
export const EVENT_NAME = 'my_custom_event' as const;

export interface IMyCustomEvent {
  message: string;
  priority: 'high' | 'normal' | 'low';
  metadata?: Record<string, any>;
}
// src/events/my-custom-event/my-custom-event.ts
import {
  BaseEvent,
  shouldHaveSessionId,
  WithRequiredEventProps,
} from '../base-event/base-event';
import { EVENT_NAME, IMyCustomEvent } from './types';

export class MyCustomEvent extends BaseEvent<IMyCustomEvent> {
  constructor(public payload: WithRequiredEventProps<IMyCustomEvent>) {
    super();
    shouldHaveSessionId(payload);
  }

  public eventName = EVENT_NAME;
  static eventName = EVENT_NAME;
}

2. Register Event in GraphEventEmitter

// src/graph-event-emitter.ts
import { MyCustomEvent } from './events/my-custom-event/my-custom-event';

export class GraphEventEmitter {
  static registerEventHandlers(server: Server): void {
    MyCustomEvent.registerEventHandlers(server);
    // ... other events
  }
}

3. Export Event

// src/events/index.ts
export { MyCustomEvent } from './my-custom-event/my-custom-event';
export type { IMyCustomEvent } from './my-custom-event/types';

4. Add to Types

// src/types/index.ts
import type { MyCustomEvent } from '../events';

export type AllEvents =
  | ToolCallEvent
  | RenderComponentEvent
  | BrowserToolCallEvent
  | MessageCacheInvalidationEvent
  | RouterEvent
  | MyCustomEvent; // Add your event here

export type MyCustomEventPayload = WithRequiredEventProps<IMyCustomEvent>;

5. Emit Your Event

There are two ways to emit events depending on your use case:

Use Case: Standard event flow that automatically reaches clients via WebSocket
How it works: event.emit()rootEventEmitterGraphEventEmitter (if registered) → WebSocket clients

// In your LangGraph node or service
import { ToolCallEvent } from '@ixo/oracles-events';

const event = new ToolCallEvent({
  sessionId: 'user-session-123',
  requestId: 'request-456',
  toolName: 'get_weather',
  args: { location: 'New York' },
  status: 'isRunning',
});

// This follows the complete flow:
// 1. event.emit() → rootEventEmitter.emit(eventName, payload)
// 2. GraphEventEmitter (registered in ws.gateway.ts) listens to rootEventEmitter
// 3. Automatically broadcasts to WebSocket clients in the session
event.emit();

Complete Event Flow:

Your Code → event.emit() → rootEventEmitter → GraphEventEmitter → WebSocket Server → Client

Registered Events: ToolCallEvent, RenderComponentEvent, BrowserToolCallEvent, MessageCacheInvalidationEvent, RouterEvent

Method 2: Direct Service Emission

Use Case: Direct emission to specific communication channels
How it works: Bypass event system and emit directly to SSE/WebSocket services

// Direct SSE emission (like in messages.service.ts)
import { SseService } from 'src/sse/sse.service';

@Injectable()
export class MyService {
  constructor(private readonly sseService: SseService) {}

  sendEventToClient(sessionId: string) {
    const event = new MyCustomEvent({
      sessionId,
      requestId: 'request-456',
      message: 'Direct emission',
      priority: 'high',
    });

    // Direct emission to SSE clients
    this.sseService.publishToSession(sessionId, event);
  }
}

Method 3: Internal Communication

Use Case: Emit events back into the internal events system
How it works: Direct rootEventEmitter usage for service-to-service communication

// Internal emission (like in ws.gateway.ts)
import { rootEventEmitter } from '@ixo/oracles-events';

// Emit back into the events system for internal processing
rootEventEmitter.emit('browser_tool_result', {
  sessionId: 'user-session-123',
  toolCallId: 'tool-123',
  result: { success: true },
  timestamp: new Date().toISOString(),
});

When to use each method:

  • Method 1: Standard registered events that automatically reach WebSocket clients (ToolCallEvent, RenderComponentEvent, etc.)
  • Method 2: Real-time streaming scenarios, direct SSE client communication
  • Method 3: Internal service communication, responding to WebSocket events and triggering internal workflows

📊 Event Flow Diagram

The following diagram shows the complete flow from your code to the client:

graph LR
    A["🧠 Your Code<br/>event.emit()"] --> B["📡 rootEventEmitter"]
    B --> C["🎯 GraphEventEmitter<br/>(listens to rootEventEmitter)"]
    C --> D["📱 WebSocket → Client"]

    style A fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style B fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style C fill:#e8f5e8,stroke:#388e3c,stroke-width:2px
    style D fill:#fce4ec,stroke:#d81b60,stroke-width:2px

How it works:

  • You call event.emit() in your code
  • This triggers rootEventEmitter (internal event bus)
  • GraphEventEmitter listens to rootEventEmitter and forwards to WebSocket
  • Client receives the event automatically

That's it! Once GraphEventEmitter is registered in ws.gateway.ts, all events automatically reach clients.

Event Status Management

Many events support status tracking:

// Start long-running operation
new ToolCallEvent({
  sessionId,
  requestId,
  toolName: 'complex_analysis',
  status: 'isRunning',
}).emit();

// Update with progress (optional)
new ToolCallEvent({
  sessionId,
  requestId,
  toolName: 'complex_analysis',
  status: 'isRunning',
  output: 'Progress: 50% complete',
}).emit();

// Complete operation
new ToolCallEvent({
  sessionId,
  requestId,
  toolName: 'complex_analysis',
  status: 'done',
  output: 'Analysis complete: Found 25 insights',
}).emit();

Event Chaining

Chain multiple events for complex workflows:

// 1. Show tool execution
new ToolCallEvent({
  sessionId,
  requestId,
  toolName: 'data_analysis',
  status: 'isRunning',
}).emit();

// 2. Complete tool and render results
const data = await performAnalysis();
new ToolCallEvent({
  sessionId,
  requestId,
  toolName: 'data_analysis',
  status: 'done',
  output: `Analyzed ${data.length} records`,
}).emit();

// 3. Render custom component with results
new RenderComponentEvent({
  sessionId,
  requestId,
  componentName: 'AnalysisChart',
  args: { data, chartType: 'bar' },
  status: 'done',
}).emit();

Required Payload Properties

Every event payload must include:

interface RequiredEventProps {
  sessionId: string; // Chat session identifier
  requestId: string; // Request identifier (for tracking)
}

Session and Request Management

// Use consistent IDs for related events
const sessionId = 'user-session-123';
const requestId = 'message-request-456';

// Multiple events for the same request
new ToolCallEvent({ sessionId, requestId /* ... */ });
new RenderComponentEvent({ sessionId, requestId /* ... */ });

Testing

Basic Event Testing

import { MyCustomEvent } from '../my-custom-event';
import { rootEventEmitter } from '../../root-event-emitter';

describe('MyCustomEvent', () => {
  it('should emit with correct payload', () => {
    const payload = {
      sessionId: 'test-session',
      requestId: 'test-request',
      message: 'test message',
      priority: 'high' as const,
    };

    const event = new MyCustomEvent(payload);
    const emitSpy = jest.spyOn(rootEventEmitter, 'emit');

    event.emit();

    expect(emitSpy).toHaveBeenCalledWith(MyCustomEvent.eventName, payload);
  });

  it('should require sessionId', () => {
    expect(() => {
      new MyCustomEvent({
        requestId: 'test-request',
        message: 'test',
        priority: 'normal',
      } as any);
    }).toThrow();
  });
});

Integration Testing

import { createTestEvent } from '../test-utils';

it('should register event handlers correctly', () => {
  const mockServer = {
    on: jest.fn(),
    emit: jest.fn(),
  };

  MyCustomEvent.registerEventHandlers(mockServer as any);

  expect(mockServer.on).toHaveBeenCalledWith(
    MyCustomEvent.eventName,
    expect.any(Function),
  );
});

Contributing

Event Naming Conventions

  • Use snake_case: tool_call, render_component, my_custom_event
  • Be descriptive: user_preferences_updated not update
  • Include domain if needed: payment_status_changed

TypeScript Best Practices

  • Always define interfaces for payload types
  • Use WithRequiredEventProps<T> for constructor parameters
  • Export both the event class and interface
  • Add proper JSDoc comments

Performance Considerations

  • Batch events when possible instead of emitting many small events
  • Use appropriate channels: SSE for notifications, WS for interactions
  • Validate payloads before emission to catch errors early
  • Avoid large payloads: Keep event data focused and minimal

Security Guidelines

  • Validate all inputs in event payloads
  • Sanitize user data before including in events
  • Ensure session isolation - events only go to correct sessions
  • Never include sensitive data in event payloads

Architecture Documentation

For comprehensive system architecture, event flows, and integration patterns, see Events System Architecture.

Thank you for using @ixo/oracles-events!

FAQs

Package last updated on 01 Jun 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts