@varia-bly/variably-sdk
Version:
Official JavaScript/TypeScript SDK for Variably feature flags, experimentation, and real-time dynamic configurations
306 lines • 8.99 kB
JavaScript
"use strict";
/**
* Cache management for Variably SDK
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserCache = exports.MemoryCache = exports.CacheManager = void 0;
class CacheManager {
constructor(config, logger) {
this.cleanupInterval = null;
this.config = {
ttl: config.ttl || 300000, // 5 minutes
maxSize: config.maxSize || 1000,
enabled: config.enabled !== false
};
this.logger = logger;
this.cache = new Map();
if (this.config.enabled) {
this.startCleanupTimer();
}
}
/**
* Get a value from cache
*/
get(key) {
if (!this.config.enabled) {
return null;
}
const entry = this.cache.get(key);
if (!entry) {
return null;
}
// Check if entry has expired
if (this.isExpired(entry)) {
this.cache.delete(key);
this.logger.debug('Cache entry expired', { key });
return null;
}
this.logger.debug('Cache hit', { key });
return entry.value;
}
/**
* Set a value in cache
*/
set(key, value, customTtl) {
if (!this.config.enabled) {
return;
}
// Enforce max size by removing oldest entries
if (this.cache.size >= this.config.maxSize) {
this.evictOldest();
}
const ttl = customTtl || this.config.ttl;
const entry = {
value,
timestamp: Date.now(),
ttl
};
this.cache.set(key, entry);
this.logger.debug('Cache entry set', { key, ttl });
}
/**
* Remove a value from cache
*/
delete(key) {
const deleted = this.cache.delete(key);
if (deleted) {
this.logger.debug('Cache entry deleted', { key });
}
return deleted;
}
/**
* Clear all cache entries
*/
clear() {
const size = this.cache.size;
this.cache.clear();
this.logger.debug('Cache cleared', { entriesRemoved: size });
}
/**
* Clear cache entries matching a pattern (supports * wildcard)
*/
clearByPattern(pattern) {
if (!this.config.enabled) {
return 0;
}
const keysToDelete = [];
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
for (const key of this.cache.keys()) {
if (regex.test(key)) {
keysToDelete.push(key);
}
}
for (const key of keysToDelete) {
this.cache.delete(key);
}
if (keysToDelete.length > 0) {
this.logger.debug('Cache entries cleared by pattern', {
pattern,
entriesRemoved: keysToDelete.length
});
}
return keysToDelete.length;
}
/**
* Get cache statistics
*/
getStats() {
return {
size: this.cache.size,
maxSize: this.config.maxSize,
hitRate: 0, // Would need to track hits/misses for this
enabled: this.config.enabled
};
}
/**
* Check if cache entry has expired
*/
isExpired(entry) {
return Date.now() - entry.timestamp > entry.ttl;
}
/**
* Remove oldest entries when cache is full
*/
evictOldest() {
let oldestKey = null;
let oldestTimestamp = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (entry.timestamp < oldestTimestamp) {
oldestTimestamp = entry.timestamp;
oldestKey = key;
}
}
if (oldestKey) {
this.cache.delete(oldestKey);
this.logger.debug('Cache entry evicted (oldest)', { key: oldestKey });
}
}
/**
* Remove expired entries from cache
*/
cleanup() {
const now = Date.now();
const keysToDelete = [];
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > entry.ttl) {
keysToDelete.push(key);
}
}
for (const key of keysToDelete) {
this.cache.delete(key);
}
if (keysToDelete.length > 0) {
this.logger.debug('Cache cleanup completed', { expiredEntries: keysToDelete.length });
}
}
/**
* Start periodic cleanup timer
*/
startCleanupTimer() {
// Run cleanup every minute
this.cleanupInterval = setInterval(() => {
this.cleanup();
}, 60000);
}
/**
* Stop cleanup timer and clear cache
*/
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
this.clear();
}
}
exports.CacheManager = CacheManager;
/**
* Simple in-memory cache for Node.js environments
*/
class MemoryCache extends CacheManager {
constructor(config, logger) {
super(config, logger);
}
}
exports.MemoryCache = MemoryCache;
/**
* Browser-compatible cache using sessionStorage/localStorage
*/
class BrowserCache {
constructor(config, logger, useSessionStorage = false) {
this.config = {
ttl: config.ttl || 300000,
maxSize: config.maxSize || 1000,
enabled: config.enabled !== false
};
this.logger = logger;
this.keyPrefix = 'variably_cache_';
if (typeof window !== 'undefined') {
this.storage = useSessionStorage ? sessionStorage : localStorage;
}
else {
// Fallback for non-browser environments
this.storage = {
getItem: () => null,
setItem: () => { },
removeItem: () => { },
clear: () => { },
length: 0,
key: () => null
};
}
}
get(key) {
if (!this.config.enabled) {
return null;
}
try {
const item = this.storage.getItem(this.keyPrefix + key);
if (!item) {
return null;
}
const entry = JSON.parse(item);
// Check expiration
if (Date.now() - entry.timestamp > entry.ttl) {
this.delete(key);
return null;
}
return entry.value;
}
catch (error) {
this.logger.warn('Failed to read from browser cache', { key, error: error instanceof Error ? error.message : String(error) });
return null;
}
}
set(key, value, customTtl) {
if (!this.config.enabled) {
return;
}
try {
const ttl = customTtl || this.config.ttl;
const entry = {
value,
timestamp: Date.now(),
ttl
};
this.storage.setItem(this.keyPrefix + key, JSON.stringify(entry));
}
catch (error) {
this.logger.warn('Failed to write to browser cache', { key, error: error instanceof Error ? error.message : String(error) });
}
}
delete(key) {
try {
const fullKey = this.keyPrefix + key;
const existed = this.storage.getItem(fullKey) !== null;
this.storage.removeItem(fullKey);
return existed;
}
catch (error) {
this.logger.warn('Failed to delete from browser cache', { key, error: error instanceof Error ? error.message : String(error) });
return false;
}
}
clear() {
try {
const keys = [];
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);
if (key && key.startsWith(this.keyPrefix)) {
keys.push(key);
}
}
for (const key of keys) {
this.storage.removeItem(key);
}
}
catch (error) {
this.logger.warn('Failed to clear browser cache', { error: error instanceof Error ? error.message : String(error) });
}
}
getStats() {
let size = 0;
try {
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);
if (key && key.startsWith(this.keyPrefix)) {
size++;
}
}
}
catch (error) {
this.logger.warn('Failed to get cache stats', { error: error instanceof Error ? error.message : String(error) });
}
return {
size,
maxSize: this.config.maxSize,
hitRate: 0,
enabled: this.config.enabled
};
}
destroy() {
this.clear();
}
}
exports.BrowserCache = BrowserCache;
//# sourceMappingURL=cache.js.map