国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

Home Web Front-end JS Tutorial Enhancing LRU Cache with Configurable Data Persistence

Enhancing LRU Cache with Configurable Data Persistence

Dec 26, 2024 pm 05:38 PM

Enhancing LRU Cache with Configurable Data Persistence

Building on the foundations of this guide on creating an in-memory cache, we’ll take it further by introducing configurable data persistence. By leveraging the Adapter and Strategy patterns, we’ll design an extensible system that decouples storage mechanisms from the caching logic, allowing seamless integration of databases or services as needed.

The Vision: Decoupling Like an ORM

The goal is to make the cache extensible without altering its core logic. Inspired by ORM systems, our approach involves a shared API abstraction. This allows storage — such as localStorage, IndexedDB, or even a remote database—to work interchangeably with minimal code changes.

The Storage Adapter Base Class

Here’s the abstract class defining the API for any persistence system:

export abstract class StorageAdapter {
  abstract connect(): Promise<void>;
  abstract add(key: string, value: unknown): Promise<void>;
  abstract get(key: string): Promise<unknown | null>;
  abstract getAll(): Promise<Record<string, unknown>>;
  abstract delete(key: string): Promise<void>;
  abstract clear(): Promise<void>;
}

Any storage solution must extend this base class, ensuring consistency in interaction. For instance, here’s the implementation for IndexedDB:

Example: IndexedDB Adapter

This adapter implements the StorageAdapter interface to persist cache data in an IndexedDB store.

import { StorageAdapter } from './storage_adapter';

/**
 * IndexedDBAdapter is an implementation of the StorageAdapter 
 * interface designed to provide a persistent storage mechanism 
 * using IndexedDB. This adapter can be reused for other cache 
 * implementations or extended for similar use cases, ensuring 
 * flexibility and scalability.
 */
export class IndexedDBAdapter extends StorageAdapter {
  private readonly dbName: string;
  private readonly storeName: string;
  private db: IDBDatabase | null = null;

  /**
   * Initializes the adapter with the specified database and store 
   * names. Defaults are provided to make it easy to set up without 
   * additional configuration.
   */
  constructor(dbName: string = 'cacheDB', storeName: string = 'cacheStore') {
    super();
    this.dbName = dbName;
    this.storeName = storeName;
  }

  /**
   * Connects to the IndexedDB database and initializes it if 
   * necessary. This asynchronous method ensures that the database 
   * and object store are available before any other operations. 
   * It uses the `onupgradeneeded` event to handle schema creation 
   * or updates, making it a robust solution for versioning.
   */
  async connect(): Promise<void> {
    return await new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);

      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName, { keyPath: 'key' });
        }
      };

      request.onsuccess = (event) => {
        this.db = (event.target as IDBOpenDBRequest).result;
        resolve();
      };

      request.onerror = () => reject(request.error);
    });
  }

  /**
   * Adds or updates a key-value pair in the store. This method is 
   * asynchronous to ensure compatibility with the non-blocking 
   * nature of IndexedDB and to prevent UI thread blocking. Using 
   * the `put` method ensures idempotency: the operation will 
   * insert or replace the entry.
   */
  async add(key: string, value: unknown): Promise<void> {
    await this._withTransaction('readwrite', (store) => store.put({ key, value }));
  }

  /**
   * Retrieves the value associated with a key. If the key does not 
   * exist, null is returned. This method is designed to integrate 
   * seamlessly with caching mechanisms, enabling fast lookups.
   */
  async get(key: string): Promise<unknown | null> {
    return await this._withTransaction('readonly', (store) =>
      this._promisifyRequest(store.get(key)).then((result) =>
        result ? (result as { key: string; value: unknown }).value : null
      )
    );
  }

  /**
   * Fetches all key-value pairs from the store. Returns an object 
   * mapping keys to their values, making it suitable for bulk 
   * operations or syncing with in-memory caches.
   */
  async getAll(): Promise<Record<string, unknown>> {
    return await this._withTransaction('readonly', (store) =>
      this._promisifyRequest(store.getAll()).then((results) =>
        results.reduce((acc: Record<string, unknown>, item: { key: string; value: unknown }) => {
          acc[item.key] = item.value;
          return acc;
        }, {})
      )
    );
  }

  /**
   * Deletes a key-value pair by its key. This method is crucial 
   * for managing cache size and removing expired entries. The 
   * `readwrite` mode is used to ensure proper deletion.
   */
  async delete(key: string): Promise<void> {
    await this._withTransaction('readwrite', (store) => store.delete(key));
  }

  /**
   * Clears all entries from the store. This method is ideal for 
   * scenarios where the entire cache needs to be invalidated, such 
   * as during application updates or environment resets.
   */
  async clear(): Promise<void> {
    await this._withTransaction('readwrite', (store) => store.clear());
  }

  /**
   * Handles transactions in a reusable way. Ensures the database 
   * is connected and abstracts the transaction logic. By 
   * centralizing transaction handling, this method reduces 
   * boilerplate code and ensures consistency across all operations.
   */
  private async _withTransaction<T>(
    mode: IDBTransactionMode,
    callback: (store: IDBObjectStore) => IDBRequest | Promise<T>
  ): Promise<T> {
    if (!this.db) throw new Error('IndexedDB is not connected');
    const transaction = this.db.transaction([this.storeName], mode);
    const store = transaction.objectStore(this.storeName);
    const result = callback(store);
    return result instanceof IDBRequest ? await this._promisifyRequest(result) : await result;
  }

  /**
   * Converts IndexedDB request events into Promises, allowing for 
   * cleaner and more modern asynchronous handling. This is 
   * essential for making IndexedDB operations fit seamlessly into 
   * the Promise-based architecture of JavaScript applications.
   */
  private async _promisifyRequest<T>(request: IDBRequest): Promise<T> {
    return await new Promise((resolve, reject) => {
      request.onsuccess = () => resolve(request.result as T);
      request.onerror = () => reject(request.error);
    });
  }
}

Integrating the Adapter into the Cache

The cache accepts an optional StorageAdapter. If provided, it initializes the database connection, loads data into memory, and keeps the cache and storage in sync.

private constructor(capacity: number, storageAdapter?: StorageAdapter) {
  this.capacity = capacity;
  this.storageAdapter = storageAdapter;

  if (this.storageAdapter) {
    this.storageAdapter.connect().catch((error) => {
      throw new Error(error);
    });

    this.storageAdapter.getAll().then((data) => {
      for (const key in data) {
        this.put(key, data[key] as T);
      }
    }).catch((error) => {
      throw new Error(error);
    });
  }

  this.hash = new Map();
  this.head = this.tail = undefined;

  this.hitCount = this.missCount = this.evictionCount = 0;
}

Why Adapter and Strategy Patterns?

Using the Adapter pattern:

  • Decouples the cache from specific storage mechanisms.
  • Ensures extensibility for new storage backends.

Combining with the Strategy pattern:

  • Enables runtime selection of the persistence layer.
  • Simplifies testing by mocking different adapters.

Key Design Practices

  • Abstract API: Keeps the cache logic agnostic of storage details.
  • Singleton Cache: Ensures shared state consistency.
  • Async Initialization: Avoids blocking operations during setup.
  • Lazy Loading: Only loads persisted data when a storage adapter is provided.

Next Steps

This design is robust but leaves room for enhancements:

  • Optimize sync logic for better performance.
  • Experiment with additional adapters like Redis or SQLite.

Try It Out! ?

If you’d like to test the cache in action, it’s available as an npm package: adev-lru. You can also explore the full source code on GitHub: adev-lru repository. I welcome any recommendations, constructive feedback, or contributions to make it even better! ?

Happy coding! ?

The above is the detailed content of Enhancing LRU Cache with Configurable Data Persistence. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undress AI Tool

Undress AI Tool

Undress images for free

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Stock Market GPT

Stock Market GPT

AI powered investment research for smarter decisions

Clothoff.io

Clothoff.io

AI clothes remover

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Vercel SPA routing and resource loading: Solve deep URL access issues Vercel SPA routing and resource loading: Solve deep URL access issues Aug 13, 2025 am 10:18 AM

This article aims to solve the problem of deep URL refresh or direct access causing page resource loading failure when deploying single page applications (SPAs) on Vercel. The core is to understand the difference between Vercel's routing rewriting mechanism and browser parsing relative paths. By configuring vercel.json to redirect all paths to index.html, and correct the reference method of static resources in HTML, change the relative path to absolute path, ensuring that the application can correctly load all resources under any URL.

Vercel Single Page Application (SPA) Deployment Guide: Solving Deep URL Asset Loading Issues Vercel Single Page Application (SPA) Deployment Guide: Solving Deep URL Asset Loading Issues Aug 13, 2025 pm 01:03 PM

This tutorial aims to solve the problem of loading assets (CSS, JS, images, etc.) when accessing multi-level URLs (such as /projects/home) when deploying single page applications (SPAs) on Vercel. The core lies in understanding the difference between Vercel's routing rewriting mechanism and relative/absolute paths in HTML. By correctly configuring vercel.json, ensure that all non-file requests are redirected to index.html and correcting asset references in HTML as absolute paths, thereby achieving stable operation of SPA at any depth URL.

Qwik: A Resumable Framework for Instant-Loading Web Apps Qwik: A Resumable Framework for Instant-Loading Web Apps Aug 15, 2025 am 08:25 AM

Qwikachievesinstantloadingbydefaultthroughresumability,nothydration:1)TheserverrendersHTMLwithserializedstateandpre-mappedeventlisteners;2)Norehydrationisneeded,enablingimmediateinteractivity;3)JavaScriptloadson-demand,onlywhenuserinteractionoccurs;4

js add element to start of array js add element to start of array Aug 14, 2025 am 11:51 AM

In JavaScript, the most common method to add elements to the beginning of an array is to use the unshift() method; 1. Using unshift() will directly modify the original array, you can add one or more elements to return the new length of the added array; 2. If you do not want to modify the original array, it is recommended to use the extension operator (such as [newElement,...arr]) to create a new array; 3. You can also use the concat() method to combine the new element array with the original number, return the new array without changing the original array; in summary, use unshift() when modifying the original array, and recommend the extension operator when keeping the original array unchanged.

How to lazy load images with JavaScript How to lazy load images with JavaScript Aug 14, 2025 pm 06:43 PM

Usetheloading="lazy"attributefornativelazyloadinginmodernbrowserswithoutJavaScript.2.Formorecontrolorolderbrowsersupport,implementlazyloadingwiththeIntersectionObserverAPIbysettingdata-srcfortheactualimageURLandusingaplaceholderinsrc.3.Obse

In-depth analysis of common vulnerabilities and improvement strategies for JavaScript XSS defense functions In-depth analysis of common vulnerabilities and improvement strategies for JavaScript XSS defense functions Aug 14, 2025 pm 10:06 PM

This article explores in-depth security vulnerabilities in custom JavaScript XSS defense functions, especially incomplete character escape and easy bypass to keyword-based filtering. By analyzing an example function, it reveals the risks of unprocessed keyword characters such as quotes and backquotes, and how code obfuscation techniques circumvent simple keyword detection. The article emphasizes the importance of context-sensitive escape and recommends the adoption of mature libraries and multi-layer defense strategies to build more robust security protection.

How to access and modify HTML elements using the DOM in JavaScript How to access and modify HTML elements using the DOM in JavaScript Aug 16, 2025 am 11:25 AM

ToaccessandmodifyHTMLelementsusingJavaScript,firstselecttheelementusingmethodslikedocument.getElementById,document.querySelector,ordocument.querySelectorAll,thenalteritscontent,attributes,orstyles;forexample,useelement.textContentforsafetextupdates,e

Optimize event handling of dynamic external link jumps in jQuery pop-up window Optimize event handling of dynamic external link jumps in jQuery pop-up window Sep 01, 2025 am 11:48 AM

This article aims to solve the problem of redirecting the external link redirect button in jQuery pop-up window causing jump errors. When a user clicks multiple external links in succession, the jump button in the pop-up may always point to the first clicked link. The core solution is to use the off('click') method to undo the old event handler before each binding of a new event, ensuring that the jump behavior always points to the latest target URL, thus achieving accurate and controllable link redirection.

See all articles