UNPKG

axiodb

Version:

The Pure JavaScript Alternative to SQLite. Embedded NoSQL database for Node.js with MongoDB-style queries, zero native dependencies, built-in InMemoryCache, and web GUI. Perfect for desktop apps, CLI tools, and embedded systems. No compilation, no platfor

301 lines 12.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InMemoryCache = void 0; /* eslint-disable @typescript-eslint/no-unused-vars */ const os_1 = __importDefault(require("os")); /** * @description This class is responsible for caching data in memory with smart TTL management * @class InMemoryCache * @export InMemoryCache * @version 2.0.0 * @since 23 April 2025 * * Features: * - Random TTL per entry (5-15 minutes) to prevent cache stampede * - Selective cache invalidation by collection/document * - Auto-cleanup of expired entries **/ class InMemoryCache { /** * Creates a new instance of the cache operation class * @param TTL - Time to live in seconds for cache entries. Defaults to 86400 seconds (24 hours) */ constructor(TTL = 86400) { this.tempSearchQuery = []; this.autoResetCacheInterval = 86400; // 24 hours this.ttl = typeof TTL === "string" ? parseInt(TTL) : TTL; this.cacheObject = new Map(); this.tempSearchQuery = []; // this.autoResetCacheInterval is already initialized to 86400 (24 hours) this.autoResetCache(); // Start the auto-reset cache process } /** * Generates a random TTL between 5-15 minutes * This prevents cache stampede (thundering herd problem) * * @returns Random TTL in milliseconds * @private */ generateRandomTTL() { const MIN_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds const MAX_TTL = 15 * 60 * 1000; // 15 minutes in milliseconds return Math.floor(Math.random() * (MAX_TTL - MIN_TTL + 1)) + MIN_TTL; } /** * Sets a value in the cache with the specified key. * Each cache entry gets a random TTL (5-15 minutes) to prevent cache stampede. * * OPTIMIZED: * - Immediate caching without threshold * - Random TTL per entry prevents synchronized expiration * - Pre-computed expiration time for O(1) validation * * @param key - The unique identifier for the cached item * @param value - The value to be stored in the cache * @returns A Promise that resolves when the value has been cached * * @example * ```typescript * await cache.setCache('user-123', { name: 'John', age: 30 }); * ``` */ setCache(key, value) { return __awaiter(this, void 0, void 0, function* () { const ttl = this.generateRandomTTL(); const now = new Date(); const expiresAt = new Date(now.getTime() + ttl); this.cacheObject.set(key, { value: value, registeredAt: now, ttl: ttl, expiresAt: expiresAt, }); return true; }); } setTempSearchQuery(queryString) { return __awaiter(this, void 0, void 0, function* () { // Track query for analytics/monitoring purposes only // No longer blocks caching - immediate caching is now enabled this.tempSearchQuery.push({ queryString: queryString, registeredAt: new Date(), }); return true; }); } /** * Retrieves a value from the cache using the specified key * Validates TTL expiration and auto-deletes expired entries * * @param key - The unique identifier to lookup in the cache * @returns A Promise that resolves to the cached value if found and not expired, false otherwise */ getCache(key) { return __awaiter(this, void 0, void 0, function* () { const cacheItem = this.cacheObject.get(key); if (!cacheItem) { return false; } // Check if expired using pre-computed expiresAt const now = new Date(); if (now >= cacheItem.expiresAt) { // Lazy cleanup: Remove expired entry this.cacheObject.delete(key); return false; } return cacheItem.value; }); } /** * Clears all cached data stored in memory. * Resets the cache object and temporary search query array to their initial empty states. * * @returns A Promise that resolves to true when the cache has been successfully cleared. */ clearAllCache() { return __awaiter(this, void 0, void 0, function* () { // clear all cache this.cacheObject.clear(); this.tempSearchQuery = []; return true; }); } /** * Invalidates all cache entries for a specific collection * Used for selective cache invalidation to preserve unrelated caches * * @param collectionPath - The filesystem path to the collection (e.g., "/db/users") * @returns A Promise that resolves to true when invalidation is complete * * @example * ```typescript * await cache.invalidateByCollection('/db/users'); * // Only /db/users cache cleared, /db/orders cache remains * ``` */ invalidateByCollection(collectionPath) { return __awaiter(this, void 0, void 0, function* () { const keysToDelete = []; // Find all cache keys belonging to this collection for (const [key] of this.cacheObject.entries()) { // Cache keys format: {collectionPath}::{query}::{limit}::{skip}::{sort} if (key.startsWith(`${collectionPath}::`)) { keysToDelete.push(key); } } // Batch delete for performance keysToDelete.forEach(key => this.cacheObject.delete(key)); return true; }); } /** * Invalidates cache entries that could be affected by a single document update/delete * * Strategy: Conservative - invalidates entire collection cache because we can't * determine which queries matched this specific document without re-executing them. * * @param collectionPath - The filesystem path to the collection * @param documentId - The ID of the document that was modified (used for logging/future optimization) * @returns A Promise that resolves to true when invalidation is complete * * @example * ```typescript * await cache.invalidateByDocument('/db/users', 'user123'); * ``` */ invalidateByDocument(collectionPath, documentId) { return __awaiter(this, void 0, void 0, function* () { // Conservative approach: invalidate entire collection // Reason: Can't determine which queries matched this document without re-executing // Alternative (complex): Maintain reverse index of documentId -> cache keys return this.invalidateByCollection(collectionPath); }); } /** * Invalidates cache entries affected by multiple document updates/deletes * * @param collectionPath - The filesystem path to the collection * @param documentIds - Array of document IDs that were modified * @returns A Promise that resolves to true when invalidation is complete * * @example * ```typescript * await cache.invalidateByDocuments('/db/users', ['user1', 'user2', 'user3']); * ``` */ invalidateByDocuments(collectionPath, documentIds) { return __awaiter(this, void 0, void 0, function* () { // Same conservative strategy as single document return this.invalidateByCollection(collectionPath); }); } /** * Retrieves detailed information about the current state of the cache. * * This method calculates: * - Total estimated size of the cache in bytes (including keys, values, and timestamps) * - Available system memory (in Node.js environments) * - Number of items currently in the cache * - Number of temporary search queries stored * * @returns {Promise<any>} A promise that resolves to an object containing cache details: * - cacheSizeInBytes: Estimated size of the cache in bytes * - availableMemoryInBytes: Available system memory in bytes (or -1 if not in Node.js) * - cacheItemCount: Number of items in the cache * - tempQueryCount: Number of temporary search queries * - Or false if an error occurs during calculation * * @throws {Error} Logs the error to console but doesn't throw; returns false instead */ getCacheDetails() { return __awaiter(this, void 0, void 0, function* () { try { // Calculate the total size of cache let totalCacheSize = 0; // Convert cache map to entries for size calculation for (const [key, value] of this.cacheObject.entries()) { // Estimate size of key totalCacheSize += key.length * 2; // String characters in JS use ~2 bytes // Estimate size of value using JSON stringify const valueSize = JSON.stringify(value.value).length * 2; totalCacheSize += valueSize; // Add size of Date object (~8 bytes) totalCacheSize += 8; } // Add size of temp search queries totalCacheSize += JSON.stringify(this.tempSearchQuery).length * 2; // Get total memory of the machine let availableMemory = 0; try { // Only works in Node.js environment availableMemory = os_1.default.freemem(); } catch (error) { // If we're in a browser or other environment where os is not available availableMemory = -1; } return { cacheSizeInBytes: totalCacheSize, availableMemoryInBytes: availableMemory, cacheItemCount: this.cacheObject.size, tempQueryCount: this.tempSearchQuery.length, }; } catch (error) { console.error("Error getting cache details:", error); return false; } }); } /** * Sets up an automatic cache reset mechanism. * Periodically cleans up expired cache entries and old search query tracking. * * OPTIMIZED: Uses pre-computed expiresAt for O(1) expiration checks * instead of computing time differences. * * @private * @returns {Promise<void>} A promise that resolves when the interval is set up. */ autoResetCache() { return __awaiter(this, void 0, void 0, function* () { setInterval(() => { // Check if cache is empty if (this.cacheObject.size === 0) { return; } const now = new Date(); const keysToDelete = []; // Collect expired entries using pre-computed expiresAt for (const [key, cacheItem] of this.cacheObject.entries()) { if (now >= cacheItem.expiresAt) { keysToDelete.push(key); } } // Batch delete for performance keysToDelete.forEach(key => this.cacheObject.delete(key)); // Clean up old temp search queries (24-hour retention) this.tempSearchQuery = this.tempSearchQuery.filter((item) => { const diff = Math.abs(now.getTime() - item.registeredAt.getTime()); return diff < this.autoResetCacheInterval * 1000; }); }, parseInt(String(this.ttl))); }); } } exports.InMemoryCache = InMemoryCache; exports.default = new InMemoryCache(86400); // 24 hours //# sourceMappingURL=memory.operation.js.map