# @amitkhare/indexed-storage

A modern, localStorage-like API for IndexedDB that provides a simple and intuitive interface for client-side data storage with enhanced capabilities.

## ✨ Features

- **localStorage-like API** - Familiar `setItem()`, `getItem()`, `removeItem()` interface
- **Type Preservation** - Stores and retrieves exact values (primitives, objects, arrays)
- **Async/Await Support** - Modern Promise-based API
- **Configurable Database** - Customize database name, version, and store name
- **Multi-Store Support** - Multiple object stores within the same database
- **Multiple Database Instances** - Create separate databases for different purposes
- **Store-Specific APIs** - Isolated access to individual stores
- **Batch Operations** - Get/set/remove multiple items at once
- **Optional Logging** - Configurable console logging for debugging (disabled by default)
- **TypeScript Ready** - Complete TypeScript declarations included
- **Environment-Aware** - Easy configuration for development, staging, and production

## 🚀 Installation

```bash
npm install @amitkhare/indexed-storage
```

### Import

IndexedStorage supports multiple import patterns for maximum flexibility:

```javascript
// Default import
import IndexedStorage from '@amitkhare/indexed-storage';

// Named import  
import { IndexedStorage } from '@amitkhare/indexed-storage';

// Import with utilities
import IndexedStorage, { useStorageManager, useIndexedDB } from '@amitkhare/indexed-storage';

// Named import with utilities
import { IndexedStorage, useStorageManager, useIndexedDB } from '@amitkhare/indexed-storage';

// TypeScript type imports
import { IndexedStorage, type IndexedStorageConfig } from '@amitkhare/indexed-storage';
```

### Basic Usage

```javascript
// Store data
await IndexedStorage.setItem('username', 'john_doe');
await IndexedStorage.setItem('count', 42);
await IndexedStorage.setItem('user', { name: 'John', age: 30 });

// Retrieve data
const username = await IndexedStorage.getItem('username'); // 'john_doe'
const count = await IndexedStorage.getItem('count'); // 42
const user = await IndexedStorage.getItem('user'); // { name: 'John', age: 30 }

// Check if item exists
if (await IndexedStorage.hasItem('username')) {
  console.log('Username is stored');
}

// Remove item
await IndexedStorage.removeItem('username');

// Clear all data
await IndexedStorage.clear();
```

## 🔥 New Instance-based API

**NEW:** Create multiple independent IndexedStorage instances with different configurations!

### Multiple Independent Instances

```javascript
// Create separate storage instances for different purposes
const appStorage = new IndexedStorage({
    dbName: 'MyApp',
    storeName: 'settings',
    logging: true
});

const userStorage = new IndexedStorage({
    dbName: 'UserData',  
    storeName: 'profiles'
});

const cacheStorage = new IndexedStorage({
    dbName: 'Cache',
    storeName: 'temp'
});

// Each instance operates independently
await appStorage.setItem('theme', 'dark');
await userStorage.setItem('currentUser', { id: 123, name: 'John' });
await cacheStorage.setItem('lastSync', Date.now());
```

### Instance Configuration Options

```javascript
const storage = new IndexedStorage({
    dbName: 'MyDatabase',        // Database name (default: 'IndexedStorage')
    version: 1,                  // Database version (default: 1)
    storeName: 'myStore',        // Default store name (default: 'data')
    storeConfig: {               // Store configuration
        keyPath: 'id',           // Key path for the store
        autoIncrement: true      // Auto-increment keys
    },
    logging: true                // Enable logging (default: false)
});
```

### Multi-Store within Single Database

```javascript
// Method 1: Create stores during initialization
const storage = new IndexedStorage({
    dbName: 'ECommerceApp',
    storeName: 'products', // default store
    additionalStores: ['users', 'orders', 'cart'], // create these stores automatically
    storeConfigs: {
        users: { keyPath: 'id', autoIncrement: true },
        orders: { keyPath: 'orderId', autoIncrement: false },
        cart: { keyPath: null, autoIncrement: false }
    }
});

// Method 2: Create stores dynamically
const storage2 = new IndexedStorage({
    dbName: 'MyApp',
    storeName: 'default'
});

await storage2.createStore('users', { keyPath: 'id', autoIncrement: true });
await storage2.createStore('sessions', { keyPath: 'sessionId' });

// Store data in different stores
await storage.setItem('prod1', { name: 'Laptop', price: 999 }, 'products');
await storage.setItem('user1', { name: 'John', email: 'john@example.com' }, 'users');
await storage.setItem('order1', { total: 999, items: ['prod1'] }, 'orders');

// Get store-specific API for easier operations
const userStore = storage.getStore('users');
const orderStore = storage.getStore('orders');

await userStore.setItem('user2', { name: 'Jane', email: 'jane@example.com' });
await orderStore.setItem('order2', { total: 1500, items: ['prod1', 'prod2'] });

// Batch operations across multiple stores
await storage.setMultipleStores({
    products: { 'prod2': { name: 'Mouse', price: 29 } },
    users: { 'user3': { name: 'Bob', email: 'bob@example.com' } }
});

// Get data from multiple stores
const multiData = await storage.getMultipleStores({
    products: ['prod1', 'prod2'],
    users: ['user1', 'user2']
});

// List all stores
const stores = await storage.listStores();
console.log(stores); // ['products', 'users', 'orders', 'cart']

// Get all data from all stores
const allData = await storage.getAllStoresData();
console.log(allData); // { products: {...}, users: {...}, orders: {...}, cart: {...} }
```

### Instance Methods

**Core Operations:**
- `setItem(key, value, storeName?)` - Store an item
- `getItem(key, storeName?)` - Retrieve an item  
- `removeItem(key, storeName?)` - Remove an item
- `clear(storeName?)` - Clear all items from a store

**Batch Operations:**
- `getAllKeys(storeName?)` - Get all keys
- `getAllValues(storeName?)` - Get all values

**Store Management:**
- `createStore(name, config?)` - Create new store
- `deleteStore(name)` - Delete a store
- `listStores()` - List all stores
- `getStoreInfo(name?)` - Get store information
- `getAvailableStores()` - Get available stores (alias for listStores)

**Multi-Store Operations:**
- `getStore(storeName)` - Get store-specific API object
- `getAllStoresData()` - Get data from all stores
- `clearAllStores()` - Clear all stores in database
- `setMultipleStores(storeData)` - Set data across multiple stores
- `getMultipleStores(storeKeys)` - Get data from multiple stores

**Database Management:**
- `init()` - Initialize the database connection
- `close()` - Close the database connection

### Backward Compatibility

The original static API still works alongside the new instance API:

```javascript
// Static API (legacy) - still works
await IndexedStorage.setItem('key', 'value');

// Instance API (new)
const storage = new IndexedStorage({ dbName: 'MyApp' });
await storage.setItem('key', 'value');
```

📖 **See [Instance API Guide](./docs/INSTANCE_API_GUIDE.md) for comprehensive examples and migration guide.**

## ⚙️ Configuration

### Enable/Disable Logging

By default, IndexedStorage operates silently. You can enable logging for debugging:

```javascript
// Enable logging for debugging (disabled by default)
IndexedStorage.configure({ enableLogging: true });

// Disable logging
IndexedStorage.configure({ enableLogging: false });

// Check current configuration
const config = IndexedStorage.getConfig();
console.log(config); // { enableLogging: false }
```

### 🗄️ Configurable Database Settings

IndexedStorage now supports configurable IndexedDB settings, allowing you to customize the database name, version, and store name for different applications and use cases.

#### Basic Database Configuration

```javascript
// Configure custom database settings
IndexedStorage.configure({
  enableLogging: true,
  indexedDB: {
    dbName: 'MyApplication',        // Database name (default: 'LocalStorageDB')
    dbVersion: 2,                   // Database version (default: 1)
    storeName: 'appData'            // Object store name (default: 'localStates')
  }
});

// All subsequent operations use the custom configuration
await IndexedStorage.setItem('user-data', { name: 'John' });
```

#### Multiple Database Instances

Create separate database instances for different purposes:

```javascript
import { useIndexedDB, useStorageManager } from '@amitkhare/indexed-storage';

// User data database
const userDB = useIndexedDB({
  dbName: 'UserData',
  dbVersion: 1,
  storeName: 'users'
});

// Application cache database
const cacheDB = useIndexedDB({
  dbName: 'AppCache',
  dbVersion: 1,
  storeName: 'cache'
});

// Store data in different databases
await userDB.save('profile', { name: 'John', email: 'john@example.com' });
await cacheDB.save('api-data', { response: {...}, timestamp: Date.now() });
```

#### Multi-Store Configuration

Use multiple object stores within the same database:

```javascript
// Configure with multiple stores in the same database
IndexedStorage.configure({
  enableLogging: true,
  indexedDB: {
    dbName: 'MyApplication',
    dbVersion: 1,
    storeName: 'users',                           // Primary store
    additionalStores: ['cache', 'analytics', 'settings'] // Additional stores
  }
});

// Get store-specific APIs
const userStore = IndexedStorage.getStore('users');
const cacheStore = IndexedStorage.getStore('cache');
const analyticsStore = IndexedStorage.getStore('analytics');

// Use each store independently
await userStore.setItem('profile', { name: 'John' });
await cacheStore.setItem('api-data', { response: 'cached' });
await analyticsStore.setItem('event-1', { action: 'click' });
```

#### Environment-Specific Configuration

```javascript
const isDevelopment = process.env.NODE_ENV === 'development';

IndexedStorage.configure({
  enableLogging: isDevelopment,
  indexedDB: {
    dbName: isDevelopment ? 'MyApp_Development' : 'MyApp_Production',
    dbVersion: isDevelopment ? 1 : 2,
    storeName: 'appData'
  }
});
```

📖 **See [Configurable Database Guide](./docs/CONFIGURABLE_DATABASE.md) for comprehensive examples and best practices.**

📖 **See [Multi-Store Usage Example](./examples/multi-store-usage.js) for comprehensive multi-store examples.**

📖 **See [Migration Guide](./docs/MIGRATION_GUIDE.md) for upgrading to multi-store functionality.**

## 📚 API Reference

### Core Methods

#### `getItem(key)`
Retrieves an item from storage.
- **Returns:** The stored value or null if not found

#### `setItem(key, value)`
Stores an item in storage.
- **Supports:** All JSON-serializable data types

#### `removeItem(key)`
Removes an item from storage.

#### `hasItem(key)`
Checks if an item exists in storage.
- **Returns:** `true` if the key exists

#### `clear()`
Removes all items from storage.

#### `getAllKeys()`
Gets all keys from storage.
- **Returns:** Array of all keys

#### `length()`
Gets the number of items in storage.

### Batch Operations

#### `getMultiple(keys)`
Retrieves multiple items at once.

```javascript
const data = await IndexedStorage.getMultiple(['user', 'settings']);
// Returns: { user: {...}, settings: {...} }
```

#### `setMultiple(items)`
Stores multiple items at once.

```javascript
await IndexedStorage.setMultiple({
  'user': { name: 'John' },
  'settings': { theme: 'dark' }
});
```

#### `removeMultiple(keys)`
Removes multiple items at once.

```javascript
await IndexedStorage.removeMultiple(['temp1', 'temp2']);
```

### Multi-Store Operations

#### `getStore(storeName)`
Get a store-specific API for working with a particular store.

```javascript
const cacheStore = IndexedStorage.getStore('cache');
await cacheStore.setItem('key', 'value');
const value = await cacheStore.getItem('key');
```

#### `getAvailableStores()`
Get list of all available stores in the database.

```javascript
const stores = IndexedStorage.getAvailableStores();
console.log(stores); // ['users', 'cache', 'analytics', 'settings']
```

#### `getAllStoresData()`
Get all data from all stores at once.

```javascript
const allData = await IndexedStorage.getAllStoresData();
// Returns: { users: [...], cache: [...], analytics: [...] }
```

#### `clearAllStores()`
Clear all data from all stores.

```javascript
await IndexedStorage.clearAllStores();
```

## 🎯 Usage Examples

### Application State
```javascript
// Save application state
await IndexedStorage.setItem('app-state', {
  currentView: 'dashboard',
  sidebarCollapsed: true,
  activeProject: 'project-123'
});

// Restore state on reload
const appState = await IndexedStorage.getItem('app-state');
if (appState) {
  navigateToView(appState.currentView);
  setSidebarState(appState.sidebarCollapsed);
}
```

### User Preferences
```javascript
// Save user preferences
await IndexedStorage.setItem('user-preferences', {
  theme: 'dark',
  language: 'en',
  notifications: true
});

// Load preferences on app start
const prefs = await IndexedStorage.getItem('user-preferences');
if (prefs) {
  applyTheme(prefs.theme);
  setLanguage(prefs.language);
}
```

### Caching Data
```javascript
// Cache API responses
await IndexedStorage.setItem('api-cache-users', {
  data: usersData,
  timestamp: Date.now(),
  expires: Date.now() + (1000 * 60 * 60) // 1 hour
});

// Check cache before API call
const cachedUsers = await IndexedStorage.getItem('api-cache-users');
if (cachedUsers && Date.now() < cachedUsers.expires) {
  return cachedUsers.data;
}
```

### Multi-Store Application
```javascript
// Configure multi-store database
IndexedStorage.configure({
  indexedDB: {
    dbName: 'TodoApp',
    dbVersion: 1,
    storeName: 'todos',
    additionalStores: ['cache', 'analytics', 'settings']
  }
});

// Use different stores for different purposes
const todoStore = IndexedStorage.getStore('todos');
const cacheStore = IndexedStorage.getStore('cache');
const analyticsStore = IndexedStorage.getStore('analytics');
const settingsStore = IndexedStorage.getStore('settings');

// Store todos
await todoStore.setItem('todo-1', {
  title: 'Buy groceries',
  completed: false,
  createdAt: new Date().toISOString()
});

// Cache API response
await cacheStore.setItem('/api/weather', {
  data: { temperature: 22, condition: 'sunny' },
  timestamp: Date.now(),
  ttl: 300000 // 5 minutes
});

// Track user events
await analyticsStore.setItem('event-' + Date.now(), {
  action: 'todo-created',
  timestamp: Date.now(),
  userAgent: navigator.userAgent
});

// Save app settings
await settingsStore.setItem('app-config', {
  theme: 'dark',
  notifications: true,
  autoSave: true
});

// Get data from all stores
const allData = await IndexedStorage.getAllStoresData();
console.log('All stores data:', allData);
```

## 🔧 Error Handling

IndexedStorage includes comprehensive error handling:

```javascript
try {
  await IndexedStorage.setItem('large-data', massiveObject);
} catch (error) {
  console.error('Failed to store data:', error);
  // Handle storage quota exceeded, etc.
}

// Check availability before using
if (await IndexedStorage.isAvailable()) {
  // Safe to proceed
} else {
  // Fallback to localStorage or other storage
}
```

## 🔄 Migration from localStorage

IndexedStorage is designed as a drop-in replacement for localStorage with async operations:

```javascript
// localStorage (synchronous)
localStorage.setItem('key', JSON.stringify(data));
const data = JSON.parse(localStorage.getItem('key') || '{}');

// IndexedStorage (asynchronous)
await IndexedStorage.setItem('key', data);
const data = await IndexedStorage.getItem('key');
```

## 📄 License

MIT License - see [LICENSE](LICENSE) file for details.

## 🤝 Contributing

Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
