Skip to content


Hybrid WebCache

๐Ÿ“š Summary โ€‹

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.


๐ŸŽฏ When Should You Use This Library? โ€‹

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.

๐Ÿ’ก It's a great fit for: โ€‹

  • 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.

Requirements โ€‹

  • Node.js >= 18 for development/test environment
  • Browsers with ES2020 support

๐Ÿš€ Main Features โ€‹

  • 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.

๐Ÿ›  Usage โ€‹

You can install the library using npm or yarn:

bash
npm i hybrid-webcache
# or 
yarn add hybrid-webcache

โœ๏ธ Example Usage โ€‹

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<string>('sessionToken');
console.log(`Token: ${tokenData?.value}`); // Output: Token: abc123
console.log(`Is Expired: ${tokenData?.isExpired}`); // 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 โ€‹

Methods โ€‹

MethodDescription
constructorInitializes the cache instance.
initInitializes the underlying storage (e.g., loads IndexedDB data into memory cache). This is crucial for synchronous IndexedDB operations.
set or setSyncAsynchronously/Synchronously stores a value at the specified keyPath with an optional TTL.
get or getSyncAsynchronously/Synchronously retrieves a value from the cache. Returns DataGetModel including value, expiresAt, and isExpired. Optionally removes expired entries.
getAll or getAllSyncAsynchronously/Synchronously retrieves all cache entries as a Map. Optionally removes expired entries.
getJson or getJsonSyncAsynchronously/Synchronously retrieves all cache entries as a plain JSON object. Optionally removes expired entries.
has or hasSyncAsynchronously/Synchronously checks if a value exists for the specified keyPath.
unset or unsetSyncAsynchronously/Synchronously removes a value at the specified keyPath. If no keyPath is provided, clears the entire cache.
resetWith or resetWithSyncAsynchronously/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.

Options to Constructor โ€‹

ParameterTypeDescription
ttlTTLSets the time to live for data in the cache. Can be in seconds, minutes, hours, or days.
removeExpiredbooleanAutomatically removes expired items when attempting to access them.
storageStorageEngineAuto, LocalStorage, IndexedDB, SessionStorage or Memory. Sets the storage engine. Auto selects the best available.
AutoAutomatically selects the best available storage engine based on browser support.
IndexedDBUses IndexedDB for caching, with synchronization between tabs via BroadcastChannel.
LocalStorageUses the browser's local storage for caching, with synchronization between tabs via BroadcastChannel.
SessionStorageUses the browser's session storage for caching, isolated per tab. Data persists only for the duration of the tab's lifecycle.
MemoryUses in-memory storage for caching, synchronization only with the instance itself.

Types Used โ€‹

ts
enum StorageEngine {Auto, LocalStorage, IndexedDB, SessionStorage, Memory }

type ValueType = null | string | number | boolean | object | DictionaryType | ValueType[];
type DictionaryType = { [key: string]: ValueType };

type RecordType<T extends ValueType> = Record<string, T>;
type KeyPath = string | Array<string>;

type TTL = number | { seconds?: number; minutes?: number; hours?: number; days?: number };

type Options = {
  storage: StorageEngine;
  ttl: Partial<TTL>;
  removeExpired: boolean;
};

interface DataModel<T> {
  value: T;
  expiresAt: number;
}

interface DataGetModel<T> extends DataModel<T> {
  isExpired: boolean;
}

๐Ÿ“ฅ Storage and Sync โ€‹

EnginePersistenceShared across tabsTTLSync
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.


โœ”๏ธ Project Scripts โ€‹

  • npm run check โ€” runs formatter, linter and import sorting to the requested files
  • npm run format โ€” run the formatter on a set of files
  • npm run lint โ€” run various checks on a set of files
  • npm run test โ€” run unit tests
  • npm run test:c โ€” run unit tests with coverage
  • npm run docs:dev โ€” run documentation locally
  • npm run commit - run conventional commits check
  • npm run release:test โ€” dry run semantic release
  • npm run build โ€” build library

๐Ÿ“ฆ Dependencies โ€‹

  • lodash: For robust object manipulation (e.g., setting, getting, and unsetting nested properties).
  • Typescript: For static typing, improved code quality, and enhanced developer experience.

๐Ÿค Contributing โ€‹

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:

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

๐Ÿ’ต Support the Project โ€‹

If you appreciate that, please consider donating to the Developer via GitHub Sponsors, Ko-fi, Paypal or Liberapay, you decide. ๐Ÿ˜‰

GitHub SponsorsPayPalKo-fiLiberapay

๐Ÿ“ License โ€‹

MIT ยฉ Heliomar P. Marques ๐Ÿ”