Hybrid WebCache - A library that combines `localStorage`, `IndexedDB`, `SessionStorage` and `Memory` to provide a high-performance hybrid cache with multi-instance synchronization support.
npm install hybrid-webcache
[![DeepScan grade][url-deepscan-badge]][url-deepscan]
[![CodeFactor][url-codefactor-badge]][url-codefactor]
[![Test][url-test-badge]][url-test]
[![Coverage][url-coverage-badge]][url-coverage-report]
[![NPM version][url-npm-badge]][url-npm]
[![Downloads][url-downloads-badge]][url-downloads]
[![PayPal][url-paypal-badge]][url-paypal]
[![Ko-fi][url-kofi-badge]][url-kofi]
[![Liberapay][url-liberapay-badge]][url-liberapay]
[![GitHub Sponsors][url-github-sponsors-badge]][url-github-sponsors]
HybridWebCache is a robust and flexible library designed for efficient cache management in modern web applications. \
It seamlessly supports multiple underlying storage mechanisms (LocalStorage, IndexedDB, SessionStorage, and in-memory Map) and includes built-in Time-To-Live (TTL) support for automatic data expiration. This library helps optimize application performance by providing a unified, easy-to-use API for storing and retrieving data, with intelligent fallbacks and cross-tab synchronization capabilities.
---
This library is ideal for web applications that require flexible and persistent data storage beyond simple session or local storage, with a focus on performance, data freshness, and multi-tab consistency.
- Offline-first applications (PWAs): Leverage IndexedDB for robust, large-scale offline data storage.
- Improving perceived performance: Cache API responses, user preferences, or frequently accessed static data to reduce network requests and load times.
- Managing user sessions and preferences: Store non-sensitive user-specific data that needs to persist across browser sessions or tabs.
- Simplifying cache logic: Abstract away the complexities of different browser storage APIs into a single, cohesive interface.
- Applications requiring data expiration: Automatically clear stale data using configurable TTLs.
- Node.js >= 18 for development/test environment
- Browsers with ES2020 support
---
- Hybrid Storage Strategies: Automatically selects the best available storage engine (IndexedDB, LocalStorage, SessionStorage, or in-memory) based on browser capabilities and user configuration.
- IndexedDB: Uses IndexedDB for caching, synchronized between tabs via _BroadcastChannel_.
- LocalStorage: Uses the browser's local storage, synchronized between tabs via _BroadcastChannel_.
- SessionStorage: Uses the browser's session storage, isolated per tab. Data persists only for the duration of the tab's lifecycle.
- Memory: Uses in-memory storage for caching, synchronized only with the instance itself.
- Automatic Expiration (TTL): Define Time-To-Live for cached items, ensuring data freshness and automatic removal of stale entries.
- Cross-Tab Synchronization: Utilizes BroadcastChannel to synchronize data changes across multiple open browser tabs/windows for LocalStorage and IndexedDB strategies, maintaining data consistency.
- Unified API: Provides a consistent and intuitive API for all storage operations, abstracting away the underlying storage mechanism complexities.
- Synchronous & Asynchronous Methods: Offers both async/await and synchronous versions of key operations (set/setSync, get/getSync, etc.) for flexible integration into your application's flow.
- Deep Key Path Support: Easily store and retrieve data from nested objects or arrays using dot notation (user.profile.name) or array indexing (items[0].id).
- TypeScript Ready: Built with TypeScript for strong typing, enhanced developer experience, and compile-time error checking.
- PWA Compatibility: Designed with Progressive Web App (PWA) principles in mind, enabling robust offline capabilities when using IndexedDB.
- Simple Integration: Integrate with your favorite frameworks, such as Next.js, React, Svelte, or Vue, for a seamless experience.
---
You can install the library using npm or yarn:
``bash`
npm i hybrid-webcacheor
yarn add hybrid-webcache
To use the library in a TypeScript or modern JavaScript project, you can import it directly:
- Basic Usage with Default Options (storage: Auto, ttl: 1 hour)
`ts
import { HybridWebCache, StorageEngine } from 'hybrid-webcache';
const cache = new HybridWebCache();
await cache.set('sessionToken', 'abc123');
const tokenData = await cache.get
console.log(Token: ${tokenData?.value}); // Output: Token: abc123Is Expired: ${tokenData?.isExpired}
console.log(); // Output: Is Expired: false`
- Creating an instance with custom options (e.g., IndexedDB, 10-minute TTL)
`ts
import { HybridWebCache, StorageEngine } from 'hybrid-webcache';
// Note: For IndexedDB, remember to call .init() if you plan to use synchronous methods
const indexedDBCache = new HybridWebCache('myAppCache', {
storage: StorageEngine.IndexedDB,
ttl: { minutes: 10 },
removeExpired: true,
});
await indexedDBCache.init(); // Initialize IndexedDB to load memory cache for sync operations
//Setting and Getting Nested Data
await indexedDBCache.set('user.profile.firstName', 'John', { hours: 1 });
indexedDBCache.setSync('user.profile.lastName', 'Doe'); // Uses instance's default TTL (10 minutes)
indexedDBCache.setSync(['user', 'profile', 'age'], 30); // Array KeyPath
const userData = await indexedDBCache.get('user.profile');
console.log(userData?.value); // Output: { firstName: 'John', lastName: 'Doe', age: 30 }
const firstNameData = indexedDBCache.getSync('user.profile.firstName');
console.log(firstNameData?.value); // Output: John
// Checking for Key Existence
const hasUser = await indexedDBCache.has('user.profile.firstName');
console.log(Has user first name: ${hasUser}); // Output: Has user first name: true
const hasNonExistentKey = indexedDBCache.hasSync('non.existent.key');
console.log(Has non-existent key: ${hasNonExistentKey}); // Output: Has non-existent key: false
// Unsetting Data (Partial and Full)
const complexObject = {
theme: 'dark',
settings: {
language: 'en-US',
notifications: { email: true, sms: false }
},
items: ['apple', 'banana', 'orange']
};
await indexedDBCache.set('appConfig', complexObject);
// Unset a nested property
await indexedDBCache.unset('appConfig.settings.notifications.sms');
const updatedAppConfig = await indexedDBCache.get('appConfig');
console.log(updatedAppConfig?.value);
// Output: { theme: 'dark', settings: { language: 'en-US', notifications: { email: true } }, items: ['apple', 'banana', 'orange'] }
// Unset an array element (sets to null)
indexedDBCache.unsetSync('appConfig.items[1]');
const updatedItems = indexedDBCache.getSync('appConfig.items');
console.log(updatedItems?.value); // Output: ['apple', null, 'orange']
// Unset the entire 'appConfig' key
await indexedDBCache.unset('appConfig');
const appConfigAfterUnset = await indexedDBCache.get('appConfig');
console.log(appConfigAfterUnset); // Output: undefined
// Retrieving All Data
await indexedDBCache.set('product1', { id: 1, name: 'Laptop' });
await indexedDBCache.set('product2', { id: 2, name: 'Mouse' });
const allItemsMap = await indexedDBCache.getAll();
console.log(allItemsMap);
/* Output:
Map(2) {
'product1' => { value: { id: 1, name: 'Laptop' }, expiresAt: ..., isExpired: false },
'product2' => { value: { id: 2, name: 'Mouse' }, expiresAt: ..., isExpired: false }
}
*/
const allItemsJson = indexedDBCache.getJsonSync();
console.log(allItemsJson);
/* Output:
{
product1: { id: 1, name: 'Laptop' },
product2: { id: 2, name: 'Mouse' }
}
*/
// Resetting the Cache
await indexedDBCache.resetWith({
user: { id: 'user123', status: 'active' },
app: { version: '1.0.0' }
}, { minutes: 5 }); // New TTL for reset
const resetData = await indexedDBCache.getJson();
console.log(resetData);
/* Output:
{
user: { id: 'user123', status: 'active' },
app: { version: '1.0.0' }
}
*/
// Getting Cache Info
const cacheInfo = indexedDBCache.info;
console.log(cacheInfo);
/* Output:
{
dataBase: 'myAppCache',
size: 'XXb', // e.g., '120b'
options: {
ttl: 300000, // 5 minutes in ms
removeExpired: true,
storage: 2 // StorageEngine.IndexedDB
}
}
*/
`
---๐ API Reference
See the API documentation for a complete list of available functions and their signatures.
| Method | Description |
| --- | --- |
| constructor| Initializes the cache instance.init
| | Initializes the underlying storage (e.g., loads IndexedDB data into memory cache). This is crucial for synchronous IndexedDB operations.set
| or setSync| Asynchronously/Synchronously stores a value at the specified keyPath with an optional TTL.get
| or getSync | Asynchronously/Synchronously retrieves a value from the cache. Returns DataGetModel including value, expiresAt, and isExpired. Optionally removes expired entries.getAll
| or getAllSync | Asynchronously/Synchronously retrieves all cache entries as a Map. Optionally removes expired entries.getJson
| or getJsonSync| Asynchronously/Synchronously retrieves all cache entries as a plain JSON object. Optionally removes expired entries.has
| or hasSync| Asynchronously/Synchronously checks if a value exists for the specified keyPath.unset
| or unsetSync| Asynchronously/Synchronously removes a value at the specified keyPath. If no keyPath is provided, clears the entire cache.resetWith
| or resetWithSync| Asynchronously/Synchronously clears the cache and sets new key-value pairs.length()
| | Getter. Returns the number of items currently stored in the cache.bytes()
| | Getter. Returns the total number of bytes used by the cache in storage.info()
| | Getter. Provides information about the current cache, including database name, size, and options.storageType()
| | Getter. Returns the type of storage engine currently used by the cache.
| Parameter | Type | Description
| --- | --- | ---
| ttl | TTL | Sets the time to live for data in the cache. Can be in seconds, minutes, hours, or days.removeExpired
| | boolean | Automatically removes expired items when attempting to access them.storage
| | StorageEngine | Auto, LocalStorage, IndexedDB, SessionStorage or Memory. Sets the storage engine. Auto selects the best available.Auto
| | | Automatically selects the best available storage engine based on browser support. IndexedDB
| | | Uses IndexedDB for caching, with synchronization between tabs via BroadcastChannel.LocalStorage
| | | Uses the browser's local storage for caching, with synchronization between tabs via BroadcastChannel.SessionStorage
| | | Uses the browser's session storage for caching, isolated per tab. Data persists only for the duration of the tab's lifecycle.Memory
| | | Uses in-memory storage for caching, synchronization only with the instance itself.
`ts
enum StorageEngine {Auto, LocalStorage, IndexedDB, SessionStorage, Memory }
type ValueType = null | string | number | boolean | object | DictionaryType | ValueType[];
type DictionaryType = { [key: string]: ValueType };
type RecordType
type KeyPath = string | Array
type TTL = number | { seconds?: number; minutes?: number; hours?: number; days?: number };
type Options = {
storage: StorageEngine;
ttl: Partial
removeExpired: boolean;
};
interface DataModel
value: T;
expiresAt: number;
}
interface DataGetModel
isExpired: boolean;
}
`
___
Engine | Persistence | Shared across tabs | TTL | Sync
--- | --- | --- | --- | ---
IndexedDB | โ
| โ
| โ
| โ
(via BroadcastChannel)
LocalStorage | โ
| โ
| โ
| โ
(via BroadcastChannel)
SessionStorage | โ
(per tab) | โ | โ
| โ
Memory | โ | โ | โ
| โ
> [!NOTE]
> Synchronous operations for IndexedDB and LocalStorage strategies primarily interact with an in-memory cache that is synchronized across tabs via BroadcastChannel.
> Actual disk persistence for the IndexedDB strategy is handled asynchronously in the background.
---
* npm run check โ runs formatter, linter and import sorting to the requested filesnpm run format
* โ run the formatter on a set of filesnpm run lint
* โ run various checks on a set of filesnpm run test
* โ run unit testsnpm run test:c
* โ run unit tests with coveragenpm run docs:dev
* โ run documentation locallynpm run commit
* - run conventional commits checknpm run release:test
* โ dry run semantic release npm run build` โ build library
*
---
- lodash: For robust object manipulation (e.g., setting, getting, and unsetting nested properties).
- Typescript: For static typing, improved code quality, and enhanced developer experience.
---
We welcome contributions! Whether it's reporting a bug, suggesting a new feature, improving documentation, or submitting a pull request, your help is greatly appreciated.
Please make sure to read before making a pull request:
- Code of Conduct
- Contributing Guide
Thank you to all the people who already contributed to project!
###### Made with contrib.nn.
That said, there's a bunch of ways you can contribute to this project, like by:
โญ Starring the repository \
๐ Reporting bugs \
๐ก Suggest features \
๐งพ Improving the documentation \
๐ข Sharing this project and recommending it to your friends
If you appreciate that, please consider donating to the Developer via GitHub Sponsors, Ko-fi, Paypal or Liberapay, you decide. ๐
[![GitHub Sponsors][url-github-sponsors-badge]][url-github-sponsors]
[![PayPal][url-paypal-badge]][url-paypal]
[![Ko-fi][url-kofi-badge]][url-kofi]
[![Liberapay][url-liberapay-badge]][url-liberapay]
MIT ยฉ Heliomar P. Marques ๐
----
[url-github-sponsors-badge]: https://img.shields.io/badge/GitHub%20-Sponsor-1C1E26?style=for-the-badge&labelColor=1C1E26&color=db61a2
[url-github-sponsors]: https://github.com/sponsors/heliomarpm
[url-paypal-badge]: https://img.shields.io/badge/donate%20on-paypal-1C1E26?style=for-the-badge&labelColor=1C1E26&color=0475fe
[url-paypal]: https://bit.ly/paypal-sponsor-heliomarpm
[url-kofi-badge]: https://img.shields.io/badge/kofi-1C1E26?style=for-the-badge&labelColor=1C1E26&color=ff5f5f
[url-kofi]: https://ko-fi.com/heliomarpm
[url-liberapay-badge]: https://img.shields.io/badge/liberapay-1C1E26?style=for-the-badge&labelColor=1C1E26&color=f6c915
[url-liberapay]: https://liberapay.com/heliomarpm
[url-codeql-badge]: https://github.com/heliomarpm/hybrid-webcache/actions/workflows/codeql.yml/badge.svg
[url-codeql]: https://github.com/heliomarpm/hybrid-webcache/security/code-scanning
[url-test-badge]: https://github.com/heliomarpm/hybrid-webcache/actions/workflows/0.test.yml/badge.svg
[url-test]: https://github.com/heliomarpm/hybrid-webcache/actions/workflows/0.test.yml
[url-coverage-badge2]: https://img.shields.io/badge/coverage-dynamic.svg?label=coverage&color=informational&style=flat&logo=jest&query=$.coverage&url=https://heliomarpm.github.io/hybrid-webcache/coverage-badge.json
[url-coverage-badge]: https://img.shields.io/endpoint?url=https://heliomarpm.github.io/hybrid-webcache/coverage/coverage-badge.json
[url-coverage-report]: https://heliomarpm.github.io/hybrid-webcache/coverage
[url-release-badge]: https://github.com/heliomarpm/hybrid-webcache/actions/workflows/3.release.yml/badge.svg
[url-release]: https://github.com/heliomarpm/hybrid-webcache/actions/workflows/3.release.yml
[url-publish-badge]: https://github.com/heliomarpm/hybrid-webcache/actions/workflows/4.publish-npm.yml/badge.svg
[url-publish]: https://github.com/heliomarpm/hybrid-webcache/actions/workflows/4.publish-npm.yml
[url-npm-badge]: https://img.shields.io/npm/v/hybrid-webcache.svg
[url-npm]: https://www.npmjs.com/package/hybrid-webcache
[url-downloads-badge]: https://img.shields.io/npm/dm/hybrid-webcache.svg
[url-downloads]: http://badge.fury.io/js/hybrid-webcache.svg
[url-deepscan-badge]: https://deepscan.io/api/teams/19612/projects/28422/branches/916358/badge/grade.svg
[url-deepscan]: https://deepscan.io/dashboard#view=project&tid=19612&pid=28422&bid=916358
[url-codefactor-badge]: https://www.codefactor.io/repository/github/heliomarpm/hybrid-webcache/badge
[url-codefactor]: https://www.codefactor.io/repository/github/heliomarpm/hybrid-webcache