UNPKG

@amitkhare/indexed-storage

Version:

A modern, localStorage-like API for IndexedDB with instance-based and static APIs, multi-store support, configurable databases, and enhanced capabilities

331 lines (297 loc) โ€ข 10.6 kB
// IndexedDB utility composable for state management // Import shared logging function import { log } from './config.js'; /** * IndexedDB composable with configurable database settings * @param {Object} config - Configuration options * @param {string} config.dbName - Database name (default: 'LocalStorageDB') * @param {number} config.dbVersion - Database version (default: 1) * @param {string} config.storeName - Object store name (default: 'localStates') * @param {string[]} config.additionalStores - Additional store names to create (optional) * @returns {Object} IndexedDB operations */ export function useIndexedDB(config = {}) { // Database configuration with defaults const DB_NAME = config.dbName || 'LocalStorageDB'; const DB_VERSION = config.dbVersion || 1; const STORE_NAME = config.storeName || 'localStates'; const ADDITIONAL_STORES = config.additionalStores || []; // All store names (primary + additional) const ALL_STORES = [STORE_NAME, ...ADDITIONAL_STORES]; /** * Opens IndexedDB connection with proper error handling * @returns {Promise<IDBDatabase>} Database instance */ function openDB() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = () => { log('error', '๐Ÿ—„๏ธโŒ IndexedDB open error:', request.error); reject(request.error); }; request.onsuccess = () => { log('log', '๐Ÿ—„๏ธโœ… IndexedDB opened successfully'); resolve(request.result); }; request.onupgradeneeded = (event) => { log('log', '๐Ÿ—„๏ธ๐Ÿ”ง IndexedDB upgrade needed, creating object stores...'); const db = event.target.result; // Create all required stores ALL_STORES.forEach(storeName => { if (!db.objectStoreNames.contains(storeName)) { const store = db.createObjectStore(storeName, { keyPath: 'id' }); // Create indexes for better querying store.createIndex('timestamp', 'timestamp', { unique: false }); store.createIndex('version', 'version', { unique: false }); log('log', `๐Ÿ—„๏ธ๐Ÿ—๏ธ Created object store '${storeName}' with indexes`); } }); }; }); } /** * Retrieves data from IndexedDB by key * @param {string} key - The key to retrieve * @param {string} storeName - Optional store name (defaults to primary store) * @returns {Promise<Object|null>} Retrieved data or null if not found */ async function get(key, storeName = STORE_NAME) { try { const db = await openDB(); const transaction = db.transaction([storeName], 'readonly'); const store = transaction.objectStore(storeName); const request = store.get(key); return new Promise((resolve, reject) => { request.onsuccess = () => { const result = request.result; resolve(result || null); }; request.onerror = () => reject(request.error); }); } catch (error) { log('error', '๐Ÿ—„๏ธโŒ Error reading from IndexedDB:', error); return null; } } /** * Saves data to IndexedDB * @param {string} key - The key to save under * @param {Object} data - The data to save * @param {string} storeName - Optional store name (defaults to primary store) * @returns {Promise<void>} */ async function save(key, data, storeName = STORE_NAME) { try { const db = await openDB(); const transaction = db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const dataToSave = { id: key, ...data, timestamp: data.timestamp || new Date().toISOString(), version: data.version || '1.0' }; const request = store.put(dataToSave); return new Promise((resolve, reject) => { request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } catch (error) { log('error', '๐Ÿ—„๏ธโŒ Error saving to IndexedDB:', error); throw error; } } /** * Removes data from IndexedDB by key * @param {string} key - The key to remove * @param {string} storeName - Optional store name (defaults to primary store) * @returns {Promise<void>} */ async function remove(key, storeName = STORE_NAME) { try { const db = await openDB(); const transaction = db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const request = store.delete(key); return new Promise((resolve, reject) => { request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } catch (error) { log('error', '๐Ÿ—„๏ธโŒ Error deleting from IndexedDB:', error); throw error; } } /** * Gets all data from IndexedDB * @param {string} storeName - Optional store name (defaults to primary store) * @returns {Promise<Array>} Array of all stored items */ async function getAll(storeName = STORE_NAME) { try { const db = await openDB(); const transaction = db.transaction([storeName], 'readonly'); const store = transaction.objectStore(storeName); const request = store.getAll(); return new Promise((resolve, reject) => { request.onsuccess = () => resolve(request.result || []); request.onerror = () => reject(request.error); }); } catch (error) { log('error', '๐Ÿ—„๏ธโŒ Error getting all data from IndexedDB:', error); return []; } } /** * Checks if a key exists in IndexedDB * @param {string} key - The key to check * @param {string} storeName - Optional store name (defaults to primary store) * @returns {Promise<boolean>} True if key exists */ async function exists(key, storeName = STORE_NAME) { try { const data = await get(key, storeName); return data !== null; } catch (error) { log('error', '๐Ÿ—„๏ธโŒ Error checking existence in IndexedDB:', error); return false; } } /** * Clears all data from IndexedDB * @param {string} storeName - Optional store name (defaults to primary store) * @returns {Promise<void>} */ async function clear(storeName = STORE_NAME) { try { const db = await openDB(); const transaction = db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const request = store.clear(); return new Promise((resolve, reject) => { request.onsuccess = () => { log('log', `๐Ÿ—„๏ธ๐Ÿงน IndexedDB store '${storeName}' cleared successfully`); resolve(); }; request.onerror = () => reject(request.error); }); } catch (error) { log('error', '๐Ÿ—„๏ธโŒ Error clearing IndexedDB:', error); throw error; } } /** * Calculates storage usage for all items in IndexedDB * @param {string} storeName - Optional store name (defaults to primary store) * @returns {Promise<Object>} Usage information */ async function calculateUsage(storeName = STORE_NAME) { try { const allData = await getAll(storeName); let totalSize = 0; const items = {}; allData.forEach(item => { const size = new Blob([JSON.stringify(item)]).size; totalSize += size; items[item.id] = size; }); return { totalSize, items }; } catch (error) { log('error', '๐Ÿ—„๏ธโŒ Error calculating IndexedDB usage:', error); return { totalSize: 0, items: {} }; } } /** * Tests if IndexedDB is available and working * @returns {Promise<boolean>} True if available */ async function testAvailability() { try { const testKey = 'indexeddb-test'; const testData = { test: true, timestamp: new Date().toISOString() }; await save(testKey, testData); const retrieved = await get(testKey); await remove(testKey); return retrieved !== null && retrieved.test === true; } catch (error) { log('error', '๐Ÿ—„๏ธโŒ IndexedDB availability test failed:', error); return false; } } /** * Get list of all available store names in the database * @returns {string[]} Array of store names */ function getAvailableStores() { return ALL_STORES; } /** * Create a store-specific API that operates only on a particular store * @param {string} storeName - The store name to create API for * @returns {Object} Store-specific API */ function createStoreAPI(storeName) { if (!ALL_STORES.includes(storeName)) { throw new Error(`Store '${storeName}' is not configured. Available stores: ${ALL_STORES.join(', ')}`); } return { get: (key) => get(key, storeName), save: (key, data) => save(key, data, storeName), remove: (key) => remove(key, storeName), getAll: () => getAll(storeName), exists: (key) => exists(key, storeName), clear: () => clear(storeName), calculateUsage: () => calculateUsage(storeName), storeName }; } /** * Clear all stores in the database * @returns {Promise<void>} */ async function clearAllStores() { try { for (const storeName of ALL_STORES) { await clear(storeName); } log('log', '๐Ÿ—„๏ธ๐Ÿงน All stores cleared successfully'); } catch (error) { log('error', '๐Ÿ—„๏ธโŒ Error clearing all stores:', error); throw error; } } /** * Get data from all stores * @returns {Promise<Object>} Object with store names as keys and their data as values */ async function getAllStoresData() { try { const result = {}; for (const storeName of ALL_STORES) { result[storeName] = await getAll(storeName); } return result; } catch (error) { log('error', '๐Ÿ—„๏ธโŒ Error getting data from all stores:', error); return {}; } } return { // Core operations (work with primary store or specified store) get, save, remove, getAll, exists, clear, // Database management openDB, // Utility functions calculateUsage, testAvailability, // Multi-store operations getAvailableStores, createStoreAPI, clearAllStores, getAllStoresData, // Configuration info dbName: DB_NAME, dbVersion: DB_VERSION, primaryStore: STORE_NAME, allStores: ALL_STORES }; }