logo
0
1
WeChat Login

indexeddb-keyvalue

A lightweight IndexedDB wrapper with dual-layer caching (Memory + IndexedDB) and HTTP request caching. Features a refactored architecture with clean separation of public and private APIs.

Features

  • Ultra Small - Only ~1KB gzipped, zero dependencies, extremely lightweight
  • Dual-Layer Caching - Memory + IndexedDB, subsequent reads return directly from memory, 100-500x performance boost
  • Single Instance Principle - Multiple CachedStorage instances with the same dbName+tableName share one underlying IndexedDB table and connection
  • localStorage-Compatible API - Drop-in replacement for localStorage with async support and object storage
  • Transparent Factory - Automatic global instance caching, no manual factory management needed
  • Automatic Version Management - No manual database upgrades needed
  • Automatic Table Creation - Tables are created automatically when used
  • Promise API - Fully asynchronous, supports async/await
  • HTTP Caching - Automatically cache fetch request results to IndexedDB
  • TypeScript Support - Complete type definitions
  • Zero Dependencies - No external dependencies

Architecture

┌─────────────────────────────────────────────────────────────┐ │ Public API (index.js) │ ├─────────────────────────────────────────────────────────────┤ │ CachedStorage CachedFetch │ │ (default export) (network caching) │ │ │ │ │ │ └──────────────────────┘ │ │ │ │ └────────────────┼────────────────────────────────────────────┘ │ (transparent factory access) ┌────────────────┼────────────────────────────────────────────┐ │ ▼ Private Implementation │ │ ┌─────────────────────┐ ┌──────────────┐ │ │ │ PrivateStorage │───>│ PrivateTiny │ │ │ │ Factory │ │ IndexDB │ │ │ │ (global caching) │ │ (low-level) │ │ │ └─────────────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘

Key architectural improvements:

  • Simplified Public API: Only CachedStorage and CachedFetch are exported
  • Transparent Factory: Users don't need to manage StorageFactory manually
  • Single Instance: Same dbName+tableName always shares one IndexedDB connection
  • Clean Separation: Private implementation classes are hidden in src/private/

Performance Comparison

OperationPure IndexedDBindexeddb-keyvalue (Memory Cache)Performance Boost
First Read~1-5ms~1-5msSame
Subsequent Reads~1-5ms~0.01ms100-500x
Write~2-8ms~2-8ms (Memory + Persistence)Reliable persistence

Based on Chrome/Edge browser testing, actual performance varies by data size and device. CachedStorage automatically caches read data to memory, making subsequent access nearly instant.

Installation

npm install indexeddb-keyvalue

Usage

CachedStorage (Recommended)

The simplest way to use it, with built-in memory caching, one line for high-performance data storage:

import { CachedStorage } from 'indexeddb-keyvalue'; // Create storage instance (with dual-layer Memory + IndexedDB caching) const storage = new CachedStorage('myDB', 'myTable'); // Save data (writes to both memory and IndexedDB) await storage.setItem('user1', { name: 'John', age: 25 }); // First read - loads from IndexedDB and caches to memory const user1 = await storage.getItem('user1'); // Second read - returns directly from memory, 100x+ faster! const user2 = await storage.getItem('user1'); // ⚡ Lightning fast, almost no delay // Check memory cache status console.log('Memory cache entries:', storage.getMemoryCacheSize()); // Delete data (removes from both memory and IndexedDB) await storage.removeItem('user1'); // Clear table (clears both memory and IndexedDB) await storage.clear(); // Get all keys (enhanced API beyond localStorage) const allKeys = await storage.keys(); console.log('All keys:', allKeys); // Get key by index (localStorage-compatible) const firstKey = await storage.key(0); console.log('First key:', firstKey); // Get total count (localStorage-compatible, async version of length) const count = await storage.length(); console.log('Total entries:', count); // Clear only memory cache (keeps IndexedDB data) storage.clearMemoryCache();

Performance Benefits:

  • First read: Load from IndexedDB → ~1-5ms
  • Subsequent reads: Return from memory → ~0.01ms, 100-500x faster

Single Instance Principle

Multiple instances with the same dbName and tableName automatically share the same underlying IndexedDB table and connection:

import { CachedStorage } from 'indexeddb-keyvalue'; // Create two instances with same dbName + tableName const storageA = new CachedStorage('myDB', 'myTable'); const storageB = new CachedStorage('myDB', 'myTable'); // Write through instance A await storageA.setItem('key', 'value from A'); // Read through instance B - gets the same data! const data = await storageB.getItem('key'); // 'value from A' // Both instances share the same IndexedDB table and connection // but each has independent memory cache

This design ensures:

  • ✅ Resource efficiency (single connection per table)
  • ✅ Data consistency across instances
  • ✅ Independent memory caching per instance

localStorage API Compatibility

This library provides a localStorage-compatible API that makes migration effortless:

localStorageindexeddb-keyvalueDifference
setItem(key, value)await storage.setItem(key, value)Async + supports objects
getItem(key)await storage.getItem(key)Async + returns objects
removeItem(key)await storage.removeItem(key)Async
clear()await storage.clear()Async
key(index)await storage.key(index)Async
lengthawait storage.length()Async method

Key advantages over localStorage:

  • ✅ Stores any JavaScript object (not just strings)
  • ✅ Asynchronous - doesn't block the main thread
  • ✅ Much larger storage capacity (typically 50MB+ vs 5-10MB)
  • ✅ Memory caching for instant subsequent reads
  • ✅ Structured data with multiple tables

CachedFetch (HTTP Request Caching)

Automatically cache network request results to IndexedDB:

import { CachedFetch } from 'indexeddb-keyvalue'; const cachedFetch = new CachedFetch('cacheDB', 'apiCache'); // First request hits the network and caches the result const data = await cachedFetch.fetchJson('https://api.example.com/data'); // Subsequent requests read directly from IndexedDB, no network access const cachedData = await cachedFetch.fetchJson('https://api.example.com/data'); // Support for other response types const text = await cachedFetch.fetchText('https://api.example.com/text'); const blob = await cachedFetch.fetchBlob('https://api.example.com/image.png'); const buffer = await cachedFetch.fetchArrayBuffer('https://api.example.com/binary'); // Use converter function to process data const users = await cachedFetch.fetchJson('https://api.example.com/users', (data) => { return data.map(user => ({ ...user, fullName: `${user.firstName} ${user.lastName}` })); }); // Access underlying CachedStorage for cache management await cachedFetch.cachedStorage.clear(); // Clear all request caches

Multi-Table Usage

Each table is completely isolated:

import { CachedStorage } from 'indexeddb-keyvalue'; // Create separate tables in the same database const userStorage = new CachedStorage('appDB', 'users'); const orderStorage = new CachedStorage('appDB', 'orders'); const productStorage = new CachedStorage('appDB', 'products'); // Each table is independent await userStorage.setItem('user1', { name: 'John' }); await orderStorage.setItem('order1', { total: 100 }); await productStorage.setItem('product1', { name: 'Product A' }); // Data is completely isolated between tables const user = await userStorage.getItem('user1'); // { name: 'John' } const order = await orderStorage.getItem('order1'); // { total: 100 }

Subscription (Reactive)

Subscribe to data changes with automatic initialization callback:

const storage = new CachedStorage('myDB', 'myTable'); // Subscribe to changes const unsubscribe = storage.subscribe('user', (value, oldValue, key) => { console.log(`Key "${key}" changed:`, oldValue, '->', value); }); // Note: Callback fires immediately with current value from memory cache // Update data - triggers notification await storage.setItem('user', { name: '张三' }); // Unsubscribe unsubscribe();

Alternative unsubscribe method:

function onChange(value, oldValue, key) { console.log('Changed:', value); } storage.subscribe('user', onChange); // Unsubscribe using method (when you can't save the return value) storage.unsubscribe('user', onChange);

Key features:

  • Immediate callback - Fires once on subscription with current value
  • Deep equality check - Only notifies when value actually changes
  • Multiple subscribers - One key can have multiple independent subscribers
  • Error isolation - One failing callback doesn't affect others
  • Cross-tab sync - Changes automatically sync across browser tabs/iframes
  • Batch unsubscribe - Clear all subscriptions at once

Batch Unsubscribe

Clear all subscriptions at once (useful when component unmounts):

const storage = new CachedStorage('myDB', 'myTable'); // Create multiple subscriptions storage.subscribe('user', onUserChange); storage.subscribe('settings', onSettingsChange); storage.subscribe('theme', onThemeChange); // Option 1: Clear subscriptions for a specific key const cleared = storage.clearKeySubscriptions('user'); console.log(`Cleared ${cleared} subscriptions for 'user'`); // Option 2: Clear all subscriptions at once const total = storage.clearAllSubscriptions(); console.log(`Cleared ${total} total subscriptions`); // Option 3: Get subscription statistics const stats = storage.getSubscriptionStats(); console.log('Total subscriptions:', stats.total); console.log('By key:', stats.byKey); // { total: 5, byKey: { user: 2, settings: 2, theme: 1 } } // Option 4: Clear memory cache only storage.clearMemoryCache();

Drop Database

For test environments or when you need to completely delete database resources:

import { CachedStorage } from 'indexeddb-keyvalue'; // Delete entire database (closes connections, clears caches, deletes IndexedDB) await CachedStorage.dropDB('my_database'); // Clear all data from current table (keeps table structure) const storage = new CachedStorage('my_database', 'my_table'); await storage.dropTable();

Use cases:

  • Test cleanup between test runs
  • Complete database reset
  • Resource cleanup when application shuts down

Note: dropDB() is a static method called on the class, while dropTable() is an instance method.

Cross-Tab Synchronization

Subscribe to data changes across browser tabs and iframes (same-origin only):

// Tab 1 const storage1 = new CachedStorage('myDB', 'myTable'); storage1.subscribe('user', (value, oldValue, key) => { console.log('Tab 1 received:', value); }); await storage1.setItem('user', { name: '张三' }); // Automatically broadcasts to other tabs
// Tab 2 (same origin) const storage2 = new CachedStorage('myDB', 'myTable'); storage2.subscribe('user', (value, oldValue, key) => { console.log('Tab 2 received:', value); // { name: '张三' } }); // Callback fires immediately with current value, then updates when Tab 1 changes

How it works:

  • Uses BroadcastChannel API for cross-tab communication
  • Changes are automatically synchronized across all open tabs/iframes
  • Memory cache is updated in real-time on all tabs
  • No extra code needed - works with the same subscribe() API
  • Same-origin only (no cross-domain support)
  • Gracefully degrades if BroadcastChannel is not available

React Hook Example

import { useState, useEffect } from 'react'; function useStorageItem(storage, key) { const [value, setValue] = useState(undefined); useEffect(() => { // subscribe fires immediately with current value return storage.subscribe(key, setValue); }, [storage, key]); return value; } // Usage function UserProfile() { const user = useStorageItem(storage, 'user'); if (user === undefined) return <div>Loading...</div>; return <div>{user?.name || 'No data'}</div>; }

Vue 3 Composable Example

import { ref, onUnmounted } from 'vue'; export function useStorageItem(storage, key) { const data = ref(undefined); const unsubscribe = storage.subscribe(key, (value) => { data.value = value; }); onUnmounted(unsubscribe); return data; } // Usage const user = useStorageItem(storage, 'user');

Svelte Store Example

import { writable } from 'svelte/store'; function storageStore(storage, key) { const { subscribe, set } = writable(undefined); const unsubscribe = storage.subscribe(key, (value) => set(value)); return { subscribe, unsubscribe }; }

API Reference

CachedStorage

Constructor

ParameterTypeDefaultDescription
dbNamestring'linushp_default'Database name
tableNamestring'linushp_t'Table name
optionsObject{}Configuration options
options.enableCrossTabbooleantrueEnable cross-tab synchronization (BroadcastChannel)

Usage Examples:

// Traditional way (backward compatible) const storage = new CachedStorage('myDB', 'myTable'); // Configuration object way const storage = new CachedStorage({ dbName: 'myDB', tableName: 'myTable', enableCrossTab: false // Disable cross-tab sync });

Note: For the same dbName + tableName, the configuration from the first created instance takes effect. Subsequent instances will reuse the same underlying PrivateCachedStorage instance regardless of their configuration.

Methods

MethodParametersReturnDescription
setItem(key, value)key: string, value: anyPromise<void>Save or update data (Memory + IndexedDB)
getItem(key)key: stringPromise<any>Get data (priority from memory cache)
removeItem(key)key: stringPromise<void>Delete data (Memory + IndexedDB)
clear()-Promise<void>Clear table (Memory + IndexedDB)
key(index)index: numberPromise<string | null>Get key at specified index
keys()-Promise<string[]>Get all keys (enhanced API)
length()-Promise<number>Get total number of entries
clearMemoryCache()-voidClear only memory cache
getMemoryCacheSize()-numberGet memory cache entry count
subscribe(key, callback)key: string, callback: (value, oldValue, key) => void() => voidSubscribe to key changes (supports cross-tab sync), returns unsubscribe function
unsubscribe(key, callback)key: string, callback: FunctionbooleanUnsubscribe a specific callback
clearKeySubscriptions(key)key: stringnumberClear all subscriptions for a specific key, returns count cleared
clearAllSubscriptions()-numberClear all subscriptions (all keys), returns total count cleared
getSubscriptionStats()-{ total, byKey }Get subscription statistics
CachedStorage.dropDB(dbName)dbName: stringPromise<void>Static - Delete entire database (close connections, clear caches, delete IndexedDB)
storage.dropTable()-Promise<void>Clear all data from current table (keeps table structure)

CachedFetch

MethodParametersReturnDescription
fetchJson(url, converter?)url: string, converter?: (data) => anyPromise<T>Get JSON data
fetchText(url, converter?)url: string, converter?: (data) => stringPromise<string>Get text data
fetchBlob(url, converter?)url: string, converter?: (data) => BlobPromise<Blob>Get Blob data
fetchArrayBuffer(url, converter?)url: string, converter?: (data) => ArrayBufferPromise<ArrayBuffer>Get binary data

Properties:

  • cachedStorage: CachedStorage - The underlying storage instance for direct cache management

TypeScript

import { CachedStorage, CachedFetch, ResponseType, Converter } from 'indexeddb-keyvalue'; const storage = new CachedStorage('myDB', 'myTable'); const cachedFetch = new CachedFetch('cacheDB', 'apiCache'); // Type-safe usage interface User { id: string; name: string; } const user = await storage.getItem('user1') as User; const converter: Converter<User[]> = (data) => { return data.map((item: any) => ({ id: item.id, name: item.name })); }; const users = await cachedFetch.fetchJson<User[]>('https://api.example.com/users', converter);

Architecture Details

Directory Structure

src/ ├── index.js # Public API: exports CachedStorage, CachedFetch ├── CachedStorage.js # Public class: dual-layer caching storage ├── CachedFetch.js # Public class: HTTP request caching └── private/ # Private implementation (not exported) ├── PrivateStorageFactory.js ├── PrivateTableStorage.js ├── PrivateDatabase.js └── utils/ └── promisifyStore.js

Key Design Decisions

  1. Single Instance Principle: Same dbName+tableName always returns the same underlying IndexedDB connection
  2. Transparent Factory: Factory pattern is internal, users just use new CachedStorage()
  3. Clean API Surface: Only 2 classes exported, reducing cognitive load
  4. Dual-Layer Caching: Memory for speed, IndexedDB for persistence
  5. localStorage Compatibility: Familiar API with enhanced capabilities

Migration from v1.x

If you were using StorageFactory or TinyIndexDB directly:

// Old (v1.x) - Using StorageFactory import { StorageFactory } from 'indexeddb-keyvalue'; const storage = StorageFactory.getStorage('db', 'table'); // New (v2.x) - Simplified API import { CachedStorage } from 'indexeddb-keyvalue'; const storage = new CachedStorage('db', 'table'); // Factory is now transparent, single instance principle still applies

Development

# Install dependencies npm install # Development mode (start local server) npm run dev # Build production version npm run build # Build development version npm run build:dev

Browser Compatibility

  • Chrome/Edge 24+
  • Firefox 16+
  • Safari 10+
  • iOS Safari 10+
  • Android Chrome 25+

License

MIT

About

No description, topics, or website provided.
Language
HTML74.6%
JavaScript25.4%