ConstRetrieves a value from cache or localStorage.
getItem: (key: string): string | null => {
try {
// Check cache first to avoid redundant reads
if (key in storageCache) {
return storageCache[key];
}
// Return from localStorage synchronously
// NOTE: For most accurate data, use getItemAsync() which checks electron-store first
const value = readLocalStorageValue(key);
// Cache the value
if (value !== null) {
storageCache[key] = value;
}
return value;
} catch (error) {
captureError(
ErrorType.STORAGE,
`Error reading from storage key: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "read" },
);
return null;
}
},
Stores a value across all storage layers (cache, localStorage, electron-store). Skips writes if the value hasn't changed in the cache.
setItem: (key: string, value: string): void => {
try {
// Redundancy check: skip write if value hasn't changed in cache
// This prevents unnecessary I/O, but can cause drift if layers get out of sync
if (storageCache[key] === value) {
console.debug(`[Storage] 🔍 Skipping redundant write for key: ${key}`);
return;
}
console.debug(
`[Storage] 🔍 Setting item: ${key} (${value.length} bytes)`,
);
// Update cache
storageCache[key] = value;
// Store in localStorage for compatibility
writeLocalStorageValue(key, value);
// Also store in electronStore if available
if (globalThis.electronStore) {
globalThis.electronStore.setItem(key, value).catch((error) => {
captureError(
ErrorType.SYSTEM,
`Error writing to electron-store: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "write", target: "electron-store" },
);
});
}
} catch (error) {
captureError(
ErrorType.STORAGE,
`Error writing to storage key: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "write", valueSize: value.length },
);
}
},
Removes a value from all storage layers.
removeItem: (key: string): void => {
try {
console.debug(`[Storage] 🔍 Removing item: ${key}`);
// Remove from cache
delete storageCache[key];
// Remove from localStorage for compatibility
removeLocalStorageValue(key);
// Also remove from electronStore if available
if (globalThis.electronStore) {
globalThis.electronStore.removeItem(key).catch((error) => {
captureError(
ErrorType.SYSTEM,
`Error deleting from electron-store: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "delete", target: "electron-store" },
);
});
}
} catch (error) {
captureError(
ErrorType.STORAGE,
`Error deleting storage key: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "delete" },
);
}
},
Clears all storage layers.
clear: (): void => {
try {
console.info("[Storage] 🗑️ Clearing all storage...");
// Clear cache
const keyCount = Object.keys(storageCache).length;
for (const key of Object.keys(storageCache)) {
delete storageCache[key];
}
// Clear localStorage for compatibility
clearLocalStorageValues();
// Also clear electronStore if available
if (globalThis.electronStore) {
globalThis.electronStore.clear().catch((error) => {
console.warn("[Storage] ❌ Error clearing electron-store:", error);
});
}
console.info(`[Storage] ✅ Cleared ${keyCount} keys from storage`);
} catch (error) {
captureError(
ErrorType.STORAGE,
"Error clearing all storage layers",
error instanceof Error ? error : new Error(String(error)),
{ operation: "clear" },
);
}
},
Stores a value to electron-store (authoritative), then syncs to localStorage.
setItemAsync: async (key: string, value: string): Promise<void> => {
if (!globalThis.electronStore) {
// Fallback to sync method if no electron store
console.debug(
`[Storage] 🔍 No electron-store available, using sync setItem for ${key}`,
);
storage.setItem(key, value);
return;
}
try {
console.debug(
`[Storage] 🔍 Async setting item: ${key} (${value.length} bytes)`,
);
// Write to electron-store first (authoritative source)
await globalThis.electronStore.setItem(key, value);
// Update cache
storageCache[key] = value;
// Sync to localStorage
writeLocalStorageValue(key, value);
console.debug(`[Storage] ✅ Async set complete: ${key}`);
} catch (error) {
captureError(
ErrorType.STORAGE,
`Error async setting item: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "write-async", valueSize: value.length },
);
throw error;
}
},
Retrieves a value, preferring electron-store (authoritative) over localStorage.
getItemAsync: async (key: string): Promise<string | null> => {
if (globalThis.electronStore) {
try {
console.debug(`[Storage] 🔍 Async getting item: ${key}`);
const value = await globalThis.electronStore.getItem(key);
if (value === null) {
console.debug(`[Storage] 🔍 Item not found: ${key}`);
} else {
console.debug(
`[Storage] ✅ Found item: ${key} (${value.length} bytes)`,
);
writeLocalStorageValue(key, value); // keep localStorage in sync
storageCache[key] = value;
}
return value;
} catch (error) {
captureError(
ErrorType.SYSTEM,
`Error reading from electron-store: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "read-async", target: "electron-store" },
);
console.debug(`[Storage] 🔍 Falling back to localStorage for: ${key}`);
// fallback to localStorage
return readLocalStorageValue(key);
}
}
// fallback if no electronStore
console.debug(
`[Storage] 🔍 No electron-store available, using localStorage for ${key}`,
);
return readLocalStorageValue(key);
},
export const storage = {
/**
* Retrieves a value from cache or localStorage.
* @param key - Storage key.
* @returns The stored value, or null if not found.
* @source
*/
getItem: (key: string): string | null => {
try {
// Check cache first to avoid redundant reads
if (key in storageCache) {
return storageCache[key];
}
// Return from localStorage synchronously
// NOTE: For most accurate data, use getItemAsync() which checks electron-store first
const value = readLocalStorageValue(key);
// Cache the value
if (value !== null) {
storageCache[key] = value;
}
return value;
} catch (error) {
captureError(
ErrorType.STORAGE,
`Error reading from storage key: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "read" },
);
return null;
}
},
/**
* Stores a value across all storage layers (cache, localStorage, electron-store).
* Skips writes if the value hasn't changed in the cache.
* @param key - Storage key.
* @param value - Value to store.
* @source
*/
setItem: (key: string, value: string): void => {
try {
// Redundancy check: skip write if value hasn't changed in cache
// This prevents unnecessary I/O, but can cause drift if layers get out of sync
if (storageCache[key] === value) {
console.debug(`[Storage] 🔍 Skipping redundant write for key: ${key}`);
return;
}
console.debug(
`[Storage] 🔍 Setting item: ${key} (${value.length} bytes)`,
);
// Update cache
storageCache[key] = value;
// Store in localStorage for compatibility
writeLocalStorageValue(key, value);
// Also store in electronStore if available
if (globalThis.electronStore) {
globalThis.electronStore.setItem(key, value).catch((error) => {
captureError(
ErrorType.SYSTEM,
`Error writing to electron-store: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "write", target: "electron-store" },
);
});
}
} catch (error) {
captureError(
ErrorType.STORAGE,
`Error writing to storage key: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "write", valueSize: value.length },
);
}
},
/**
* Removes a value from all storage layers.
* @param key - Storage key to remove.
* @source
*/
removeItem: (key: string): void => {
try {
console.debug(`[Storage] 🔍 Removing item: ${key}`);
// Remove from cache
delete storageCache[key];
// Remove from localStorage for compatibility
removeLocalStorageValue(key);
// Also remove from electronStore if available
if (globalThis.electronStore) {
globalThis.electronStore.removeItem(key).catch((error) => {
captureError(
ErrorType.SYSTEM,
`Error deleting from electron-store: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "delete", target: "electron-store" },
);
});
}
} catch (error) {
captureError(
ErrorType.STORAGE,
`Error deleting storage key: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "delete" },
);
}
},
/**
* Clears all storage layers.
* @source
*/
clear: (): void => {
try {
console.info("[Storage] 🗑️ Clearing all storage...");
// Clear cache
const keyCount = Object.keys(storageCache).length;
for (const key of Object.keys(storageCache)) {
delete storageCache[key];
}
// Clear localStorage for compatibility
clearLocalStorageValues();
// Also clear electronStore if available
if (globalThis.electronStore) {
globalThis.electronStore.clear().catch((error) => {
console.warn("[Storage] ❌ Error clearing electron-store:", error);
});
}
console.info(`[Storage] ✅ Cleared ${keyCount} keys from storage`);
} catch (error) {
captureError(
ErrorType.STORAGE,
"Error clearing all storage layers",
error instanceof Error ? error : new Error(String(error)),
{ operation: "clear" },
);
}
},
/**
* Stores a value to electron-store (authoritative), then syncs to localStorage.
* @param key - Storage key.
* @param value - Value to store.
* @returns Promise that resolves when complete.
* @source
*/
setItemAsync: async (key: string, value: string): Promise<void> => {
if (!globalThis.electronStore) {
// Fallback to sync method if no electron store
console.debug(
`[Storage] 🔍 No electron-store available, using sync setItem for ${key}`,
);
storage.setItem(key, value);
return;
}
try {
console.debug(
`[Storage] 🔍 Async setting item: ${key} (${value.length} bytes)`,
);
// Write to electron-store first (authoritative source)
await globalThis.electronStore.setItem(key, value);
// Update cache
storageCache[key] = value;
// Sync to localStorage
writeLocalStorageValue(key, value);
console.debug(`[Storage] ✅ Async set complete: ${key}`);
} catch (error) {
captureError(
ErrorType.STORAGE,
`Error async setting item: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "write-async", valueSize: value.length },
);
throw error;
}
},
/**
* Retrieves a value, preferring electron-store (authoritative) over localStorage.
* @param key - Storage key.
* @returns Promise resolving to the stored value or null.
* @source
*/
getItemAsync: async (key: string): Promise<string | null> => {
if (globalThis.electronStore) {
try {
console.debug(`[Storage] 🔍 Async getting item: ${key}`);
const value = await globalThis.electronStore.getItem(key);
if (value === null) {
console.debug(`[Storage] 🔍 Item not found: ${key}`);
} else {
console.debug(
`[Storage] ✅ Found item: ${key} (${value.length} bytes)`,
);
writeLocalStorageValue(key, value); // keep localStorage in sync
storageCache[key] = value;
}
return value;
} catch (error) {
captureError(
ErrorType.SYSTEM,
`Error reading from electron-store: ${key}`,
error instanceof Error ? error : new Error(String(error)),
{ key, operation: "read-async", target: "electron-store" },
);
console.debug(`[Storage] 🔍 Falling back to localStorage for: ${key}`);
// fallback to localStorage
return readLocalStorageValue(key);
}
}
// fallback if no electronStore
console.debug(
`[Storage] 🔍 No electron-store available, using localStorage for ${key}`,
);
return readLocalStorageValue(key);
},
};
Unified storage abstraction across cache, localStorage, and electron-store.