Skip to main content
Glama
UpdatePoller.ts5.39 kB
/** * Update Poller * * Polling-based real-time update system for MCP-UI artifacts. * Tracks artifact changes and provides updates via MCP tool calls. * * Architecture Decision: Use polling instead of SSE to avoid external HTTP server. * Trade-off: 2s latency vs 500ms SSE (acceptable for visualizations). * * @module realtime/UpdatePoller */ /** * Types of artifact changes that can be tracked */ export type ArtifactChangeType = 'data_update' | 'filter_change' | 'theme_change'; /** * Artifact change event */ export interface ArtifactChange { /** Artifact ID that changed */ artifactId: string; /** Type of change */ type: ArtifactChangeType; /** Change payload (type-specific data) */ payload: unknown; /** Timestamp when change occurred (Unix ms) */ timestamp: number; } /** * Update poller configuration */ export interface UpdatePollerConfig { /** Maximum age for changes (ms) - older changes are purged */ maxAge?: number; /** Maximum changes per artifact - older changes are dropped */ maxChangesPerArtifact?: number; } /** * Default configuration */ const DEFAULT_CONFIG: Required<UpdatePollerConfig> = { maxAge: 60000, // 1 minute maxChangesPerArtifact: 100, }; /** * Polling-based update tracker for artifacts * * Manages a change log for artifacts, allowing clients to poll for updates * since a specific timestamp. Automatically cleans up old changes to prevent * memory leaks. */ export class UpdatePoller { private changeLog: Map<string, ArtifactChange[]> = new Map(); private config: Required<UpdatePollerConfig>; constructor(config: UpdatePollerConfig = {}) { this.config = { ...DEFAULT_CONFIG, ...config }; } /** * Record a change to an artifact * * @param artifactId Artifact that changed * @param change Change details (without timestamp - added automatically) */ recordChange( artifactId: string, change: Omit<ArtifactChange, 'artifactId' | 'timestamp'> ): void { const entry: ArtifactChange = { artifactId, type: change.type, payload: change.payload, timestamp: Date.now(), }; // Initialize change log for artifact if needed if (!this.changeLog.has(artifactId)) { this.changeLog.set(artifactId, []); } const changes = this.changeLog.get(artifactId)!; changes.push(entry); // Enforce max changes limit if (changes.length > this.config.maxChangesPerArtifact) { changes.shift(); // Remove oldest } // Cleanup old changes across all artifacts this.cleanupOldChanges(); } /** * Get updates for an artifact since a timestamp * * @param artifactId Artifact to get updates for * @param since Timestamp to get updates after (Unix ms) * @returns Array of changes since timestamp (newest first) */ getUpdates(artifactId: string, since: number): ArtifactChange[] { const changes = this.changeLog.get(artifactId) || []; return changes .filter((c) => c.timestamp > since) .sort((a, b) => b.timestamp - a.timestamp); // Newest first } /** * Get all updates across all artifacts since a timestamp * * @param since Timestamp to get updates after (Unix ms) * @returns Array of all changes since timestamp (newest first) */ getAllUpdates(since: number): ArtifactChange[] { const allChanges: ArtifactChange[] = []; for (const changes of this.changeLog.values()) { allChanges.push(...changes.filter((c) => c.timestamp > since)); } return allChanges.sort((a, b) => b.timestamp - a.timestamp); } /** * Clear all changes for an artifact * * @param artifactId Artifact to clear */ clearArtifact(artifactId: string): void { this.changeLog.delete(artifactId); } /** * Clear all changes across all artifacts */ clearAll(): void { this.changeLog.clear(); } /** * Get current change log size (for monitoring) * * @returns Total number of tracked changes */ getChangeCount(): number { let count = 0; for (const changes of this.changeLog.values()) { count += changes.length; } return count; } /** * Get number of tracked artifacts * * @returns Number of artifacts with changes */ getArtifactCount(): number { return this.changeLog.size; } /** * Cleanup old changes (older than maxAge) * * Removes stale changes to prevent unbounded memory growth. * Called automatically after each recordChange(). */ private cleanupOldChanges(): void { const cutoff = Date.now() - this.config.maxAge; const artifactsToDelete: string[] = []; for (const [artifactId, changes] of this.changeLog.entries()) { // Filter out old changes const recentChanges = changes.filter((c) => c.timestamp > cutoff); if (recentChanges.length === 0) { // No recent changes, mark for deletion artifactsToDelete.push(artifactId); } else if (recentChanges.length < changes.length) { // Some changes removed, update array this.changeLog.set(artifactId, recentChanges); } } // Delete artifacts with no recent changes for (const artifactId of artifactsToDelete) { this.changeLog.delete(artifactId); } } } /** * Global update poller instance (singleton pattern) */ export const updatePoller = new UpdatePoller();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/EricA1019/CTS_MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server