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