UNPKG

@firecms/core

Version:

Awesome Firebase/Firestore-based headless open-source CMS

205 lines (177 loc) 6.7 kB
import { EntityReference, GeoPoint, Vector } from "../types"; // Define a unique prefix for entity keys in localStorage to avoid key collisions const LOCAL_STORAGE_PREFIX = "entity_cache::"; // In-memory cache to store entities for quick access const entityCache: Map<string, object> = new Map(); // Check `localStorage` availability once during initialization const isLocalStorageAvailable = typeof localStorage !== "undefined"; // Define custom replacer for JSON.stringify function customReplacer(key: string): any { // @ts-ignore const value = this[key]; // Handle Date objects // @ts-ignore if (value instanceof Date) { return { __type: "Date", value: value.toISOString() }; } // Handle EntityReference // @ts-ignore if (value instanceof EntityReference) { return { __type: "EntityReference", id: value.id, path: value.path }; } // Handle GeoPoint // @ts-ignore if (value instanceof GeoPoint) { return { __type: "GeoPoint", latitude: value.latitude, longitude: value.longitude }; } // Handle Vector // @ts-ignore if (value instanceof Vector) { return { __type: "Vector", value: value.value }; } return value; } // Define custom reviver for JSON.parse function customReviver(key: string, value: any): any { if (value && typeof value === "object" && "__type" in value) { switch (value.__type) { case "Date": return new Date(value.value); case "EntityReference": return new EntityReference(value.id, value.path); case "GeoPoint": return new GeoPoint(value.latitude, value.longitude); case "Vector": return new Vector(value.value); default: return value; } } return value; } // Initialize the in-memory cache by loading entities from `localStorage` if (isLocalStorageAvailable) { try { // Iterate over all keys in localStorage to find those with the specified prefix for (let i = 0; i < localStorage.length; i++) { const fullKey = localStorage.key(i); if (fullKey && fullKey.startsWith(LOCAL_STORAGE_PREFIX)) { const path = fullKey.substring(LOCAL_STORAGE_PREFIX.length); const entityString = localStorage.getItem(fullKey); if (entityString) { try { const entity: object = JSON.parse(entityString, customReviver); entityCache.set(path, entity); } catch (parseError) { console.error( `Failed to parse entity for path "${path}" from localStorage:`, parseError ); } } } } } catch (error) { console.error("Error accessing localStorage during initialization:", error); } } /** * Saves data to the in-memory cache and persists it individually in `localStorage`. * @param path - The unique path/key for the data. * @param data - The data to cache and persist. */ export function saveEntityToCache(path: string, data: object): void { // Update the in-memory cache entityCache.set(path, data); // Persist the data individually in localStorage if (isLocalStorageAvailable) { try { const key = LOCAL_STORAGE_PREFIX + path; const entityString = JSON.stringify(data, customReplacer); localStorage.setItem(key, entityString); } catch (error) { console.error( `Failed to save entity for path "${path}" to localStorage:`, error ); } } } /** * Retrieves an entity from the in-memory cache or `localStorage`. * If the entity is not in the cache but exists in `localStorage`, it loads it into the cache. * @param path - The unique path/key for the entity. * @returns The cached entity or `undefined` if not found. */ export function getEntityFromCache(path: string): object | undefined { // Attempt to retrieve the entity from the in-memory cache if (entityCache.has(path)) { return entityCache.get(path); } // If not in the cache, attempt to load it from localStorage if (isLocalStorageAvailable) { try { const key = LOCAL_STORAGE_PREFIX + path; const entityString = localStorage.getItem(key); if (entityString) { const entity: object = JSON.parse(entityString, customReviver); entityCache.set(path, entity); // Update the cache return entity; } } catch (error) { console.error( `Failed to load entity for path "${path}" from localStorage:`, error ); } } // Entity not found return undefined; } export function hasEntityInCache(path: string): boolean { return entityCache.has(path); } /** * Removes an entity from both the in-memory cache and `localStorage`. * @param path - The unique path/key for the entity to remove. */ export function removeEntityFromCache(path: string): void { console.debug("Removing entity from cache", path); // Remove from the in-memory cache entityCache.delete(path); // Remove from localStorage if (isLocalStorageAvailable) { try { const key = LOCAL_STORAGE_PREFIX + path; localStorage.removeItem(key); } catch (error) { console.error( `Failed to remove entity for path "${path}" from localStorage:`, error ); } } } /** * Clears the entire in-memory cache and removes all related entities from `localStorage`. */ export function clearEntityCache(): void { // Clear the in-memory cache entityCache.clear(); // Remove all entities with the specified prefix from localStorage if (isLocalStorageAvailable) { try { const keysToRemove: string[] = []; for (let i = 0; i < localStorage.length; i++) { const fullKey = localStorage.key(i); if (fullKey && fullKey.startsWith(LOCAL_STORAGE_PREFIX)) { keysToRemove.push(fullKey); } } // Remove the keys after collecting them to avoid issues while iterating keysToRemove.forEach((key) => localStorage.removeItem(key)); } catch (error) { console.error("Failed to clear entity cache from localStorage:", error); } } }