MemoryCacheService.ts

Fast in-memory cache service with LRU eviction and TTL support

MemoryCacheService.tsv1.0.027.1 KB
MemoryCacheService.ts(typescript)
1/**
2 * Memory Cache Service - Sanitized for UCM Publishing
3 * 
4 * This service provides a fast, single-process memory cache with LRU eviction,
5 * TTL support, and automatic cleanup - perfect for development and testing environments.
6 * 
7 * @author UCM Publishing System
8 * @version 1.0.0
9 * @technology in-memory-cache
10 * @category implementations
11 * @dependencies none
12 * 
13 * REQUIRED DEPENDENCIES:
14 * None - This service uses only Node.js built-in functionality
15 * 
16 * This service has no external dependencies and works with any Node.js version 16+
17 */
18
19/**
20 * NOTE: The following interfaces and classes (BaseService, ServiceMetadata, ILogger, 
21 * ConsoleLogger, ICacheService, and related types) are embedded from the 
22 * micro-block architecture framework for portability.
23 * 
24 * RECOMMENDATION: When integrating this service into your project:
25 * - Move these interfaces to shared/reusable files
26 * - Modify to match your existing service architecture
27 * - Integrate with your project's base classes and patterns
28 * - Consider creating a service registry system if not already present
29 * - Implement your own logging system if needed
30 */
31
32// ===== EMBEDDED INTERFACES FROM MICRO-BLOCK ARCHITECTURE =====
33
34/**
35 * Validation rules for operation parameters
36 */
37export interface ParameterValidation {
38  min?: number;                    // Minimum value (numbers) or length (strings/arrays)
39  max?: number;                    // Maximum value (numbers) or length (strings/arrays)
40  options?: string[];              // Valid options for dropdown/select
41  pattern?: string;                // Regex pattern for string validation
42  step?: number;                   // Step increment for numbers
43}
44
45/**
46 * Definition of a parameter for a service operation
47 */
48export interface OperationParameter {
49  name: string;                    // Parameter name (matches method signature)
50  type: 'string' | 'number' | 'boolean' | 'object' | 'array';
51  required: boolean;               // Whether parameter is required
52  defaultValue?: any;              // Default value to use in UI
53  description?: string;            // Human-readable description
54  displayName?: string;            // UI-friendly name (defaults to name)
55  validation?: ParameterValidation; // Validation rules
56  group?: string;                  // Group parameters in UI (e.g., 'Performance', 'Security')
57  sensitive?: boolean;             // Mark as sensitive (password field, etc.)
58}
59
60/**
61 * Definition of a service operation that can be executed via admin interface
62 */
63export interface ServiceOperation {
64  name: string;                    // Method name to call (e.g., 'start', 'stop')
65  displayName: string;             // Human-friendly name for UI
66  description: string;             // What this operation does
67  parameters: OperationParameter[]; // Parameters this operation accepts
68  category?: string;               // Group operations (e.g., 'control', 'configuration', 'maintenance')
69  permissions?: string[];          // Required permissions to execute
70  dangerous?: boolean;             // Requires confirmation dialog
71  confirmationMessage?: string;    // Custom confirmation message for dangerous operations
72  icon?: string;                   // UI icon name/class
73  buttonStyle?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger'; // UI button styling
74}
75
76/**
77 * Result of executing an operation
78 */
79export interface OperationResult<T = any> {
80  success: boolean;
81  data?: T;
82  message?: string;
83  error?: string;
84  metadata?: Record<string, any>;
85}
86
87/**
88 * Service metadata interface for comprehensive service discovery and selection
89 */
90export interface ServiceMetadata {
91  // === Identity & Basic Info ===
92  name: string;                    // Unique service identifier
93  displayName: string;             // Human-friendly name  
94  description: string;             // What this service does
95  contract: string;                // Interface it implements (e.g., 'ICacheService')
96  implementation: string;          // Implementation type (e.g., 'InMemory', 'Redis')
97  version: string;                 // Service implementation version
98  contractVersion: string;         // Interface contract version
99  
100  // === Capabilities & Features ===
101  features: string[];              // Key features this implementation provides
102  limitations?: string[];          // Known limitations or constraints
103  
104  // === Requirements ===
105  requirements: {
106    services?: string[];           // Other service dependencies (e.g., ['IDatabaseService'])
107    runtime?: string[];            // External dependencies (e.g., ['Redis 6.0+', 'Node.js 18+'])
108    configuration: {
109      required: string[];          // Required config keys (e.g., ['REDIS_URL'])
110      optional?: string[];         // Optional config keys (e.g., ['REDIS_POOL_SIZE'])
111    };
112  };
113  
114  // === Use Case Recommendations ===
115  recommendations: {
116    idealFor: string[];            // Perfect use cases
117    acceptableFor?: string[];      // Works but not optimal
118    notRecommendedFor?: string[];  // Poor fit scenarios
119  };
120  
121  // === Configuration Schema (optional) ===
122  configurationSchema?: {
123    [key: string]: {
124      type: 'string' | 'number' | 'boolean' | 'array' | 'object';
125      description: string;
126      default?: any;
127      validation?: string;         // Validation rule or regex
128      example?: any;
129    };
130  };
131}
132
133/**
134 * Abstract base class for all services in the micro-block architecture.
135 * Enforces metadata requirements for service discovery and selection.
136 */
137export abstract class BaseService {
138  /**
139   * Static metadata that describes this service implementation.
140   * Must be accessible before instantiation for discovery purposes.
141   */
142  static readonly metadata: ServiceMetadata;
143  
144  /**
145   * Get the metadata for this service.
146   * Returns the static metadata property.
147   */
148  abstract getMetadata(): ServiceMetadata;
149  
150  /**
151   * Initialize the service with optional configuration.
152   * Override this method to implement service-specific initialization.
153   */
154  abstract initialize(config?: Record<string, any>): Promise<void> | void;
155  
156  /**
157   * Health check for the service.
158   * Override this method to implement service-specific health checks.
159   */
160  abstract isHealthy(): Promise<boolean> | boolean;
161  
162  /**
163   * Graceful shutdown of the service.
164   * Override this method to implement service-specific cleanup.
165   */
166  abstract destroy(): Promise<void> | void;
167  
168  /**
169   * Get service-specific status information.
170   * Override this method to provide custom status details.
171   */
172  getStatus(): Record<string, any> {
173    return {
174      healthy: this.isHealthy(),
175      metadata: this.getMetadata()
176    };
177  }
178  
179  /**
180   * Get available operations for this service.
181   * Override this method to expose service-specific operations for admin interface.
182   * Can be async to allow services to determine available operations based on current state.
183   */
184  getOperations(): ServiceOperation[] | Promise<ServiceOperation[]> {
185    return [];
186  }
187
188  /**
189   * Execute a service-specific operation with enhanced error handling and result formatting.
190   * This provides a generic way for services to expose custom operations
191   * while maintaining type safety through service-specific interfaces.
192   */
193  async executeOperation<T = any>(
194    operation: string, 
195    params?: Record<string, any>
196  ): Promise<OperationResult<T>> {
197    try {
198      // Validate that the operation exists - handle both sync and async getOperations
199      const operationsResult = this.getOperations();
200      const availableOperations = await Promise.resolve(operationsResult);
201      const operationDef = availableOperations.find((op: ServiceOperation) => op.name === operation);
202      
203      if (!operationDef) {
204        return {
205          success: false,
206          error: `Operation '${operation}' is not supported by service '${this.getMetadata().name}'`,
207          metadata: { availableOperations: availableOperations.map((op: ServiceOperation) => op.name) }
208        };
209      }
210
211      // Validate parameters
212      const validationResult = this.validateOperationParameters(operationDef, params || {});
213      if (!validationResult.valid) {
214        return {
215          success: false,
216          error: `Parameter validation failed: ${validationResult.errors.join(', ')}`,
217          metadata: { validationErrors: validationResult.errors }
218        };
219      }
220
221      // Execute the operation
222      const result = await this.executeOperationInternal(operation, params || {});
223      
224      return {
225        success: true,
226        data: result,
227        message: `Operation '${operation}' completed successfully`,
228        metadata: { operation: operationDef.name, executedAt: new Date().toISOString() }
229      };
230
231    } catch (error) {
232      return {
233        success: false,
234        error: error instanceof Error ? error.message : String(error),
235        metadata: { operation, executedAt: new Date().toISOString() }
236      };
237    }
238  }
239
240  /**
241   * Internal operation execution - override this in derived classes.
242   * This method should handle the actual operation logic.
243   */
244  protected async executeOperationInternal<T = any>(
245    operation: string, 
246    params: Record<string, any>
247  ): Promise<T> {
248    throw new Error(
249      `Operation '${operation}' is not implemented by service '${this.getMetadata().name}'`
250    );
251  }
252  
253  /**
254   * Validate operation parameters against their definitions.
255   */
256  protected validateOperationParameters(
257    operation: ServiceOperation, 
258    params: Record<string, any>
259  ): { valid: boolean; errors: string[] } {
260    const errors: string[] = [];
261
262    // Check required parameters
263    for (const param of operation.parameters) {
264      if (param.required && (params[param.name] === undefined || params[param.name] === null)) {
265        errors.push(`Required parameter '${param.name}' is missing`);
266        continue;
267      }
268
269      const value = params[param.name];
270      if (value !== undefined && value !== null) {
271        // Type validation
272        const actualType = Array.isArray(value) ? 'array' : typeof value;
273        if (actualType !== param.type) {
274          errors.push(`Parameter '${param.name}' must be of type '${param.type}', got '${actualType}'`);
275          continue;
276        }
277
278        // Validation rules
279        if (param.validation) {
280          const validationErrors = this.validateParameterValue(param.name, value, param.validation);
281          errors.push(...validationErrors);
282        }
283      }
284    }
285
286    // Check for unknown parameters
287    const allowedParams = new Set(operation.parameters.map(p => p.name));
288    for (const paramName of Object.keys(params)) {
289      if (!allowedParams.has(paramName)) {
290        errors.push(`Unknown parameter '${paramName}'`);
291      }
292    }
293
294    return { valid: errors.length === 0, errors };
295  }
296
297  /**
298   * Validate a single parameter value against validation rules.
299   */
300  private validateParameterValue(name: string, value: any, validation: ParameterValidation): string[] {
301    const errors: string[] = [];
302
303    // Min/Max validation
304    if (typeof value === 'number') {
305      if (validation.min !== undefined && value < validation.min) {
306        errors.push(`Parameter '${name}' must be at least ${validation.min}`);
307      }
308      if (validation.max !== undefined && value > validation.max) {
309        errors.push(`Parameter '${name}' must be at most ${validation.max}`);
310      }
311    }
312
313    // String/Array length validation
314    if (typeof value === 'string' || Array.isArray(value)) {
315      if (validation.min !== undefined && value.length < validation.min) {
316        errors.push(`Parameter '${name}' must have at least ${validation.min} characters/items`);
317      }
318      if (validation.max !== undefined && value.length > validation.max) {
319        errors.push(`Parameter '${name}' must have at most ${validation.max} characters/items`);
320      }
321    }
322
323    // Options validation (enum-like)
324    if (validation.options && !validation.options.includes(String(value))) {
325      errors.push(`Parameter '${name}' must be one of: ${validation.options.join(', ')}`);
326    }
327
328    // Pattern validation (regex)
329    if (validation.pattern && typeof value === 'string') {
330      const regex = new RegExp(validation.pattern);
331      if (!regex.test(value)) {
332        errors.push(`Parameter '${name}' does not match required pattern`);
333      }
334    }
335
336    return errors;
337  }
338
339  /**
340   * Validate configuration against the service's requirements.
341   * Uses the metadata to check required and optional configuration keys.
342   */
343  protected validateConfiguration(config: Record<string, any>): void {
344    const metadata = this.getMetadata();
345    const required = metadata.requirements.configuration.required || [];
346    
347    // Check required configuration
348    for (const key of required) {
349      if (config[key] === undefined || config[key] === null) {
350        throw new Error(
351          `Required configuration key '${key}' is missing for service '${metadata.name}'`
352        );
353      }
354    }
355    
356    // Validate configuration against schema if provided
357    if (metadata.configurationSchema) {
358      for (const [key, value] of Object.entries(config)) {
359        const schema = metadata.configurationSchema[key];
360        if (schema && !this.validateConfigValue(value, schema)) {
361          throw new Error(
362            `Configuration key '${key}' is invalid for service '${metadata.name}': ${schema.validation || 'Type mismatch'}`
363          );
364        }
365      }
366    }
367  }
368  
369  /**
370   * Validate a single configuration value against its schema.
371   */
372  private validateConfigValue(value: any, schema: NonNullable<ServiceMetadata['configurationSchema']>[string]): boolean {
373    // Basic type checking
374    const actualType = Array.isArray(value) ? 'array' : typeof value;
375    if (actualType !== schema.type) {
376      return false;
377    }
378    
379    // Additional validation rules can be added here
380    if (schema.validation) {
381      // For now, just basic validation - could be extended with regex, range checks, etc.
382      if (schema.type === 'string' && schema.validation.includes('Must be')) {
383        // Could implement more sophisticated validation
384        return value.length > 0;
385      }
386    }
387    
388    return true;
389  }
390}
391
392// ===== END EMBEDDED INTERFACES =====
393
394/**
395 * Cache statistics interface
396 */
397export interface CacheStats {
398  size?: number;
399  keys?: string[];
400  hitRate?: number;
401  missRate?: number;
402  connectionStatus: 'connected' | 'disconnected' | 'connecting';
403  memoryUsage?: number;
404}
405
406/**
407 * Cache entry for batch operations
408 */
409export interface CacheEntry<T> {
410  key: string;
411  value: T;
412  ttlSeconds?: number;
413}
414
415/**
416 * Interface for cache service providing caching with TTL support
417 * Supports both in-memory and network-based cache implementations
418 */
419export interface ICacheService extends BaseService {
420  /**
421   * Get a value from cache
422   * @param key - The cache key
423   * @returns The cached value or null if not found/expired
424   */
425  get<T>(key: string): Promise<T | null>;
426
427  /**
428   * Set a value in cache with TTL
429   * @param key - The cache key
430   * @param value - The value to cache
431   * @param ttlSeconds - Time to live in seconds (default varies by implementation)
432   */
433  set<T>(key: string, value: T, ttlSeconds?: number): Promise<void>;
434
435  /**
436   * Delete a value from cache
437   * @param key - The cache key to delete
438   * @returns True if the key existed and was deleted
439   */
440  delete(key: string): Promise<boolean>;
441
442  /**
443   * Delete all cache entries matching a pattern
444   * @param pattern - The pattern to match (e.g., "user-summaries:123:*")
445   * @returns Number of keys deleted
446   */
447  deletePattern(pattern: string): Promise<number>;
448
449  /**
450   * Clear all cache entries
451   */
452  flush(): Promise<void>;
453
454  /**
455   * Get multiple values from cache
456   * @param keys - Array of cache keys
457   * @returns Array of values in same order as keys, null for missing/expired
458   */
459  mget<T>(keys: string[]): Promise<(T | null)[]>;
460
461  /**
462   * Set multiple values in cache
463   * @param entries - Array of cache entries to set
464   */
465  mset<T>(entries: CacheEntry<T>[]): Promise<void>;
466
467  /**
468   * Connect to the cache backend
469   */
470  connect(): Promise<void>;
471
472  /**
473   * Disconnect from the cache backend
474   */
475  disconnect(): Promise<void>;
476
477  /**
478   * Check if connected to cache backend
479   * @returns True if connected
480   */
481  isConnected(): boolean;
482
483  /**
484   * Get cache statistics and health information
485   * @returns Object containing cache statistics
486   */
487  getStats(): Promise<CacheStats>;
488
489  /**
490   * Check if the cache service is healthy and operational
491   * @returns True if healthy
492   */
493  isHealthy(): Promise<boolean>;
494
495  /**
496   * Cleanup resources and stop background processes
497   */
498  destroy(): Promise<void>;
499}
500
501/**
502 * Logger interface for optional logging support
503 */
504export interface ILogger {
505  debug(message: string, context?: Record<string, any>): void;
506  info(message: string, context?: Record<string, any>): void;
507  warn(message: string, context?: Record<string, any>): void;
508  error(message: string, error?: Error | unknown, context?: Record<string, any>): void;
509}
510
511/**
512 * Simple console logger implementation
513 */
514export class ConsoleLogger implements ILogger {
515  constructor(private serviceName: string) {}
516
517  debug(message: string, context?: Record<string, any>): void {
518    console.log(`[DEBUG] ${this.serviceName}: ${message}`, context ? JSON.stringify(context, null, 2) : '');
519  }
520
521  info(message: string, context?: Record<string, any>): void {
522    console.log(`[INFO] ${this.serviceName}: ${message}`, context ? JSON.stringify(context, null, 2) : '');
523  }
524
525  warn(message: string, context?: Record<string, any>): void {
526    console.warn(`[WARN] ${this.serviceName}: ${message}`, context ? JSON.stringify(context, null, 2) : '');
527  }
528
529  error(message: string, error?: Error | unknown, context?: Record<string, any>): void {
530    const errorDetails = error instanceof Error ? {
531      name: error.name,
532      message: error.message,
533      stack: error.stack
534    } : { error: String(error) };
535
536    console.error(`[ERROR] ${this.serviceName}: ${message}`, {
537      ...errorDetails,
538      ...(context || {})
539    });
540  }
541}
542
543/**
544 * Cache configuration interface
545 */
546export interface MemoryCacheConfig {
547  maxSize?: number;
548  defaultTTL?: number;
549  cleanupInterval?: number;
550}
551
552/**
553 * Internal cache entry with value and expiration
554 */
555interface InternalCacheEntry<T> {
556  value: T;
557  expiresAt: number;
558}
559
560/**
561 * In-memory cache service with TTL support
562 * 
563 * Features:
564 * - Zero configuration required
565 * - Automatic memory management with LRU eviction
566 * - TTL support with automatic cleanup
567 * - Synchronous operations for fast access
568 * - Built-in statistics and monitoring
569 * 
570 * Perfect for:
571 * - Development environments
572 * - Unit testing
573 * - Prototypes and MVPs
574 * - Single-instance applications
575 * - Session storage for simple apps
576 */
577export class MemoryCacheService extends BaseService implements ICacheService {
578  private static instance: MemoryCacheService;
579  private cache: Map<string, InternalCacheEntry<any>>;
580  private hitCount: number = 0;
581  private missCount: number = 0;
582  private connected: boolean = true;
583  private readonly logger: ILogger;
584  private cleanupInterval: NodeJS.Timeout | null = null;
585  private maxSize: number;
586  private defaultTTL: number;
587  private cleanupIntervalMs: number;
588
589  static readonly metadata: ServiceMetadata = {
590    name: 'MemoryCacheService',
591    displayName: 'In-Memory Cache',
592    description: 'Fast, single-process memory cache with LRU eviction for development and testing',
593    contract: 'ICacheService',
594    implementation: 'InMemory',
595    version: '1.0.0',
596    contractVersion: '1.0',
597    
598    features: [
599      'Zero configuration required',
600      'Automatic memory management',
601      'TTL support for entries',
602      'Automatic cleanup of expired entries',
603      'Synchronous operations',
604      'Built-in statistics'
605    ],
606    
607    limitations: [
608      'Data lost on process restart',
609      'Single process only - no sharing between instances',
610      'Memory limited by Node.js heap size',
611      'No persistence across restarts'
612    ],
613    
614    requirements: {
615      configuration: {
616        required: [],
617        optional: ['maxSize', 'defaultTTL', 'cleanupInterval']
618      }
619    },
620    
621    recommendations: {
622      idealFor: [
623        'Development environments',
624        'Unit testing',
625        'Prototypes and MVPs',
626        'Single-instance applications',
627        'Session storage for simple apps'
628      ],
629      acceptableFor: [
630        'Small production applications with single server',
631        'Temporary data caching'
632      ],
633      notRecommendedFor: [
634        'Production multi-server deployments',
635        'Data that must persist between restarts',
636        'Distributed applications',
637        'Large datasets (>1GB)'
638      ]
639    },
640    
641    configurationSchema: {
642      maxSize: {
643        type: 'number',
644        description: 'Maximum number of items to store in cache',
645        default: 10000,
646        validation: 'Must be positive integer',
647        example: 50000
648      },
649      defaultTTL: {
650        type: 'number',
651        description: 'Default TTL in seconds for cache entries',
652        default: 300,
653        validation: 'Must be positive number',
654        example: 3600
655      },
656      cleanupInterval: {
657        type: 'number',
658        description: 'Cleanup interval in milliseconds for expired entries',
659        default: 60000,
660        validation: 'Must be positive number',
661        example: 30000
662      }
663    }
664  };
665
666  private constructor(config?: MemoryCacheConfig, logger?: ILogger) {
667    super();
668    this.logger = logger || new ConsoleLogger('MemoryCacheService');
669    this.cache = new Map();
670    
671    // Set configuration with defaults
672    this.maxSize = config?.maxSize || 10000;
673    this.defaultTTL = config?.defaultTTL || 300;
674    this.cleanupIntervalMs = config?.cleanupInterval || 60000;
675    
676    // Start cleanup interval
677    this.startCleanupInterval();
678    
679    this.logger.debug('MemoryCacheService initialized', {
680      maxSize: this.maxSize,
681      defaultTTL: this.defaultTTL,
682      cleanupInterval: this.cleanupIntervalMs
683    });
684  }
685
686  public static getInstance(config?: MemoryCacheConfig): MemoryCacheService {
687    if (!MemoryCacheService.instance) {
688      MemoryCacheService.instance = new MemoryCacheService(config);
689    }
690    return MemoryCacheService.instance;
691  }
692
693  /**
694   * Get a value from cache
695   */
696  public async get<T>(key: string): Promise<T | null> {
697    const entry = this.cache.get(key);
698    
699    if (!entry) {
700      this.missCount++;
701      return null;
702    }
703    
704    // Check if expired
705    if (Date.now() > entry.expiresAt) {
706      this.cache.delete(key);
707      this.missCount++;
708      return null;
709    }
710    
711    this.hitCount++;
712    return entry.value as T;
713  }
714
715  /**
716   * Set a value in cache with TTL
717   */
718  public async set<T>(key: string, value: T, ttlSeconds?: number): Promise<void> {
719    const ttl = ttlSeconds || this.defaultTTL;
720    
721    // Check if we need to evict items to make room
722    if (this.cache.size >= this.maxSize) {
723      this.evictOldest();
724    }
725    
726    const expiresAt = Date.now() + (ttl * 1000);
727    this.cache.set(key, { value, expiresAt });
728    this.logger.debug(`Cache set: ${key}`, { ttl });
729  }
730
731  /**
732   * Evict the oldest entry (simple LRU)
733   */
734  private evictOldest(): void {
735    const firstKey = this.cache.keys().next().value;
736    if (firstKey) {
737      this.cache.delete(firstKey);
738      this.logger.debug(`Cache evicted oldest entry: ${firstKey}`);
739    }
740  }
741
742  /**
743   * Delete a value from cache
744   */
745  public async delete(key: string): Promise<boolean> {
746    const result = this.cache.delete(key);
747    if (result) {
748      this.logger.debug(`Cache delete: ${key}`);
749    }
750    return result;
751  }
752
753  /**
754   * Delete all cache entries matching a pattern
755   */
756  public async deletePattern(pattern: string): Promise<number> {
757    let deletedCount = 0;
758    const keys = Array.from(this.cache.keys());
759    
760    // Convert pattern to regex (simple wildcard support)
761    const regexPattern = pattern.replace(/\*/g, '.*');
762    const regex = new RegExp(`^${regexPattern}$`);
763    
764    for (const key of keys) {
765      if (regex.test(key)) {
766        this.cache.delete(key);
767        deletedCount++;
768      }
769    }
770    
771    this.logger.debug(`Cache deletePattern: ${pattern} - ${deletedCount} keys deleted`);
772    return deletedCount;
773  }
774
775  /**
776   * Clear all cache entries
777   */
778  public async flush(): Promise<void> {
779    const size = this.cache.size;
780    this.cache.clear();
781    this.logger.info(`Cache flushed: ${size} entries removed`);
782  }
783
784  /**
785   * Get multiple values from cache
786   */
787  public async mget<T>(keys: string[]): Promise<(T | null)[]> {
788    const results: (T | null)[] = [];
789    for (const key of keys) {
790      results.push(await this.get<T>(key));
791    }
792    return results;
793  }
794
795  /**
796   * Set multiple values in cache
797   */
798  public async mset<T>(entries: CacheEntry<T>[]): Promise<void> {
799    for (const entry of entries) {
800      await this.set(entry.key, entry.value, entry.ttlSeconds);
801    }
802  }
803
804  /**
805   * Connect to the cache backend (no-op for memory cache)
806   */
807  public async connect(): Promise<void> {
808    this.connected = true;
809  }
810
811  /**
812   * Disconnect from the cache backend (no-op for memory cache)
813   */
814  public async disconnect(): Promise<void> {
815    this.connected = false;
816  }
817
818  /**
819   * Check if connected to cache backend
820   */
821  public isConnected(): boolean {
822    return this.connected;
823  }
824
825  /**
826   * Get cache statistics
827   */
828  public async getStats(): Promise<CacheStats> {
829    const totalRequests = this.hitCount + this.missCount;
830    return {
831      size: this.cache.size,
832      keys: Array.from(this.cache.keys()),
833      hitRate: totalRequests > 0 ? this.hitCount / totalRequests : 0,
834      missRate: totalRequests > 0 ? this.missCount / totalRequests : 0,
835      connectionStatus: this.connected ? 'connected' : 'disconnected',
836      memoryUsage: this.cache.size * 64 // Rough estimate
837    };
838  }
839
840  /**
841   * Start cleanup interval to remove expired entries
842   */
843  private startCleanupInterval(): void {
844    this.cleanupInterval = setInterval(() => {
845      let cleaned = 0;
846      const now = Date.now();
847      
848      for (const [key, entry] of this.cache.entries()) {
849        if (now > entry.expiresAt) {
850          this.cache.delete(key);
851          cleaned++;
852        }
853      }
854      
855      if (cleaned > 0) {
856        this.logger.debug(`Cache cleanup: ${cleaned} expired entries removed`);
857      }
858    }, this.cleanupIntervalMs);
859  }
860
861  /**
862   * Check if the cache service is healthy
863   */
864  public async isHealthy(): Promise<boolean> {
865    try {
866      const testKey = '__health_check__';
867      await this.set(testKey, 'test', 1);
868      const result = await this.get(testKey);
869      await this.delete(testKey);
870      return result === 'test' && this.connected;
871    } catch (error) {
872      return false;
873    }
874  }
875
876  /**
877   * Stop the cleanup interval
878   */
879  public async destroy(): Promise<void> {
880    if (this.cleanupInterval) {
881      clearInterval(this.cleanupInterval);
882      this.cleanupInterval = null;
883    }
884    this.cache.clear();
885    this.connected = false;
886  }
887
888  /**
889   * Get the metadata for this service
890   */
891  public getMetadata(): ServiceMetadata {
892    return MemoryCacheService.metadata;
893  }
894
895  /**
896   * Initialize the service with optional configuration
897   */
898  public initialize(config?: Record<string, any>): void {
899    if (config) {
900      this.validateConfiguration(config);
901      // Could implement configuration-based initialization here
902      // For now, this service doesn't need special initialization
903    }
904  }
905}

Metadata

Path
utaba/main/services/cache/MemoryCacheService.ts
Namespace
utaba/main/services/cache
Author
utaba
Category
services
Technology
typescript
Contract Version
1.0.0
MIME Type
application/typescript
Published
18-Jul-2025
Last Updated
18-Jul-2025