UNPKG

claritykit-svelte

Version:

A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility

232 lines (231 loc) 7 kB
// Event Capture Utility for Event Sourcing import { BROWSER, DEV } from 'esm-env'; // Safely access environment variables let API_BASE_URL = '/api'; let APP_VERSION = '1.0.0'; if (BROWSER) { try { API_BASE_URL = globalThis.VITE_API_URL || '/api'; APP_VERSION = globalThis.VITE_APP_VERSION || '1.0.0'; } catch { // Fallback values already set } } /** * Creates an event capture for staging.captures table * This implements the event sourcing pattern for database operations */ export async function createEventCapture(event) { const enrichedEvent = { ...event, timestamp: event.timestamp || new Date().toISOString(), metadata: { ...event.metadata, client_version: APP_VERSION, user_agent: navigator.userAgent, viewport: { width: window.innerWidth, height: window.innerHeight } } }; try { const response = await fetch(`${API_BASE_URL}/captures`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Source': 'clarity-kit-ui' }, body: JSON.stringify(enrichedEvent) }); if (!response.ok) { throw new Error(`Failed to capture event: ${response.statusText}`); } // Log successful event capture in development if (DEV) { console.log('[Event Captured]', enrichedEvent); } } catch (error) { console.error('[Event Capture Failed]', error, enrichedEvent); // Store failed events in localStorage for retry storeFailedEvent(enrichedEvent); // Re-throw for handling by caller throw error; } } /** * Batch create multiple event captures */ export async function createBatchEventCapture(events) { const enrichedEvents = events.map(event => ({ ...event, timestamp: event.timestamp || new Date().toISOString(), metadata: { ...event.metadata, batch_id: crypto.randomUUID(), client_version: APP_VERSION } })); try { const response = await fetch(`${API_BASE_URL}/captures/batch`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Source': 'clarity-kit-ui' }, body: JSON.stringify({ events: enrichedEvents }) }); if (!response.ok) { throw new Error(`Failed to capture batch events: ${response.statusText}`); } } catch (error) { console.error('[Batch Event Capture Failed]', error); // Store failed events individually enrichedEvents.forEach(event => storeFailedEvent(event)); throw error; } } /** * Store failed events in localStorage for retry */ function storeFailedEvent(event) { try { const failedEvents = getFailedEvents(); failedEvents.push({ ...event, retry_count: 0, failed_at: new Date().toISOString() }); // Keep only last 100 failed events const trimmedEvents = failedEvents.slice(-100); localStorage.setItem('ck_failed_events', JSON.stringify(trimmedEvents)); } catch (e) { console.error('[Failed to store event]', e); } } /** * Get failed events from localStorage */ export function getFailedEvents() { try { const stored = localStorage.getItem('ck_failed_events'); return stored ? JSON.parse(stored) : []; } catch { return []; } } /** * Retry failed events */ export async function retryFailedEvents() { const failedEvents = getFailedEvents(); if (failedEvents.length === 0) return; const retryableEvents = failedEvents.filter((event) => event.retry_count < 3); for (const event of retryableEvents) { try { await createEventCapture(event); // Remove successful event from failed list removeFailedEvent(event); } catch (error) { // Increment retry count updateFailedEventRetryCount(event); } } } /** * Remove a successfully sent event from failed events */ function removeFailedEvent(event) { const failedEvents = getFailedEvents(); const filtered = failedEvents.filter((e) => e.timestamp !== event.timestamp); localStorage.setItem('ck_failed_events', JSON.stringify(filtered)); } /** * Update retry count for a failed event */ function updateFailedEventRetryCount(event) { const failedEvents = getFailedEvents(); const updated = failedEvents.map((e) => { if (e.timestamp === event.timestamp) { return { ...e, retry_count: (e.retry_count || 0) + 1 }; } return e; }); localStorage.setItem('ck_failed_events', JSON.stringify(updated)); } /** * Clear all failed events */ export function clearFailedEvents() { localStorage.removeItem('ck_failed_events'); } /** * Subscribe to real-time event updates via SSE */ export function subscribeToEvents(entityType, entityId, callback) { const eventSource = new EventSource(`${API_BASE_URL}/events/subscribe/${entityType}/${entityId}`); eventSource.addEventListener('message', (event) => { try { const data = JSON.parse(event.data); callback(data); } catch (error) { console.error('[Event Parse Error]', error); } }); eventSource.addEventListener('error', (error) => { console.error('[EventSource Error]', error); }); return eventSource; } /** * Create an optimistic update event * Used for immediate UI updates before server confirmation */ export function createOptimisticEvent(event) { return { ...event, metadata: { ...event.metadata, optimistic: true, optimistic_id: crypto.randomUUID() } }; } /** * Validate event payload before sending */ export function validateEventPayload(event) { if (!event.action || !event.entity_type || !event.source) { console.error('[Invalid Event]', 'Missing required fields', event); return false; } if (event.entity_type === 'task' || event.entity_type === 'project') { if (!event.payload && !event.entity_id) { console.error('[Invalid Event]', 'Missing payload or entity_id', event); return false; } } return true; } // Auto-retry failed events on page visibility change if (typeof document !== 'undefined') { document.addEventListener('visibilitychange', () => { if (!document.hidden) { retryFailedEvents().catch(console.error); } }); } // Periodic retry of failed events (every 5 minutes) if (typeof window !== 'undefined') { setInterval(() => { retryFailedEvents().catch(console.error); }, 5 * 60 * 1000); }