linxus fs fixes
This commit is contained in:
parent
ac23b70146
commit
0b7846fe67
292 changed files with 41947 additions and 41947 deletions
518
libs/cache/src/providers/memory-cache.ts
vendored
518
libs/cache/src/providers/memory-cache.ts
vendored
|
|
@ -1,259 +1,259 @@
|
|||
import { getLogger } from '@stock-bot/logger';
|
||||
import { CacheProvider, CacheOptions, CacheStats } from '../types';
|
||||
|
||||
interface CacheEntry<T> {
|
||||
value: T;
|
||||
expiry: number;
|
||||
accessed: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory cache provider with LRU eviction and comprehensive metrics
|
||||
*/
|
||||
export class MemoryCache implements CacheProvider {
|
||||
private store = new Map<string, CacheEntry<any>>();
|
||||
private logger = getLogger('memory-cache');
|
||||
private defaultTTL: number;
|
||||
private keyPrefix: string;
|
||||
private maxItems: number;
|
||||
private enableMetrics: boolean;
|
||||
private startTime = Date.now();
|
||||
|
||||
private stats: CacheStats = {
|
||||
hits: 0,
|
||||
misses: 0,
|
||||
errors: 0,
|
||||
hitRate: 0,
|
||||
total: 0,
|
||||
uptime: 0
|
||||
};
|
||||
|
||||
constructor(options: CacheOptions = {}) {
|
||||
this.defaultTTL = options.ttl ?? 3600; // 1 hour default
|
||||
this.keyPrefix = options.keyPrefix ?? 'cache:';
|
||||
this.maxItems = options.maxMemoryItems ?? 1000;
|
||||
this.enableMetrics = options.enableMetrics ?? true;
|
||||
|
||||
this.logger.info('Memory cache initialized', {
|
||||
maxItems: this.maxItems,
|
||||
defaultTTL: this.defaultTTL,
|
||||
enableMetrics: this.enableMetrics
|
||||
});
|
||||
|
||||
// Cleanup expired entries every 5 minutes
|
||||
setInterval(() => this.cleanup(), 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
private getKey(key: string): string {
|
||||
return `${this.keyPrefix}${key}`;
|
||||
}
|
||||
|
||||
private updateStats(hit: boolean, error = false): void {
|
||||
if (!this.enableMetrics) return;
|
||||
|
||||
if (error) {
|
||||
this.stats.errors++;
|
||||
} else if (hit) {
|
||||
this.stats.hits++;
|
||||
} else {
|
||||
this.stats.misses++;
|
||||
}
|
||||
|
||||
this.stats.total = this.stats.hits + this.stats.misses;
|
||||
this.stats.hitRate = this.stats.total > 0 ? this.stats.hits / this.stats.total : 0;
|
||||
this.stats.uptime = Date.now() - this.startTime;
|
||||
}
|
||||
|
||||
private cleanup(): void {
|
||||
const now = Date.now();
|
||||
let cleaned = 0;
|
||||
|
||||
for (const [key, entry] of this.store.entries()) {
|
||||
if (entry.expiry < now) {
|
||||
this.store.delete(key);
|
||||
cleaned++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cleaned > 0) {
|
||||
this.logger.debug('Cleaned expired entries', {
|
||||
cleaned,
|
||||
remaining: this.store.size
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private evictLRU(): void {
|
||||
if (this.store.size <= this.maxItems) return;
|
||||
|
||||
// Find least recently accessed item
|
||||
let oldestKey = '';
|
||||
let oldestAccess = Date.now();
|
||||
|
||||
for (const [key, entry] of this.store.entries()) {
|
||||
if (entry.accessed < oldestAccess) {
|
||||
oldestAccess = entry.accessed;
|
||||
oldestKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldestKey) {
|
||||
this.store.delete(oldestKey);
|
||||
this.logger.debug('Evicted LRU entry', { key: oldestKey });
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | null> {
|
||||
try {
|
||||
const fullKey = this.getKey(key);
|
||||
const entry = this.store.get(fullKey);
|
||||
|
||||
if (!entry) {
|
||||
this.updateStats(false);
|
||||
this.logger.debug('Cache miss', { key });
|
||||
return null;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
if (entry.expiry < now) {
|
||||
this.store.delete(fullKey);
|
||||
this.updateStats(false);
|
||||
this.logger.debug('Cache miss (expired)', { key });
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update access time for LRU
|
||||
entry.accessed = now;
|
||||
this.updateStats(true);
|
||||
this.logger.debug('Cache hit', { key, hitRate: this.stats.hitRate });
|
||||
|
||||
return entry.value;
|
||||
} catch (error) {
|
||||
this.updateStats(false, true);
|
||||
this.logger.error('Cache get error', {
|
||||
key,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
|
||||
try {
|
||||
const fullKey = this.getKey(key);
|
||||
const now = Date.now();
|
||||
const expiry = now + 1000 * (ttl ?? this.defaultTTL);
|
||||
|
||||
// Evict if necessary
|
||||
this.evictLRU();
|
||||
|
||||
this.store.set(fullKey, {
|
||||
value,
|
||||
expiry,
|
||||
accessed: now
|
||||
});
|
||||
|
||||
this.logger.debug('Cache set', { key, ttl: ttl ?? this.defaultTTL });
|
||||
} catch (error) {
|
||||
this.updateStats(false, true);
|
||||
this.logger.error('Cache set error', {
|
||||
key,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
try {
|
||||
const fullKey = this.getKey(key);
|
||||
const deleted = this.store.delete(fullKey);
|
||||
this.logger.debug('Cache delete', { key, deleted });
|
||||
} catch (error) {
|
||||
this.updateStats(false, true);
|
||||
this.logger.error('Cache delete error', {
|
||||
key,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async exists(key: string): Promise<boolean> {
|
||||
try {
|
||||
const fullKey = this.getKey(key);
|
||||
const entry = this.store.get(fullKey);
|
||||
|
||||
if (!entry) return false;
|
||||
|
||||
// Check if expired
|
||||
if (entry.expiry < Date.now()) {
|
||||
this.store.delete(fullKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.updateStats(false, true);
|
||||
this.logger.error('Cache exists error', {
|
||||
key,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
try {
|
||||
const size = this.store.size;
|
||||
this.store.clear();
|
||||
this.logger.info('Cache cleared', { entriesDeleted: size });
|
||||
} catch (error) {
|
||||
this.updateStats(false, true);
|
||||
this.logger.error('Cache clear error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async health(): Promise<boolean> {
|
||||
try {
|
||||
// Simple health check - try to set and get a test value
|
||||
await this.set('__health_check__', 'ok', 1);
|
||||
const result = await this.get('__health_check__');
|
||||
await this.del('__health_check__');
|
||||
return result === 'ok';
|
||||
} catch (error) {
|
||||
this.logger.error('Memory cache health check failed', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getStats(): CacheStats {
|
||||
return {
|
||||
...this.stats,
|
||||
uptime: Date.now() - this.startTime
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional memory cache specific stats
|
||||
*/
|
||||
getMemoryStats() {
|
||||
return {
|
||||
...this.getStats(),
|
||||
entries: this.store.size,
|
||||
maxItems: this.maxItems,
|
||||
memoryUsage: this.estimateMemoryUsage()
|
||||
};
|
||||
}
|
||||
|
||||
private estimateMemoryUsage(): number {
|
||||
// Rough estimation of memory usage in bytes
|
||||
let bytes = 0;
|
||||
for (const [key, entry] of this.store.entries()) {
|
||||
bytes += key.length * 2; // UTF-16 characters
|
||||
bytes += JSON.stringify(entry.value).length * 2;
|
||||
bytes += 24; // Overhead for entry object
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
import { CacheProvider, CacheOptions, CacheStats } from '../types';
|
||||
|
||||
interface CacheEntry<T> {
|
||||
value: T;
|
||||
expiry: number;
|
||||
accessed: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory cache provider with LRU eviction and comprehensive metrics
|
||||
*/
|
||||
export class MemoryCache implements CacheProvider {
|
||||
private store = new Map<string, CacheEntry<any>>();
|
||||
private logger = getLogger('memory-cache');
|
||||
private defaultTTL: number;
|
||||
private keyPrefix: string;
|
||||
private maxItems: number;
|
||||
private enableMetrics: boolean;
|
||||
private startTime = Date.now();
|
||||
|
||||
private stats: CacheStats = {
|
||||
hits: 0,
|
||||
misses: 0,
|
||||
errors: 0,
|
||||
hitRate: 0,
|
||||
total: 0,
|
||||
uptime: 0
|
||||
};
|
||||
|
||||
constructor(options: CacheOptions = {}) {
|
||||
this.defaultTTL = options.ttl ?? 3600; // 1 hour default
|
||||
this.keyPrefix = options.keyPrefix ?? 'cache:';
|
||||
this.maxItems = options.maxMemoryItems ?? 1000;
|
||||
this.enableMetrics = options.enableMetrics ?? true;
|
||||
|
||||
this.logger.info('Memory cache initialized', {
|
||||
maxItems: this.maxItems,
|
||||
defaultTTL: this.defaultTTL,
|
||||
enableMetrics: this.enableMetrics
|
||||
});
|
||||
|
||||
// Cleanup expired entries every 5 minutes
|
||||
setInterval(() => this.cleanup(), 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
private getKey(key: string): string {
|
||||
return `${this.keyPrefix}${key}`;
|
||||
}
|
||||
|
||||
private updateStats(hit: boolean, error = false): void {
|
||||
if (!this.enableMetrics) return;
|
||||
|
||||
if (error) {
|
||||
this.stats.errors++;
|
||||
} else if (hit) {
|
||||
this.stats.hits++;
|
||||
} else {
|
||||
this.stats.misses++;
|
||||
}
|
||||
|
||||
this.stats.total = this.stats.hits + this.stats.misses;
|
||||
this.stats.hitRate = this.stats.total > 0 ? this.stats.hits / this.stats.total : 0;
|
||||
this.stats.uptime = Date.now() - this.startTime;
|
||||
}
|
||||
|
||||
private cleanup(): void {
|
||||
const now = Date.now();
|
||||
let cleaned = 0;
|
||||
|
||||
for (const [key, entry] of this.store.entries()) {
|
||||
if (entry.expiry < now) {
|
||||
this.store.delete(key);
|
||||
cleaned++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cleaned > 0) {
|
||||
this.logger.debug('Cleaned expired entries', {
|
||||
cleaned,
|
||||
remaining: this.store.size
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private evictLRU(): void {
|
||||
if (this.store.size <= this.maxItems) return;
|
||||
|
||||
// Find least recently accessed item
|
||||
let oldestKey = '';
|
||||
let oldestAccess = Date.now();
|
||||
|
||||
for (const [key, entry] of this.store.entries()) {
|
||||
if (entry.accessed < oldestAccess) {
|
||||
oldestAccess = entry.accessed;
|
||||
oldestKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldestKey) {
|
||||
this.store.delete(oldestKey);
|
||||
this.logger.debug('Evicted LRU entry', { key: oldestKey });
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | null> {
|
||||
try {
|
||||
const fullKey = this.getKey(key);
|
||||
const entry = this.store.get(fullKey);
|
||||
|
||||
if (!entry) {
|
||||
this.updateStats(false);
|
||||
this.logger.debug('Cache miss', { key });
|
||||
return null;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
if (entry.expiry < now) {
|
||||
this.store.delete(fullKey);
|
||||
this.updateStats(false);
|
||||
this.logger.debug('Cache miss (expired)', { key });
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update access time for LRU
|
||||
entry.accessed = now;
|
||||
this.updateStats(true);
|
||||
this.logger.debug('Cache hit', { key, hitRate: this.stats.hitRate });
|
||||
|
||||
return entry.value;
|
||||
} catch (error) {
|
||||
this.updateStats(false, true);
|
||||
this.logger.error('Cache get error', {
|
||||
key,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
|
||||
try {
|
||||
const fullKey = this.getKey(key);
|
||||
const now = Date.now();
|
||||
const expiry = now + 1000 * (ttl ?? this.defaultTTL);
|
||||
|
||||
// Evict if necessary
|
||||
this.evictLRU();
|
||||
|
||||
this.store.set(fullKey, {
|
||||
value,
|
||||
expiry,
|
||||
accessed: now
|
||||
});
|
||||
|
||||
this.logger.debug('Cache set', { key, ttl: ttl ?? this.defaultTTL });
|
||||
} catch (error) {
|
||||
this.updateStats(false, true);
|
||||
this.logger.error('Cache set error', {
|
||||
key,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
try {
|
||||
const fullKey = this.getKey(key);
|
||||
const deleted = this.store.delete(fullKey);
|
||||
this.logger.debug('Cache delete', { key, deleted });
|
||||
} catch (error) {
|
||||
this.updateStats(false, true);
|
||||
this.logger.error('Cache delete error', {
|
||||
key,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async exists(key: string): Promise<boolean> {
|
||||
try {
|
||||
const fullKey = this.getKey(key);
|
||||
const entry = this.store.get(fullKey);
|
||||
|
||||
if (!entry) return false;
|
||||
|
||||
// Check if expired
|
||||
if (entry.expiry < Date.now()) {
|
||||
this.store.delete(fullKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.updateStats(false, true);
|
||||
this.logger.error('Cache exists error', {
|
||||
key,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
try {
|
||||
const size = this.store.size;
|
||||
this.store.clear();
|
||||
this.logger.info('Cache cleared', { entriesDeleted: size });
|
||||
} catch (error) {
|
||||
this.updateStats(false, true);
|
||||
this.logger.error('Cache clear error', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async health(): Promise<boolean> {
|
||||
try {
|
||||
// Simple health check - try to set and get a test value
|
||||
await this.set('__health_check__', 'ok', 1);
|
||||
const result = await this.get('__health_check__');
|
||||
await this.del('__health_check__');
|
||||
return result === 'ok';
|
||||
} catch (error) {
|
||||
this.logger.error('Memory cache health check failed', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getStats(): CacheStats {
|
||||
return {
|
||||
...this.stats,
|
||||
uptime: Date.now() - this.startTime
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional memory cache specific stats
|
||||
*/
|
||||
getMemoryStats() {
|
||||
return {
|
||||
...this.getStats(),
|
||||
entries: this.store.size,
|
||||
maxItems: this.maxItems,
|
||||
memoryUsage: this.estimateMemoryUsage()
|
||||
};
|
||||
}
|
||||
|
||||
private estimateMemoryUsage(): number {
|
||||
// Rough estimation of memory usage in bytes
|
||||
let bytes = 0;
|
||||
for (const [key, entry] of this.store.entries()) {
|
||||
bytes += key.length * 2; // UTF-16 characters
|
||||
bytes += JSON.stringify(entry.value).length * 2;
|
||||
bytes += 24; // Overhead for entry object
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue