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
JavaScript
// 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);
}