Implement Phase 4: Transaction Resilience

This commit implements Phase 4 of the Sacred Pipeline, focusing on transaction resilience. It introduces:

- **Timeout Detection & Recovery**: New utilities in `src/lib/timeoutDetection.ts` to detect, categorize (minor, moderate, critical), and provide recovery strategies for timeouts across various sources (fetch, Supabase, edge functions, database). Includes a `withTimeout` wrapper.
- **Lock Auto-Release**: Implemented in `src/lib/moderation/lockAutoRelease.ts` to automatically release submission locks on error, timeout, abandonment, or inactivity. Includes mechanisms for unload events and inactivity monitoring.
- **Idempotency Key Lifecycle Management**: A new module `src/lib/idempotencyLifecycle.ts` to track idempotency keys through their states (pending, processing, completed, failed, expired) using IndexedDB. Includes automatic cleanup of expired keys.
- **Enhanced Idempotency Helpers**: Updated `src/lib/idempotencyHelpers.ts` to integrate with the new lifecycle management, providing functions to generate, register, validate, and update the status of idempotency keys.
- **Transaction Resilience Hook**: A new hook `src/hooks/useTransactionResilience.ts` that combines timeout handling, lock auto-release, and idempotency key management for robust transaction execution.
- **Submission Queue Integration**: Updated `src/hooks/useSubmissionQueue.ts` to leverage the new submission queue and idempotency lifecycle functionalities.
- **Documentation**: Added `PHASE4_TRANSACTION_RESILIENCE.md` detailing the implemented features and their usage.
This commit is contained in:
gpt-engineer-app[bot]
2025-11-07 15:03:12 +00:00
parent 095278dafd
commit 34dbe2e262
7 changed files with 1397 additions and 12 deletions

View File

@@ -1,9 +1,14 @@
import { useState, useEffect, useCallback } from 'react';
import { QueuedSubmission } from '@/components/submission/SubmissionQueueIndicator';
import { useNetworkStatus } from './useNetworkStatus';
// This is a placeholder implementation
// In a real app, this would interact with IndexedDB and the actual submission system
import {
getPendingSubmissions,
processQueue,
removeFromQueue,
clearQueue as clearQueueStorage,
getPendingCount,
} from '@/lib/submissionQueue';
import { logger } from '@/lib/logger';
interface UseSubmissionQueueOptions {
autoRetry?: boolean;
@@ -42,13 +47,24 @@ export function useSubmissionQueue(options: UseSubmissionQueueOptions = {}) {
}, [isOnline, autoRetry, queuedItems.length, retryDelayMs]);
const loadQueueFromStorage = useCallback(async () => {
// Placeholder: Load from IndexedDB
// In real implementation, this would query the offline queue
try {
// const items = await getQueuedSubmissions();
// setQueuedItems(items);
const pending = await getPendingSubmissions();
// Transform to QueuedSubmission format
const items: QueuedSubmission[] = pending.map(item => ({
id: item.id,
type: item.type,
entityName: item.data?.name || item.data?.title || 'Unknown',
timestamp: new Date(item.timestamp),
status: item.retries >= 3 ? 'failed' : (item.lastAttempt ? 'retrying' : 'pending'),
retryCount: item.retries,
error: item.error || undefined,
}));
setQueuedItems(items);
logger.info('[SubmissionQueue] Loaded queue', { count: items.length });
} catch (error) {
console.error('Failed to load queue:', error);
logger.error('[SubmissionQueue] Failed to load queue', { error });
}
}, []);
@@ -97,13 +113,24 @@ export function useSubmissionQueue(options: UseSubmissionQueueOptions = {}) {
}
}, [queuedItems, maxRetries, retryItem]);
const removeItem = useCallback((id: string) => {
setQueuedItems(prev => prev.filter(item => item.id !== id));
const removeItem = useCallback(async (id: string) => {
try {
await removeFromQueue(id);
setQueuedItems(prev => prev.filter(item => item.id !== id));
logger.info('[SubmissionQueue] Removed item', { id });
} catch (error) {
logger.error('[SubmissionQueue] Failed to remove item', { id, error });
}
}, []);
const clearQueue = useCallback(async () => {
// Placeholder: Clear from IndexedDB
setQueuedItems([]);
try {
const count = await clearQueueStorage();
setQueuedItems([]);
logger.info('[SubmissionQueue] Cleared queue', { count });
} catch (error) {
logger.error('[SubmissionQueue] Failed to clear queue', { error });
}
}, []);
return {