Type-safe localStorage wrapper with LRU eviction and automatic quota management.
import { StorageManager } from '@zappzarapp/browser-utils/storage';
interface UserPrefs {
theme: 'light' | 'dark';
language: string;
}
const storage = StorageManager.create<UserPrefs>({ prefix: 'myApp' });
storage.set('prefs', { theme: 'dark', language: 'en' });
const prefs = storage.get('prefs');| Class / Type | Description |
|---|---|
StorageManager |
Type-safe localStorage wrapper with LRU eviction |
StorageConfig |
Immutable configuration for storage managers |
BaseStorageManager |
Abstract base class for storage implementations |
MemoryStorage |
In-memory fallback when localStorage is unavailable |
StorageStats |
Statistics about storage state |
StorageConfigOptions |
Options for creating StorageConfig |
| Option | Type | Default | Description |
|---|---|---|---|
prefix |
string |
'storage' |
Key prefix for all entries (alphanumeric) |
maxEntries |
number |
50 |
Maximum entries before LRU eviction (1-10000) |
minSafeEntries |
number |
5 |
Minimum entries to keep during emergency eviction |
logger |
Logger | LoggerConfigOptions |
Silent | Logger for debug output |
useMemoryFallback |
boolean |
true |
Use memory storage when localStorage unavailable |
serializer |
(value: unknown) => string |
JSON.stringify |
Custom function to serialize values |
deserializer |
(raw: string) => unknown |
JSON.parse |
Custom function to deserialize values |
// Default configuration
const storage = StorageManager.create<MyData>();
// Custom configuration
const customStorage = StorageManager.create<MyData>({
prefix: 'myApp',
maxEntries: 100,
useMemoryFallback: true,
});// Development mode with debug logging
const storage = StorageManager.withDebugLogging<MyData>('myApp');// Create from existing config
const config = StorageConfig.create({ prefix: 'myApp' });
const storage = StorageManager.fromConfig<MyData>(config);const storage = StorageManager.create<UserData>({ prefix: 'app' });
// Set value
storage.set('user', { id: 1, name: 'Alice' });
// Get value
const user = storage.get('user'); // UserData | null
// Check existence
if (storage.has('user')) {
// ...
}
// Remove value
storage.remove('user');
// Get all keys
const keys = storage.keys(); // string[]
// Clear all entries
storage.clear();import { Result } from '@zappzarapp/browser-utils/core';
import { StorageManager } from '@zappzarapp/browser-utils/storage';
const storage = StorageManager.create<MyData>({ prefix: 'app' });
const result = storage.getResult('settings');
if (Result.isOk(result)) {
console.log('Settings:', result.value);
} else {
console.error('Failed:', result.error.message);
}const stats = storage.stats();
// {
// count: 10,
// isMemoryFallback: false,
// prefix: 'app',
// maxEntries: 50
// }const config = StorageConfig.create({ prefix: 'app' })
.withMaxEntries(100)
.withMinSafeEntries(10)
.withLogger(Logger.development('[Storage]'));
const storage = StorageManager.fromConfig<MyData>(config);// Get all entries sorted by timestamp (newest first)
const entries = storage.entries();
// Array<{ key: string; value: T; timestamp: number }>By default values are serialized with JSON.stringify / JSON.parse. To
preserve non-JSON-native types (Date, Map, Set, RegExp, etc.) provide custom
serializer and deserializer functions.
const config = StorageConfig.create({
prefix: 'app',
serializer: (value) =>
JSON.stringify(value, (_key, v) =>
v instanceof Date ? { __date: v.toISOString() } : v
),
deserializer: (raw) =>
JSON.parse(raw, (_key, v) => (v?.__date ? new Date(v.__date) : v)),
});
const storage = StorageManager.fromConfig<MyData>(config);The serializer receives the full StorageEntry<T> object (with data and
timestamp). The deserializer must return a value that matches the
StorageEntry<T> shape.
StorageManager supports listening for storage changes from other tabs/windows
via the browser's storage event. This event only fires when localStorage is
modified by another browsing context with the same origin.
const storage = StorageManager.create<UserPrefs>({ prefix: 'myApp' });
// Listen for changes from other tabs
const cleanup = storage.onExternalChange((event) => {
console.log(`Key "${event.key}" changed`);
console.log('Old value:', event.oldValue);
console.log('New value:', event.newValue);
if (event.key === 'prefs' && event.newValue) {
applyPreferences(event.newValue);
}
});
// Stop listening
cleanup();| Property | Type | Description |
|---|---|---|
key |
string |
Key that changed (no prefix) |
newValue |
T | null |
New value (null if removed) |
oldValue |
T | null |
Previous value (null if new) |
Note: In memory fallback mode, onExternalChange returns a no-op cleanup
function since cross-tab sync requires native localStorage.
The storage manager automatically evicts the oldest entries when:
- Entry Limit Reached: When
maxEntriesis exceeded, oldest entries are removed - Quota Exceeded: When localStorage quota is full, entries are evicted down
to
minSafeEntries
const storage = StorageManager.create<string>({
prefix: 'cache',
maxEntries: 100, // Keep max 100 entries
minSafeEntries: 10, // In emergency, keep at least 10
});// Check if localStorage is available
if (StorageManager.isLocalStorageAvailable()) {
// Use localStorage
} else {
// Using memory fallback or disabled
}- Key Validation - Keys are validated to prevent injection attacks
- Prefix Isolation - All keys are prefixed to avoid conflicts with other applications
- No Sensitive Data - Do not store sensitive data (passwords, tokens) in localStorage
- XSS Vulnerability - localStorage is accessible to any JavaScript on the page
- Memory Fallback - In private browsing mode, data is only stored in memory