linxus fs fixes

This commit is contained in:
Boki 2025-06-09 22:55:51 -04:00
parent ac23b70146
commit 0b7846fe67
292 changed files with 41947 additions and 41947 deletions

View file

@ -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;
}
}