@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
JavaScript
// 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
};
}