UNPKG

@firecms/core

Version:

Awesome Firebase/Firestore-based headless open-source CMS

220 lines (193 loc) 6.73 kB
import { EntityReference, GeoPoint, Vector } from "../types"; import { isObject, isPlainObject } from "./objects"; // 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, databaseId: value.databaseId }; } // 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, value.databaseId); case "GeoPoint": return new GeoPoint(value.latitude, value.longitude); case "Vector": return new Vector(value.value); default: return value; } } return value; } /** * 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 { // Persist the data individually in localStorage if (isLocalStorageAvailable) { try { const key = LOCAL_STORAGE_PREFIX + path; const entityString = JSON.stringify(data, customReplacer); console.debug("Saving entity to localStorage:", { key, entityString }); localStorage.setItem(key, entityString); } catch (error) { console.error( `Failed to save entity for path "${path}" to localStorage:`, error ); } } } export function removeEntityFromMemoryCache(path: string): void { entityCache.delete(path); } export function saveEntityToMemoryCache(path: string, data: object): void { entityCache.set(path, data); } export function getEntityFromMemoryCache(path: string): object | undefined { return entityCache.get(path); } export function hasEntityInCache(path: string): boolean { return entityCache.has(path); } /** * 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 { // 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); return entity; } } catch (error) { console.error( `Failed to load entity for path "${path}" from localStorage:`, error ); } } // Entity not found return undefined; } /** * 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 { 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); } } } export function flattenKeys(obj: any, prefix = "", result: string[] = []): string[] { if (isObject(obj) || Array.isArray(obj)) { const plainObject = isPlainObject(obj); if (!plainObject && prefix) { result.push(prefix); } else { for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const newKey = prefix ? Array.isArray(obj) ? `${prefix}[${key}]` : `${prefix}.${key}` : key; if (isObject(obj[key]) || Array.isArray(obj[key])) { flattenKeys(obj[key], newKey, result); } else { result.push(newKey); } } } } } return result; }