Overview
Introduce cache namespaces to isolate different parts of an application, allowing different configuration settings per namespace and providing logical separation of cached data.
Background Analysis
Current State
- RunCache operates as a singleton with global configuration
- All cache operations work on a single, shared cache store
- Configuration is applied globally across all cache entries
- No built-in way to isolate cache data by application domain or feature
Namespace Requirements
- Isolation: Different namespaces should have separate cache stores
- Configuration: Each namespace can have its own settings (TTL, eviction policy, storage, etc.)
- API Consistency: Maintain familiar API while adding namespace support
- Performance: Minimal overhead for namespace operations
- Storage: Persistent storage should respect namespace boundaries
Implementation Strategy
Phase 1: Namespace Architecture
1.1 Namespace Manager Design
Create a central manager to handle multiple cache instances:
// src/core/namespace-manager.ts
export class NamespaceManager {
private static instance: NamespaceManager;
private namespaces: Map<string, CacheStore> = new Map();
private defaultNamespace: string = 'default';
static getInstance(): NamespaceManager {
if (!NamespaceManager.instance) {
NamespaceManager.instance = new NamespaceManager();
}
return NamespaceManager.instance;
}
async getNamespace(name: string): Promise<CacheStore> {
if (!this.namespaces.has(name)) {
const store = await CacheStore.create();
this.namespaces.set(name, store);
}
return this.namespaces.get(name)!;
}
async createNamespace(name: string, config?: CacheConfig): Promise<CacheStore> {
const store = await CacheStore.create(config);
this.namespaces.set(name, store);
return store;
}
listNamespaces(): string[] {
return Array.from(this.namespaces.keys());
}
async deleteNamespace(name: string): Promise<boolean> {
if (name === this.defaultNamespace) {
throw new Error('Cannot delete default namespace');
}
const store = this.namespaces.get(name);
if (store) {
await store.shutdown();
return this.namespaces.delete(name);
}
return false;
}
}
1.2 Namespace Interface Design
Define the public interface for namespace operations:
// src/types/namespace.ts
export interface NamespaceConfig extends CacheConfig {
name: string;
isolated?: boolean; // Whether to use separate storage
}
export interface NamespaceStats {
name: string;
entryCount: number;
memoryUsage: number;
hitRate: number;
lastAccessed: number;
config: CacheConfig;
}
export interface NamespaceAPI {
// Core cache operations
set(params: SetParams): Promise<boolean>;
get(key: string): Promise<string | string[] | undefined>;
delete(key: string): Promise<boolean>;
has(key: string): Promise<boolean>;
flush(): Promise<void>;
refetch(key: string): Promise<boolean>;
// Configuration
configure(config: CacheConfig): Promise<void>;
getConfig(): Promise<CacheConfig>;
// Events
onExpiry(callback: EventCallback): Promise<void>;
onKeyExpiry(key: string, callback: EventCallback): Promise<void>;
// ... other event methods
// Stats and management
getStats(): Promise<NamespaceStats>;
shutdown(): Promise<void>;
}
1.3 Storage Adapter Namespace Support
Update storage adapters to support namespace isolation:
// src/types/storage-adapter.ts
export interface NamespacedStorageAdapter extends StorageAdapter {
/**
* Save data for a specific namespace
*/
saveNamespace(namespace: string, data: string): Promise<void>;
/**
* Load data for a specific namespace
*/
loadNamespace(namespace: string): Promise<string | null>;
/**
* Clear data for a specific namespace
*/
clearNamespace(namespace: string): Promise<void>;
/**
* List all available namespaces
*/
listNamespaces(): Promise<string[]>;
}
Phase 2: Core Namespace Implementation
2.1 Namespace Class Implementation
Create a wrapper class that implements the namespace API:
// src/core/namespace.ts
export class Namespace implements NamespaceAPI {
private store: CacheStore;
private name: string;
constructor(name: string, store: CacheStore) {
this.name = name;
this.store = store;
}
// Delegate all operations to the underlying store
async set(params: SetParams): Promise<boolean> {
return this.store.set(params);
}
async get(key: string): Promise<string | string[] | undefined> {
return this.store.get(key);
}
// ... implement all other cache operations
async getStats(): Promise<NamespaceStats> {
// Collect statistics from the underlying store
return {
name: this.name,
entryCount: this.store.size(),
memoryUsage: this.store.getMemoryUsage(),
hitRate: this.store.getHitRate(),
lastAccessed: this.store.getLastAccessed(),
config: this.store.getConfig(),
};
}
}
2.2 Update CacheStore for Namespace Support
Add namespace-aware methods to CacheStore:
// src/core/cache-store.ts
export class CacheStore {
private namespace?: string;
constructor(config: CacheConfig = {}, namespace?: string) {
this.namespace = namespace;
// ... existing constructor logic
// Update storage adapter to use namespace
if (this.storageAdapter && namespace) {
this.updateStorageForNamespace(namespace);
}
}
private updateStorageForNamespace(namespace: string): void {
if (this.storageAdapter && 'saveNamespace' in this.storageAdapter) {
// Wrap storage operations to include namespace
const adapter = this.storageAdapter as NamespacedStorageAdapter;
this.saveToStorage = () => adapter.saveNamespace(namespace, this.serializeCache());
this.loadFromStorage = () => adapter.loadNamespace(namespace);
} else {
// For legacy adapters, prefix the storage key
this.updateStorageKey(namespace);
}
}
private updateStorageKey(namespace: string): void {
// Update storage key to include namespace prefix
const originalKey = this.storageAdapter?.storageKey || 'run-cache-data';
this.storageAdapter.storageKey = `${originalKey}:${namespace}`;
}
getNamespace(): string | undefined {
return this.namespace;
}
// Add methods for statistics
size(): number {
return this.cache.size;
}
getMemoryUsage(): number {
// Calculate approximate memory usage
let usage = 0;
for (const [key, state] of this.cache.entries()) {
usage += key.length * 2; // UTF-16 characters
usage += state.value.length * 2;
usage += 200; // Approximate overhead per entry
}
return usage;
}
getHitRate(): number {
// Track and return hit rate
return this.hitRate;
}
getLastAccessed(): number {
return this.lastAccessTime;
}
}
Phase 3: RunCache API Updates
3.1 Namespace-Aware RunCache API
Update the main RunCache class to support namespaces:
// src/run-cache.ts
export class RunCache {
private static namespaceManager: NamespaceManager;
private static currentNamespace: string = 'default';
// Initialize namespace manager
static {
RunCache.namespaceManager = NamespaceManager.getInstance();
}
// Namespace management methods
static async createNamespace(name: string, config?: CacheConfig): Promise<Namespace> {
const store = await RunCache.namespaceManager.createNamespace(name, config);
return new Namespace(name, store);
}
static async getNamespace(name: string): Promise<Namespace> {
const store = await RunCache.namespaceManager.getNamespace(name);
return new Namespace(name, store);
}
static async useNamespace(name: string): Promise<void> {
RunCache.currentNamespace = name;
// Ensure namespace exists
await RunCache.namespaceManager.getNamespace(name);
}
static getCurrentNamespace(): string {
return RunCache.currentNamespace;
}
static listNamespaces(): string[] {
return RunCache.namespaceManager.listNamespaces();
}
static async deleteNamespace(name: string): Promise<boolean> {
return RunCache.namespaceManager.deleteNamespace(name);
}
// Update existing methods to use current namespace
private static async getCurrentStore(): Promise<CacheStore> {
return RunCache.namespaceManager.getNamespace(RunCache.currentNamespace);
}
static async set(params: SetParams): Promise<boolean> {
const store = await RunCache.getCurrentStore();
return store.set(params);
}
static async get(key: string): Promise<string | string[] | undefined> {
const store = await RunCache.getCurrentStore();
return store.get(key);
}
// ... update all existing methods to use getCurrentStore()
// Namespace-specific operations
static async configureNamespace(namespace: string, config: CacheConfig): Promise<void> {
const store = await RunCache.namespaceManager.getNamespace(namespace);
await store.configure(config);
}
static async getNamespaceStats(namespace?: string): Promise<NamespaceStats> {
const targetNamespace = namespace || RunCache.currentNamespace;
const ns = await RunCache.getNamespace(targetNamespace);
return ns.getStats();
}
static async getAllNamespaceStats(): Promise<NamespaceStats[]> {
const namespaces = RunCache.listNamespaces();
return Promise.all(
namespaces.map(name => RunCache.getNamespaceStats(name))
);
}
// Enhanced shutdown for all namespaces
static async shutdown(): Promise<void> {
const namespaces = RunCache.listNamespaces();
await Promise.all(
namespaces.map(async (name) => {
const store = await RunCache.namespaceManager.getNamespace(name);
await store.shutdown();
})
);
RunCache.namespaceManager.clear();
}
}
3.2 Fluent Namespace API
Provide a fluent interface for namespace operations:
// src/namespace-builder.ts
export class NamespaceBuilder {
private config: NamespaceConfig = { name: '' };
static create(name: string): NamespaceBuilder {
return new NamespaceBuilder().name(name);
}
name(name: string): NamespaceBuilder {
this.config.name = name;
return this;
}
maxEntries(count: number): NamespaceBuilder {
this.config.maxEntries = count;
return this;
}
evictionPolicy(policy: EvictionPolicy): NamespaceBuilder {
this.config.evictionPolicy = policy;
return this;
}
storage(adapter: StorageAdapter): NamespaceBuilder {
this.config.storageAdapter = adapter;
return this;
}
isolated(isolated: boolean = true): NamespaceBuilder {
this.config.isolated = isolated;
return this;
}
async build(): Promise<Namespace> {
return RunCache.createNamespace(this.config.name, this.config);
}
}
// Usage example:
const userNamespace = await NamespaceBuilder
.create('users')
.maxEntries(1000)
.evictionPolicy(EvictionPolicy.LRU)
.storage(new IndexedDBAdapter({ storageKey: 'users-cache' }))
.build();
Phase 4: Storage Adapter Updates
4.1 Update Existing Storage Adapters
Modify storage adapters to support namespaces:
// src/storage/local-storage-adapter.ts
export class LocalStorageAdapter implements NamespacedStorageAdapter {
private baseStorageKey: string;
constructor(config?: Partial<StorageAdapterConfig>) {
this.baseStorageKey = config?.storageKey || 'run-cache-data';
}
// Legacy methods (maintain compatibility)
async save(data: string): Promise<void> {
return this.saveNamespace('default', data);
}
async load(): Promise<string | null> {
return this.loadNamespace('default');
}
async clear(): Promise<void> {
return this.clearNamespace('default');
}
// New namespace methods
async saveNamespace(namespace: string, data: string): Promise<void> {
const key = this.getNamespaceKey(namespace);
window.localStorage.setItem(key, data);
}
async loadNamespace(namespace: string): Promise<string | null> {
const key = this.getNamespaceKey(namespace);
return window.localStorage.getItem(key);
}
async clearNamespace(namespace: string): Promise<void> {
const key = this.getNamespaceKey(namespace);
window.localStorage.removeItem(key);
}
async listNamespaces(): Promise<string[]> {
const namespaces: string[] = [];
const prefix = `${this.baseStorageKey}:`;
for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i);
if (key?.startsWith(prefix)) {
const namespace = key.substring(prefix.length);
namespaces.push(namespace);
}
}
return namespaces;
}
private getNamespaceKey(namespace: string): string {
return `${this.baseStorageKey}:${namespace}`;
}
}
4.2 IndexedDB Namespace Support
Update IndexedDB adapter for namespace support:
// src/storage/indexed-db-adapter.ts
export class IndexedDBAdapter implements NamespacedStorageAdapter {
private dbName: string = 'run-cache-db';
private baseStoreName: string = 'cache-store';
async saveNamespace(namespace: string, data: string): Promise<void> {
const db = await this.initDB();
const storeName = this.getNamespaceStore(namespace);
return new Promise<void>((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.put({ id: this.storageKey, data });
request.onsuccess = () => resolve();
request.onerror = () => reject(new Error('Failed to save namespace data'));
});
}
async loadNamespace(namespace: string): Promise<string | null> {
const db = await this.initDB();
const storeName = this.getNamespaceStore(namespace);
return new Promise<string | null>((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const request = store.get(this.storageKey);
request.onsuccess = () => {
resolve(request.result?.data || null);
};
request.onerror = () => reject(new Error('Failed to load namespace data'));
});
}
async listNamespaces(): Promise<string[]> {
const db = await this.initDB();
const storeNames = Array.from(db.objectStoreNames);
const prefix = `${this.baseStoreName}-`;
return storeNames
.filter(name => name.startsWith(prefix))
.map(name => name.substring(prefix.length));
}
private getNamespaceStore(namespace: string): string {
return `${this.baseStoreName}-${namespace}`;
}
// Update initDB to create namespace stores on demand
private async initDB(): Promise<IDBDatabase> {
// Enhanced initialization logic to handle dynamic store creation
}
}
Phase 5: Advanced Features
5.1 Cross-Namespace Operations
Support operations across multiple namespaces:
// src/core/cross-namespace-operations.ts
export class CrossNamespaceOperations {
static async searchAcrossNamespaces(
pattern: string,
namespaces?: string[]
): Promise<{ namespace: string; key: string; value: string }[]> {
const targetNamespaces = namespaces || RunCache.listNamespaces();
const results: { namespace: string; key: string; value: string }[] = [];
for (const namespace of targetNamespaces) {
const store = await RunCache.namespaceManager.getNamespace(namespace);
const values = await store.get(pattern);
if (Array.isArray(values)) {
// Handle multiple matches
values.forEach((value, index) => {
results.push({ namespace, key: `${pattern}-${index}`, value });
});
} else if (values !== undefined) {
results.push({ namespace, key: pattern, value: values });
}
}
return results;
}
static async copyBetweenNamespaces(
fromNamespace: string,
toNamespace: string,
keyPattern: string
): Promise<number> {
const fromStore = await RunCache.namespaceManager.getNamespace(fromNamespace);
const toStore = await RunCache.namespaceManager.getNamespace(toNamespace);
// Implementation for copying cache entries
let copiedCount = 0;
// ... copy logic
return copiedCount;
}
static async syncNamespaces(
source: string,
target: string,
options?: { overwrite?: boolean; filter?: (key: string) => boolean }
): Promise<void> {
// Implementation for syncing namespaces
}
}
5.2 Namespace Events
Add namespace-specific event handling:
// src/types/namespace-events.ts
export interface NamespaceEventParam extends EventParam {
namespace: string;
}
export const NAMESPACE_EVENT = {
NAMESPACE_CREATED: 'namespace_created',
NAMESPACE_DELETED: 'namespace_deleted',
NAMESPACE_CONFIGURED: 'namespace_configured',
CROSS_NAMESPACE_OPERATION: 'cross_namespace_operation',
} as const;
// Usage in RunCache
static async onNamespaceCreated(callback: (event: NamespaceEventParam) => void): Promise<void> {
// Register namespace creation event
}
static async onNamespaceDeleted(callback: (event: NamespaceEventParam) => void): Promise<void> {
// Register namespace deletion event
}
Phase 6: Testing and Documentation
6.1 Comprehensive Test Suite
Create extensive tests for namespace functionality:
// src/namespaces/namespaces.test.ts
describe('Cache Namespaces', () => {
describe('Namespace Creation and Management', () => {
it('should create new namespaces with unique stores');
it('should list all available namespaces');
it('should delete namespaces and clean up resources');
it('should prevent deletion of default namespace');
});
describe('Namespace Isolation', () => {
it('should isolate data between namespaces');
it('should allow same keys in different namespaces');
it('should maintain separate configurations per namespace');
});
describe('Storage Integration', () => {
it('should persist namespaced data correctly');
it('should restore namespaced data on reload');
it('should handle storage adapter namespace support');
});
describe('Cross-Namespace Operations', () => {
it('should search across multiple namespaces');
it('should copy data between namespaces');
it('should sync namespaces with options');
});
describe('API Compatibility', () => {
it('should maintain backward compatibility for existing code');
it('should default to default namespace for legacy operations');
});
});
6.2 Performance and Integration Tests
Test namespace performance and integration:
// src/namespaces/performance.test.ts
describe('Namespace Performance', () => {
it('should have minimal overhead for namespace operations');
it('should scale well with many namespaces');
it('should efficiently manage memory across namespaces');
});
// src/namespaces/integration.test.ts
describe('Namespace Integration', () => {
describe('With Existing Features', () => {
it('should work with middleware');
it('should work with eviction policies');
it('should work with TTL and auto-refetch');
it('should work with tags and dependencies');
});
});
Usage Examples
Basic Namespace Usage
import { RunCache, NamespaceBuilder } from 'run-cache';
// Create namespaces for different application areas
const userCache = await RunCache.createNamespace('users', {
maxEntries: 1000,
evictionPolicy: EvictionPolicy.LRU
});
const sessionCache = await RunCache.createNamespace('sessions', {
maxEntries: 500,
defaultTTL: 30 * 60 * 1000 // 30 minutes
});
// Use namespace-specific operations
await userCache.set({ key: 'user:123', value: 'John Doe' });
await sessionCache.set({ key: 'session:abc', value: 'active' });
// Switch default namespace context
await RunCache.useNamespace('users');
await RunCache.set({ key: 'user:456', value: 'Jane Smith' }); // Goes to users namespace
Fluent Builder Pattern
const productCache = await NamespaceBuilder
.create('products')
.maxEntries(5000)
.evictionPolicy(EvictionPolicy.LFU)
.storage(new IndexedDBAdapter({ storageKey: 'products' }))
.isolated(true)
.build();
await productCache.set({
key: 'product:123',
value: JSON.stringify({ name: 'Widget', price: 29.99 }),
tags: ['electronics', 'widgets']
});
Cross-Namespace Operations
// Search across multiple namespaces
const results = await CrossNamespaceOperations.searchAcrossNamespaces(
'user:*',
['users', 'profiles', 'sessions']
);
// Copy data between namespaces
await CrossNamespaceOperations.copyBetweenNamespaces(
'staging-users',
'production-users',
'user:verified:*'
);
Namespace Statistics and Monitoring
// Get statistics for specific namespace
const userStats = await RunCache.getNamespaceStats('users');
console.log(`Users cache: ${userStats.entryCount} entries, ${userStats.memoryUsage} bytes`);
// Get statistics for all namespaces
const allStats = await RunCache.getAllNamespaceStats();
allStats.forEach(stats => {
console.log(`${stats.name}: hit rate ${stats.hitRate}%`);
});
File Structure
src/
├── core/
│ ├── namespace-manager.ts # Central namespace management
│ ├── namespace.ts # Individual namespace implementation
│ ├── cache-store.ts # Updated with namespace support
│ └── cross-namespace-operations.ts # Cross-namespace utilities
├── types/
│ ├── namespace.ts # Namespace type definitions
│ └── namespace-events.ts # Namespace event types
├── namespaces/
│ ├── namespace-builder.ts # Fluent builder interface
│ ├── namespaces.test.ts # Core namespace tests
│ ├── integration.test.ts # Integration tests
│ └── performance.test.ts # Performance benchmarks
├── storage/
│ ├── local-storage-adapter.ts # Updated with namespace support
│ ├── indexed-db-adapter.ts # Updated with namespace support
│ └── filesystem-adapter.ts # Updated with namespace support
└── run-cache.ts # Updated main API
Timeline Estimation
Phase 1 (Week 1): Architecture Design
- Namespace manager and core interfaces
- Storage adapter interface updates
- Initial namespace class structure
Phase 2 (Week 2): Core Implementation
- Namespace manager implementation
- Update CacheStore for namespace support
- Basic namespace operations
Phase 3 (Week 3): API Integration
- Update RunCache API for namespaces
- Fluent builder interface
- Backward compatibility layer
Phase 4 (Week 4): Storage Updates
- Update all storage adapters for namespace support
- Migration and compatibility handling
- Persistent namespace management
Phase 5 (Week 5): Advanced Features
- Cross-namespace operations
- Namespace events and monitoring
- Performance optimizations
Phase 6 (Week 6): Testing & Documentation
- Comprehensive test suite
- Performance benchmarks
- Documentation and examples
Success Metrics
- Functionality: Complete namespace isolation with independent configurations
- Compatibility: Zero breaking changes for existing code
- Performance: <5% overhead for namespace operations
- Usability: Intuitive API with clear separation of concerns
- Storage: Efficient namespace-aware persistence
- Testing: 100% test coverage for namespace features
Risk Mitigation
- Memory Usage: Monitor memory consumption with multiple namespaces
- Storage Complexity: Ensure storage adapters handle namespaces efficiently
- API Confusion: Clear documentation distinguishing namespace vs global operations
- Migration Complexity: Smooth migration path from global to namespaced usage
- Performance Degradation: Optimize namespace lookup and switching
Future Enhancements
- Namespace Templates: Predefined namespace configurations for common use cases
- Dynamic Namespace Creation: Auto-create namespaces based on key patterns
- Namespace Policies: Advanced rules for namespace management and lifecycle
- Import/Export: Bulk operations for moving data between namespaces
- Namespace Analytics: Advanced monitoring and reporting for namespace usage
Overview
Introduce cache namespaces to isolate different parts of an application, allowing different configuration settings per namespace and providing logical separation of cached data.
Background Analysis
Current State
Namespace Requirements
Implementation Strategy
Phase 1: Namespace Architecture
1.1 Namespace Manager Design
Create a central manager to handle multiple cache instances:
1.2 Namespace Interface Design
Define the public interface for namespace operations:
1.3 Storage Adapter Namespace Support
Update storage adapters to support namespace isolation:
Phase 2: Core Namespace Implementation
2.1 Namespace Class Implementation
Create a wrapper class that implements the namespace API:
2.2 Update CacheStore for Namespace Support
Add namespace-aware methods to CacheStore:
Phase 3: RunCache API Updates
3.1 Namespace-Aware RunCache API
Update the main RunCache class to support namespaces:
3.2 Fluent Namespace API
Provide a fluent interface for namespace operations:
Phase 4: Storage Adapter Updates
4.1 Update Existing Storage Adapters
Modify storage adapters to support namespaces:
4.2 IndexedDB Namespace Support
Update IndexedDB adapter for namespace support:
Phase 5: Advanced Features
5.1 Cross-Namespace Operations
Support operations across multiple namespaces:
5.2 Namespace Events
Add namespace-specific event handling:
Phase 6: Testing and Documentation
6.1 Comprehensive Test Suite
Create extensive tests for namespace functionality:
6.2 Performance and Integration Tests
Test namespace performance and integration:
Usage Examples
Basic Namespace Usage
Fluent Builder Pattern
Cross-Namespace Operations
Namespace Statistics and Monitoring
File Structure
Timeline Estimation
Phase 1 (Week 1): Architecture Design
Phase 2 (Week 2): Core Implementation
Phase 3 (Week 3): API Integration
Phase 4 (Week 4): Storage Updates
Phase 5 (Week 5): Advanced Features
Phase 6 (Week 6): Testing & Documentation
Success Metrics
Risk Mitigation
Future Enhancements