Events
Type-safe event system for reactive updates
LocalMode provides a type-safe event system for building reactive applications. Subscribe to VectorDB lifecycle events, embedding operations, and custom events for real-time UI updates.
Overview
The event system enables:
- Reactive UI updates — Re-render components when data changes
- Cross-component communication — Notify different parts of your app
- Debugging & logging — Track all database operations
- Custom integrations — Build workflows on top of database events
Quick Start
import { createEventEmitter, VectorDBEvents } from '@localmode/core';
// Create an event emitter
const events = createEventEmitter<VectorDBEvents>();
// Subscribe to events
events.on('add', ({ id }) => {
console.log('Document added:', id);
});
// Emit events
events.emit('add', { id: 'doc-1' });Creating Event Emitters
Typed Event Emitter
import { createEventEmitter, VectorDBEvents } from '@localmode/core';
// Create with built-in VectorDB event types
const dbEvents = createEventEmitter<VectorDBEvents>();
// Or create a new EventEmitter class directly
import { EventEmitter } from '@localmode/core';
const emitter = new EventEmitter<VectorDBEvents>();Custom Event Types
import { EventEmitter } from '@localmode/core';
// Define your custom event types
interface MyAppEvents {
userLogin: { userId: string; timestamp: Date };
searchPerformed: { query: string; resultCount: number };
documentProcessed: { docId: string; chunks: number };
}
const appEvents = new EventEmitter<MyAppEvents>();
// Type-safe subscriptions
appEvents.on('userLogin', ({ userId, timestamp }) => {
console.log(`User ${userId} logged in at ${timestamp}`);
});
// Type-safe emissions
appEvents.emit('userLogin', {
userId: 'user-123',
timestamp: new Date(),
});VectorDB Events
Built-in event types for VectorDB operations:
Prop
Type
Embedding Events
Event types for embedding operations:
Prop
Type
Event Methods
on(event, callback)
Subscribe to an event. Returns an unsubscribe function.
const unsubscribe = events.on('add', ({ id }) => {
console.log('Added:', id);
});
// Later: unsubscribe
unsubscribe();once(event, callback)
Subscribe for a single emission only.
events.once('modelLoad', ({ modelId }) => {
console.log('Model loaded (first time only):', modelId);
});emit(event, data)
Emit an event synchronously.
events.emit('add', { id: 'doc-1', collection: 'default' });emitAsync(event, data)
Emit an event and wait for all async handlers to complete.
await events.emitAsync('add', { id: 'doc-1' });
// All handlers (including async ones) have completedoff(event?)
Remove listeners.
// Remove all listeners for specific event
events.off('add');
// Remove all listeners for all events
events.off();Utility Methods
// Get listener count
const count = events.listenerCount('add');
// Check if there are any listeners
const hasListeners = events.hasListeners('add');
// Get all event names with listeners
const eventNames = events.eventNames();Global Event Bus
LocalMode provides a global event bus for app-wide events:
import { globalEventBus } from '@localmode/core';
// Subscribe anywhere in your app
globalEventBus.on('add', ({ id }) => {
console.log('Document added somewhere:', id);
});
// Useful for:
// - Debugging all database operations
// - Syncing state across components
// - Global loggingThe global event bus receives events from all VectorDB instances, making it useful for centralized logging and state management.
Event Middleware
Create middleware that emits events for VectorDB operations:
import { wrapVectorDB, createEventEmitter, eventMiddleware } from '@localmode/core';
// Create event emitter
const events = createEventEmitter();
// Subscribe to events
events.on('add', ({ id }) => console.log('Added:', id));
events.on('delete', ({ id }) => console.log('Deleted:', id));
// Create DB with event middleware
const db = wrapVectorDB({
db: baseDb,
middleware: eventMiddleware(events),
});
// Now all operations emit events automatically
await db.add({ id: 'doc-1', vector, metadata });
// Console: "Added: doc-1"React Integration
Custom Hook
import { useEffect, useState } from 'react';
import { createEventEmitter, VectorDBEvents } from '@localmode/core';
const events = createEventEmitter<VectorDBEvents>();
function useVectorDBEvents() {
const [documentCount, setDocumentCount] = useState(0);
const [lastOperation, setLastOperation] = useState<string | null>(null);
useEffect(() => {
const unsubscribeAdd = events.on('add', () => {
setDocumentCount((c) => c + 1);
setLastOperation('add');
});
const unsubscribeDelete = events.on('delete', () => {
setDocumentCount((c) => c - 1);
setLastOperation('delete');
});
const unsubscribeClear = events.on('clear', () => {
setDocumentCount(0);
setLastOperation('clear');
});
return () => {
unsubscribeAdd();
unsubscribeDelete();
unsubscribeClear();
};
}, []);
return { documentCount, lastOperation };
}Search Analytics
function SearchAnalytics() {
const [searches, setSearches] = useState<
Array<{
query: string;
results: number;
duration: number;
}>
>([]);
useEffect(() => {
return globalEventBus.on('search', ({ resultsCount, k, durationMs }) => {
setSearches((prev) => [
...prev.slice(-99), // Keep last 100
{ query: 'unknown', results: resultsCount, duration: durationMs },
]);
});
}, []);
const avgDuration = searches.reduce((sum, s) => sum + s.duration, 0) / searches.length;
return (
<div>
<p>Total searches: {searches.length}</p>
<p>Average duration: {avgDuration.toFixed(2)}ms</p>
</div>
);
}Full Example
import {
createVectorDB,
embed,
createEventEmitter,
VectorDBEvents,
EmbeddingEvents,
} from '@localmode/core';
import { transformers } from '@localmode/transformers';
// Create event emitters
const dbEvents = createEventEmitter<VectorDBEvents>();
const embedEvents = createEventEmitter<EmbeddingEvents>();
// Set up logging
dbEvents.on('add', ({ id }) => console.log(`[DB] Added: ${id}`));
dbEvents.on('search', ({ resultsCount, durationMs }) => {
console.log(`[DB] Search: ${resultsCount} results in ${durationMs}ms`);
});
dbEvents.on('error', ({ operation, error }) => {
console.error(`[DB] Error in ${operation}:`, error);
});
embedEvents.on('embedStart', ({ valueCount }) => {
console.log(`[Embed] Starting ${valueCount} values`);
});
embedEvents.on('embedComplete', ({ valueCount, durationMs, tokens }) => {
console.log(`[Embed] Completed ${valueCount} values in ${durationMs}ms (${tokens} tokens)`);
});
embedEvents.on('modelLoad', ({ modelId, durationMs }) => {
console.log(`[Embed] Model ${modelId} loaded in ${durationMs}ms`);
});
// Create database
const db = await createVectorDB({ name: 'documents', dimensions: 384 });
const model = transformers.embedding('Xenova/all-MiniLM-L6-v2');
// Manually emit events (or use middleware)
async function addDocument(text: string) {
const id = crypto.randomUUID();
embedEvents.emit('embedStart', { valueCount: 1 });
const start = performance.now();
const { embedding, usage } = await embed({ model, value: text });
embedEvents.emit('embedComplete', {
valueCount: 1,
durationMs: performance.now() - start,
tokens: usage.tokens,
});
await db.add({ id, vector: embedding, metadata: { text } });
dbEvents.emit('add', { id });
return id;
}