mirror of
https://github.com/ruvnet/RuView
synced 2026-08-08 20:11:43 +00:00
Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Context Cache for prompt and response caching
|
||||
*/
|
||||
|
||||
export class ContextCache {
|
||||
constructor(options = {}) {
|
||||
this.maxSize = options.maxSize || 100;
|
||||
this.ttl = options.ttl || 3600000; // 1 hour default
|
||||
this.cache = new Map();
|
||||
this.stats = {
|
||||
hits: 0,
|
||||
misses: 0,
|
||||
evictions: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached value
|
||||
* @param {string} key - Cache key
|
||||
* @returns {*} Cached value or null
|
||||
*/
|
||||
get(key) {
|
||||
const entry = this.cache.get(key);
|
||||
|
||||
if (!entry) {
|
||||
this.stats.misses++;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check TTL
|
||||
if (Date.now() - entry.timestamp > this.ttl) {
|
||||
this.cache.delete(key);
|
||||
this.stats.misses++;
|
||||
return null;
|
||||
}
|
||||
|
||||
this.stats.hits++;
|
||||
entry.accessCount++;
|
||||
entry.lastAccess = Date.now();
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache value
|
||||
* @param {string} key - Cache key
|
||||
* @param {*} value - Value to cache
|
||||
*/
|
||||
set(key, value) {
|
||||
// Evict if at capacity
|
||||
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
||||
this._evictLRU();
|
||||
}
|
||||
|
||||
this.cache.set(key, {
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
lastAccess: Date.now(),
|
||||
accessCount: 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if key exists
|
||||
*/
|
||||
has(key) {
|
||||
const entry = this.cache.get(key);
|
||||
if (!entry) return false;
|
||||
|
||||
// Check TTL
|
||||
if (Date.now() - entry.timestamp > this.ttl) {
|
||||
this.cache.delete(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache
|
||||
*/
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
this.stats = { hits: 0, misses: 0, evictions: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
...this.stats,
|
||||
size: this.cache.size,
|
||||
hitRate: this.stats.hits / (this.stats.hits + this.stats.misses) || 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict least recently used entry
|
||||
* @private
|
||||
*/
|
||||
_evictLRU() {
|
||||
let oldestKey = null;
|
||||
let oldestAccess = Infinity;
|
||||
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
if (entry.lastAccess < oldestAccess) {
|
||||
oldestAccess = entry.lastAccess;
|
||||
oldestKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldestKey) {
|
||||
this.cache.delete(oldestKey);
|
||||
this.stats.evictions++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* Context caching system for performance optimization
|
||||
*/
|
||||
|
||||
import { CacheStrategy, CacheError } from '../types.js';
|
||||
|
||||
export interface CacheEntry<T = unknown> {
|
||||
key: string;
|
||||
value: T;
|
||||
timestamp: number;
|
||||
ttl: number;
|
||||
hits: number;
|
||||
}
|
||||
|
||||
export interface CacheOptions {
|
||||
strategy: CacheStrategy;
|
||||
ttl: number;
|
||||
maxSize?: number;
|
||||
onEvict?: (key: string, value: unknown) => void;
|
||||
}
|
||||
|
||||
export abstract class CacheStore {
|
||||
abstract get<T>(key: string): Promise<T | null>;
|
||||
abstract set<T>(key: string, value: T, ttl?: number): Promise<void>;
|
||||
abstract has(key: string): Promise<boolean>;
|
||||
abstract delete(key: string): Promise<boolean>;
|
||||
abstract clear(): Promise<void>;
|
||||
abstract size(): Promise<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory cache implementation with LRU eviction
|
||||
*/
|
||||
export class MemoryCache extends CacheStore {
|
||||
private cache: Map<string, CacheEntry>;
|
||||
private maxSize: number;
|
||||
private defaultTTL: number;
|
||||
private onEvict?: (key: string, value: unknown) => void;
|
||||
|
||||
constructor(options: Omit<CacheOptions, 'strategy'>) {
|
||||
super();
|
||||
this.cache = new Map();
|
||||
this.maxSize = options.maxSize || 1000;
|
||||
this.defaultTTL = options.ttl;
|
||||
this.onEvict = options.onEvict;
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | null> {
|
||||
const entry = this.cache.get(key);
|
||||
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if expired
|
||||
if (Date.now() - entry.timestamp > entry.ttl * 1000) {
|
||||
await this.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update hits and move to end (LRU)
|
||||
entry.hits++;
|
||||
this.cache.delete(key);
|
||||
this.cache.set(key, entry);
|
||||
|
||||
return entry.value as T;
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
|
||||
// Evict if at max size
|
||||
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
||||
await this.evictLRU();
|
||||
}
|
||||
|
||||
const entry: CacheEntry<T> = {
|
||||
key,
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
ttl: ttl || this.defaultTTL,
|
||||
hits: 0
|
||||
};
|
||||
|
||||
this.cache.set(key, entry);
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
const value = await this.get(key);
|
||||
return value !== null;
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<boolean> {
|
||||
const entry = this.cache.get(key);
|
||||
const deleted = this.cache.delete(key);
|
||||
|
||||
if (deleted && entry && this.onEvict) {
|
||||
this.onEvict(key, entry.value);
|
||||
}
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
if (this.onEvict) {
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
this.onEvict(key, entry.value);
|
||||
}
|
||||
}
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
async size(): Promise<number> {
|
||||
return this.cache.size;
|
||||
}
|
||||
|
||||
private async evictLRU(): Promise<void> {
|
||||
// First entry is least recently used
|
||||
const firstKey = this.cache.keys().next().value;
|
||||
if (firstKey) {
|
||||
await this.delete(firstKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
getStats() {
|
||||
let totalHits = 0;
|
||||
let expiredCount = 0;
|
||||
const now = Date.now();
|
||||
|
||||
for (const entry of this.cache.values()) {
|
||||
totalHits += entry.hits;
|
||||
if (now - entry.timestamp > entry.ttl * 1000) {
|
||||
expiredCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
size: this.cache.size,
|
||||
maxSize: this.maxSize,
|
||||
totalHits,
|
||||
expiredCount,
|
||||
hitRate: totalHits / (this.cache.size || 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op cache for disabled caching
|
||||
*/
|
||||
export class NoCache extends CacheStore {
|
||||
async get<T>(): Promise<T | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async set<T>(): Promise<void> {
|
||||
// No-op
|
||||
}
|
||||
|
||||
async has(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
async delete(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
// No-op
|
||||
}
|
||||
|
||||
async size(): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache manager factory
|
||||
*/
|
||||
export class CacheManager {
|
||||
private store: CacheStore;
|
||||
|
||||
constructor(options: CacheOptions) {
|
||||
switch (options.strategy) {
|
||||
case 'memory':
|
||||
this.store = new MemoryCache(options);
|
||||
break;
|
||||
case 'none':
|
||||
this.store = new NoCache();
|
||||
break;
|
||||
case 'disk':
|
||||
// TODO: Implement disk cache
|
||||
throw new CacheError('Disk cache not yet implemented', { strategy: 'disk' });
|
||||
default:
|
||||
throw new CacheError(`Unknown cache strategy: ${options.strategy}`, {
|
||||
strategy: options.strategy
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from cache
|
||||
*/
|
||||
async get<T>(key: string): Promise<T | null> {
|
||||
try {
|
||||
return await this.store.get<T>(key);
|
||||
} catch (error) {
|
||||
throw new CacheError('Failed to get cache value', { key, error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value in cache
|
||||
*/
|
||||
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
|
||||
try {
|
||||
await this.store.set(key, value, ttl);
|
||||
} catch (error) {
|
||||
throw new CacheError('Failed to set cache value', { key, error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if key exists in cache
|
||||
*/
|
||||
async has(key: string): Promise<boolean> {
|
||||
try {
|
||||
return await this.store.has(key);
|
||||
} catch (error) {
|
||||
throw new CacheError('Failed to check cache key', { key, error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete key from cache
|
||||
*/
|
||||
async delete(key: string): Promise<boolean> {
|
||||
try {
|
||||
return await this.store.delete(key);
|
||||
} catch (error) {
|
||||
throw new CacheError('Failed to delete cache key', { key, error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cache entries
|
||||
*/
|
||||
async clear(): Promise<void> {
|
||||
try {
|
||||
await this.store.clear();
|
||||
} catch (error) {
|
||||
throw new CacheError('Failed to clear cache', { error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache size
|
||||
*/
|
||||
async size(): Promise<number> {
|
||||
try {
|
||||
return await this.store.size();
|
||||
} catch (error) {
|
||||
throw new CacheError('Failed to get cache size', { error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate cache key from parameters
|
||||
*/
|
||||
static generateKey(prefix: string, params: Record<string, unknown>): string {
|
||||
const sorted = Object.keys(params)
|
||||
.sort()
|
||||
.map(key => `${key}:${JSON.stringify(params[key])}`)
|
||||
.join('|');
|
||||
return `${prefix}:${sorted}`;
|
||||
}
|
||||
}
|
||||
|
||||
export { CacheStrategy, CacheError };
|
||||
Reference in New Issue
Block a user