veza/apps/web/src/utils/statePersistence.ts

138 lines
3.5 KiB
TypeScript
Raw Normal View History

/**
* State Persistence Utilities
* FE-STATE-001: Utilities for managing state persistence in Zustand stores
*
* Provides helpers for consistent state persistence configuration
*/
import { StateStorage } from 'zustand/middleware';
/**
* Custom storage implementation with error handling
*/
export const createPersistentStorage = (name: string): StateStorage => {
return {
getItem: (key: string): string | null => {
try {
if (typeof window === 'undefined') {
return null;
}
return localStorage.getItem(key);
} catch (error) {
console.warn(`[StatePersistence] Failed to get item ${key} from localStorage:`, error);
return null;
}
},
setItem: (key: string, value: string): void => {
try {
if (typeof window === 'undefined') {
return;
}
localStorage.setItem(key, value);
} catch (error) {
console.warn(`[StatePersistence] Failed to set item ${key} in localStorage:`, error);
// Handle quota exceeded error
if (error instanceof DOMException && error.name === 'QuotaExceededError') {
console.error('[StatePersistence] localStorage quota exceeded. Clearing old data...');
// Optionally clear old data or notify user
}
}
},
removeItem: (key: string): void => {
try {
if (typeof window === 'undefined') {
return;
}
localStorage.removeItem(key);
} catch (error) {
console.warn(`[StatePersistence] Failed to remove item ${key} from localStorage:`, error);
}
},
};
};
/**
* Clear all persisted state for a specific store
*/
export const clearPersistedState = (storeName: string): void => {
try {
if (typeof window === 'undefined') {
return;
}
localStorage.removeItem(storeName);
} catch (error) {
console.warn(`[StatePersistence] Failed to clear persisted state for ${storeName}:`, error);
}
};
/**
* Get persisted state for a specific store
*/
export const getPersistedState = <T = any>(storeName: string): T | null => {
try {
if (typeof window === 'undefined') {
return null;
}
const item = localStorage.getItem(storeName);
if (!item) {
return null;
}
return JSON.parse(item) as T;
} catch (error) {
console.warn(`[StatePersistence] Failed to get persisted state for ${storeName}:`, error);
return null;
}
};
/**
* Check if localStorage is available
*/
export const isLocalStorageAvailable = (): boolean => {
try {
if (typeof window === 'undefined') {
return false;
}
const test = '__localStorage_test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch {
return false;
}
};
/**
* Get storage usage information
*/
export const getStorageInfo = (): {
used: number;
available: number;
percentage: number;
} => {
try {
if (typeof window === 'undefined') {
return { used: 0, available: 0, percentage: 0 };
}
let used = 0;
for (const key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
used += localStorage[key].length + key.length;
}
}
// Estimate available storage (typically 5-10MB)
const available = 5 * 1024 * 1024; // 5MB estimate
const percentage = (used / available) * 100;
return {
used,
available,
percentage: Math.min(percentage, 100),
};
} catch {
return { used: 0, available: 0, percentage: 0 };
}
};