138 lines
3.5 KiB
TypeScript
138 lines
3.5 KiB
TypeScript
|
|
/**
|
||
|
|
* 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 };
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|