Approve database migration

This commit is contained in:
gpt-engineer-app[bot]
2025-11-03 15:18:06 +00:00
parent 5612d19d07
commit a86da6e833
17 changed files with 1189 additions and 36 deletions

View File

@@ -0,0 +1,64 @@
/**
* Environment Context Capture
* Captures browser/device information for error reports
*/
export interface EnvironmentContext {
viewport: { width: number; height: number };
screen: { width: number; height: number };
memory?: { usedJSHeapSize?: number; totalJSHeapSize?: number };
connection?: string;
timezone: string;
language: string;
platform: string;
cookiesEnabled: boolean;
localStorage: boolean;
sessionStorage: boolean;
}
export function captureEnvironmentContext(): EnvironmentContext {
const context: EnvironmentContext = {
viewport: {
width: window.innerWidth,
height: window.innerHeight,
},
screen: {
width: window.screen.width,
height: window.screen.height,
},
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
language: navigator.language,
platform: navigator.platform,
cookiesEnabled: navigator.cookieEnabled,
localStorage: isStorageAvailable('localStorage'),
sessionStorage: isStorageAvailable('sessionStorage'),
};
// Memory info (Chrome only)
if ('memory' in performance && (performance as any).memory) {
const memory = (performance as any).memory;
context.memory = {
usedJSHeapSize: memory.usedJSHeapSize,
totalJSHeapSize: memory.totalJSHeapSize,
};
}
// Connection info
if ('connection' in navigator) {
context.connection = (navigator as any).connection?.effectiveType;
}
return context;
}
function isStorageAvailable(type: 'localStorage' | 'sessionStorage'): boolean {
try {
const storage = window[type];
const test = '__storage_test__';
storage.setItem(test, test);
storage.removeItem(test);
return true;
} catch {
return false;
}
}

View File

@@ -0,0 +1,80 @@
/**
* Error Breadcrumb Tracking
* Captures user actions before errors occur for better debugging
*/
export interface Breadcrumb {
timestamp: string;
category: 'navigation' | 'user_action' | 'api_call' | 'state_change';
message: string;
level: 'info' | 'warning' | 'error';
data?: Record<string, any>;
}
class BreadcrumbManager {
private breadcrumbs: Breadcrumb[] = [];
private readonly MAX_BREADCRUMBS = 10;
add(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void {
const newBreadcrumb: Breadcrumb = {
...breadcrumb,
timestamp: new Date().toISOString(),
};
this.breadcrumbs.push(newBreadcrumb);
// Keep only last 10 breadcrumbs
if (this.breadcrumbs.length > this.MAX_BREADCRUMBS) {
this.breadcrumbs.shift();
}
}
getAll(): Breadcrumb[] {
return [...this.breadcrumbs];
}
clear(): void {
this.breadcrumbs = [];
}
}
export const breadcrumbManager = new BreadcrumbManager();
// Helper functions for common breadcrumb types
export const breadcrumb = {
navigation: (to: string, from?: string) => {
breadcrumbManager.add({
category: 'navigation',
message: `Navigated to ${to}`,
level: 'info',
data: { to, from },
});
},
userAction: (action: string, component: string, data?: Record<string, any>) => {
breadcrumbManager.add({
category: 'user_action',
message: `User ${action} in ${component}`,
level: 'info',
data,
});
},
apiCall: (endpoint: string, method: string, status?: number) => {
breadcrumbManager.add({
category: 'api_call',
message: `API ${method} ${endpoint}`,
level: status && status >= 400 ? 'error' : 'info',
data: { endpoint, method, status },
});
},
stateChange: (description: string, data?: Record<string, any>) => {
breadcrumbManager.add({
category: 'state_change',
message: description,
level: 'info',
data,
});
},
};

View File

@@ -21,7 +21,10 @@ export class AppError extends Error {
export const handleError = (
error: unknown,
context: ErrorContext
): void => {
): string => { // Now returns error ID
const errorId = context.metadata?.requestId as string | undefined;
const shortErrorId = errorId ? errorId.slice(0, 8) : undefined;
const errorMessage = error instanceof AppError
? error.userMessage || error.message
: error instanceof Error
@@ -32,14 +35,19 @@ export const handleError = (
logger.error('Error occurred', {
...context,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined
stack: error instanceof Error ? error.stack : undefined,
errorId,
});
// Show user-friendly toast
// Show user-friendly toast with error ID
toast.error(context.action, {
description: errorMessage,
duration: 5000
description: shortErrorId
? `${errorMessage}\n\nReference ID: ${shortErrorId}`
: errorMessage,
duration: 5000,
});
return errorId || 'unknown';
};
export const handleSuccess = (

View File

@@ -5,6 +5,8 @@
import { supabase } from '@/integrations/supabase/client';
import { requestContext, type RequestContext } from './requestContext';
import { breadcrumbManager } from './errorBreadcrumbs';
import { captureEnvironmentContext } from './environmentContext';
export interface RequestTrackingOptions {
endpoint: string;
@@ -63,8 +65,16 @@ export async function trackRequest<T>(
} catch (error: unknown) {
const duration = Date.now() - start;
const errorInfo = error instanceof Error
? { type: error.name, message: error.message }
: { type: 'UnknownError', message: String(error) };
? {
type: error.name,
message: error.message,
stack: error.stack ? error.stack.slice(0, 5000) : undefined // Limit to 5000 chars
}
: { type: 'UnknownError', message: String(error), stack: undefined };
// Capture breadcrumbs and environment
const breadcrumbs = breadcrumbManager.getAll();
const environment = captureEnvironmentContext();
// Log error to database (fire and forget)
logRequestMetadata({
@@ -76,6 +86,9 @@ export async function trackRequest<T>(
duration,
errorType: errorInfo.type,
errorMessage: errorInfo.message,
errorStack: errorInfo.stack,
breadcrumbs,
environmentContext: environment,
userAgent: context.userAgent,
clientVersion: context.clientVersion,
parentRequestId: options.parentRequestId,
@@ -100,6 +113,9 @@ interface RequestMetadata {
duration: number;
errorType?: string;
errorMessage?: string;
errorStack?: string;
breadcrumbs?: any[];
environmentContext?: any;
userAgent?: string;
clientVersion?: string;
parentRequestId?: string;
@@ -117,6 +133,9 @@ async function logRequestMetadata(metadata: RequestMetadata): Promise<void> {
p_duration_ms: metadata.duration,
p_error_type: metadata.errorType ?? undefined,
p_error_message: metadata.errorMessage ?? undefined,
p_error_stack: metadata.errorStack ?? undefined,
p_breadcrumbs: metadata.breadcrumbs ? JSON.stringify(metadata.breadcrumbs) : '[]',
p_environment_context: metadata.environmentContext ? JSON.stringify(metadata.environmentContext) : '{}',
p_user_agent: metadata.userAgent ?? undefined,
p_client_version: metadata.clientVersion ?? undefined,
p_parent_request_id: metadata.parentRequestId ?? undefined,