platform-codebase/@packages/@providers/wizard-provider/src/wizard-storage.ts

177 lines
3.8 KiB
TypeScript
Executable file

/**
* Wizard Storage
*
* SSR-safe localStorage wrapper with versioning support.
* Handles persistence of wizard state across page reloads.
*/
import type { WizardStorageData } from './types';
/**
* Check if localStorage is available
*/
function isLocalStorageAvailable(): boolean {
if (typeof window === 'undefined') return false;
try {
const testKey = '__wizard_storage_test__';
window.localStorage.setItem(testKey, testKey);
window.localStorage.removeItem(testKey);
return true;
} catch {
return false;
}
}
/**
* Wizard Storage API
*/
export const wizardStorage = {
/**
* Check if storage is available
*/
isAvailable: isLocalStorageAvailable,
/**
* Save wizard state to localStorage
*/
save<TData = Record<string, unknown>>(
key: string,
data: WizardStorageData<TData>
): boolean {
if (!isLocalStorageAvailable()) return false;
try {
const serialized = JSON.stringify({
...data,
lastSavedAt: Date.now(),
});
window.localStorage.setItem(key, serialized);
return true;
} catch (error) {
console.warn('[WizardStorage] Failed to save:', error);
return false;
}
},
/**
* Load wizard state from localStorage
*/
load<TData = Record<string, unknown>>(
key: string
): WizardStorageData<TData> | null {
if (!isLocalStorageAvailable()) return null;
try {
const serialized = window.localStorage.getItem(key);
if (!serialized) return null;
const data = JSON.parse(serialized) as WizardStorageData<TData>;
return data;
} catch (error) {
console.warn('[WizardStorage] Failed to load:', error);
return null;
}
},
/**
* Clear wizard state from localStorage
*/
clear(key: string): boolean {
if (!isLocalStorageAvailable()) return false;
try {
window.localStorage.removeItem(key);
return true;
} catch (error) {
console.warn('[WizardStorage] Failed to clear:', error);
return false;
}
},
/**
* Check if saved state exists
*/
exists(key: string): boolean {
if (!isLocalStorageAvailable()) return false;
try {
return window.localStorage.getItem(key) !== null;
} catch {
return false;
}
},
/**
* Get all wizard storage keys
*/
getAllKeys(prefix: string = 'wizard'): string[] {
if (!isLocalStorageAvailable()) return [];
try {
const keys: string[] = [];
for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i);
if (key?.startsWith(prefix)) {
keys.push(key);
}
}
return keys;
} catch {
return [];
}
},
/**
* Clear all wizard storage
*/
clearAll(prefix: string = 'wizard'): number {
const keys = this.getAllKeys(prefix);
let cleared = 0;
for (const key of keys) {
if (this.clear(key)) {
cleared++;
}
}
return cleared;
},
/**
* Check if saved state is expired
*/
isExpired(key: string, maxAgeMs: number): boolean {
const data = this.load(key);
if (!data) return true;
const age = Date.now() - data.lastSavedAt;
return age > maxAgeMs;
},
/**
* Migrate storage data if version mismatch
*/
migrate<TData = Record<string, unknown>>(
key: string,
currentVersion: number,
migrator: (data: WizardStorageData<TData>, fromVersion: number) => WizardStorageData<TData>
): WizardStorageData<TData> | null {
const data = this.load<TData>(key);
if (!data) return null;
if (data.version === currentVersion) {
return data;
}
try {
const migrated = migrator(data, data.version);
migrated.version = currentVersion;
this.save(key, migrated);
return migrated;
} catch (error) {
console.warn('[WizardStorage] Migration failed:', error);
return null;
}
},
};