@profullstack/state-manager
Version:
Enhanced state manager with web component integration, persistence, and subscription management
389 lines (339 loc) • 9.64 kB
JavaScript
/**
* Persistence Manager for State Manager
*
* Provides functionality for persisting state to various storage mechanisms.
*/
/**
* Create a persistence manager
* @param {Object} options - Configuration options
* @param {boolean} options.enabled - Whether persistence is enabled
* @param {string} options.key - Key for storage
* @param {Object} options.adapter - Storage adapter (default: localStorage)
* @param {string[]} options.persistentKeys - Keys to persist (default: all)
* @returns {Object} Persistence manager
*/
export function createPersistenceManager(options = {}) {
// Default options
const config = {
enabled: false,
key: 'app_state',
adapter: null,
persistentKeys: null,
...options
};
// Use localStorage as default adapter if available
if (!config.adapter && typeof localStorage !== 'undefined') {
config.adapter = createLocalStorageAdapter();
} else if (!config.adapter && typeof sessionStorage !== 'undefined') {
config.adapter = createSessionStorageAdapter();
} else if (!config.adapter) {
config.adapter = createMemoryAdapter();
}
/**
* Save state to storage
* @param {Object} state - State to save
* @returns {boolean} Whether the save was successful
*/
function save(state) {
if (!config.enabled) {
return false;
}
try {
// If persistentKeys is specified, only save those keys
let stateToSave = state;
if (Array.isArray(config.persistentKeys) && config.persistentKeys.length > 0) {
stateToSave = {};
for (const key of config.persistentKeys) {
if (typeof key === 'string' && key.includes('.')) {
// Handle nested keys
const parts = key.split('.');
let currentState = state;
let currentSaveState = stateToSave;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (currentState[part] === undefined) {
break;
}
if (currentSaveState[part] === undefined) {
currentSaveState[part] = {};
}
currentState = currentState[part];
currentSaveState = currentSaveState[part];
}
const lastPart = parts[parts.length - 1];
if (currentState[lastPart] !== undefined) {
currentSaveState[lastPart] = currentState[lastPart];
}
} else if (state[key] !== undefined) {
// Handle top-level keys
stateToSave[key] = state[key];
}
}
}
config.adapter.setItem(config.key, JSON.stringify(stateToSave));
return true;
} catch (error) {
console.error('Error saving state to storage:', error);
return false;
}
}
/**
* Load state from storage
* @returns {Object|null} Loaded state or null if not found
*/
function load() {
if (!config.enabled) {
return null;
}
try {
const savedState = config.adapter.getItem(config.key);
if (!savedState) {
return null;
}
return JSON.parse(savedState);
} catch (error) {
console.error('Error loading state from storage:', error);
return null;
}
}
/**
* Clear persisted state
* @returns {boolean} Whether the clear was successful
*/
function clear() {
if (!config.enabled) {
return false;
}
try {
config.adapter.removeItem(config.key);
return true;
} catch (error) {
console.error('Error clearing state from storage:', error);
return false;
}
}
/**
* Check if state exists in storage
* @returns {boolean} Whether state exists
*/
function exists() {
if (!config.enabled) {
return false;
}
try {
return config.adapter.getItem(config.key) !== null;
} catch (error) {
console.error('Error checking if state exists in storage:', error);
return false;
}
}
/**
* Get the persistence key
* @returns {string} Persistence key
*/
function getKey() {
return config.key;
}
/**
* Set the persistence key
* @param {string} key - New persistence key
*/
function setKey(key) {
config.key = key;
}
/**
* Enable or disable persistence
* @param {boolean} enabled - Whether persistence is enabled
*/
function setEnabled(enabled) {
config.enabled = enabled;
}
/**
* Check if persistence is enabled
* @returns {boolean} Whether persistence is enabled
*/
function isEnabled() {
return config.enabled;
}
/**
* Set the persistent keys
* @param {string[]} keys - Keys to persist
*/
function setPersistentKeys(keys) {
config.persistentKeys = Array.isArray(keys) ? keys : null;
}
/**
* Get the persistent keys
* @returns {string[]|null} Persistent keys
*/
function getPersistentKeys() {
return config.persistentKeys;
}
/**
* Set the storage adapter
* @param {Object} adapter - Storage adapter
*/
function setAdapter(adapter) {
config.adapter = adapter;
}
/**
* Get the storage adapter
* @returns {Object} Storage adapter
*/
function getAdapter() {
return config.adapter;
}
// Return the persistence manager
return {
save,
load,
clear,
exists,
getKey,
setKey,
setEnabled,
isEnabled,
setPersistentKeys,
getPersistentKeys,
setAdapter,
getAdapter
};
}
/**
* Create a localStorage adapter
* @returns {Object} localStorage adapter
*/
export function createLocalStorageAdapter() {
return {
getItem(key) {
return localStorage.getItem(key);
},
setItem(key, value) {
localStorage.setItem(key, value);
},
removeItem(key) {
localStorage.removeItem(key);
}
};
}
/**
* Create a sessionStorage adapter
* @returns {Object} sessionStorage adapter
*/
export function createSessionStorageAdapter() {
return {
getItem(key) {
return sessionStorage.getItem(key);
},
setItem(key, value) {
sessionStorage.setItem(key, value);
},
removeItem(key) {
sessionStorage.removeItem(key);
}
};
}
/**
* Create an in-memory adapter
* @returns {Object} In-memory adapter
*/
export function createMemoryAdapter() {
const storage = new Map();
return {
getItem(key) {
return storage.has(key) ? storage.get(key) : null;
},
setItem(key, value) {
storage.set(key, value);
},
removeItem(key) {
storage.delete(key);
}
};
}
/**
* Create an IndexedDB adapter
* @param {string} dbName - Database name
* @param {string} storeName - Store name
* @returns {Object} IndexedDB adapter
*/
export function createIndexedDBAdapter(dbName = 'stateManagerDB', storeName = 'state') {
let db = null;
// Initialize the database
const initPromise = new Promise((resolve, reject) => {
if (typeof window !== "undefined" && !window.indexedDB) {
reject(new Error('IndexedDB is not supported in this browser'));
return;
}
const request = window.indexedDB.open(dbName, 1);
request.onerror = (event) => {
reject(new Error('Error opening IndexedDB'));
};
request.onsuccess = (event) => {
db = event.target.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore(storeName);
};
});
return {
async getItem(key) {
try {
await initPromise;
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const request = store.get(key);
request.onsuccess = (event) => {
resolve(request.result);
};
request.onerror = (event) => {
reject(new Error('Error getting item from IndexedDB'));
};
});
} catch (error) {
console.error('Error getting item from IndexedDB:', error);
return null;
}
},
async setItem(key, value) {
try {
await initPromise;
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.put(value, key);
request.onsuccess = (event) => {
resolve();
};
request.onerror = (event) => {
reject(new Error('Error setting item in IndexedDB'));
};
});
} catch (error) {
console.error('Error setting item in IndexedDB:', error);
}
},
async removeItem(key) {
try {
await initPromise;
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.delete(key);
request.onsuccess = (event) => {
resolve();
};
request.onerror = (event) => {
reject(new Error('Error removing item from IndexedDB'));
};
});
} catch (error) {
console.error('Error removing item from IndexedDB:', error);
}
}
};
}
export default createPersistenceManager;