Storage & Cache Management
March 9, 2026 · View on GitHub
Technical documentation for the storage and caching system in Debian Time Capsule.
Overview
CDE uses a layered storage approach:
- IndexedDB - Primary storage for structured data
- localStorage - Fallback for browsers without IndexedDB
- Memory Cache - Runtime caching for performance
Architecture
┌─────────────────────────────────────┐
│ Storage Adapter (Unified API) │
├─────────────────────────────────────┤
│ IndexedDB Manager │ localStorage │
├─────────────────────────────────────┤
│ Memory Cache │
└─────────────────────────────────────┘
IndexedDB Implementation
Database Schema
// Database: cde-time-capsule
// Version: 1
const DB_NAME = 'cde-time-capsule';
const DB_VERSION = 1;
// Object Stores
const STORES = {
SETTINGS: 'settings', // User preferences
SESSION: 'session', // Window positions, state
FILESYSTEM: 'filesystem', // VFS data (future)
CACHE: 'cache', // Temporary data
};
Store Details
1. Settings Store
// Key-value pairs for user settings
{
'theme': {
palette: 'Broica',
backdrop: 'CircuitBoards'
},
'accessibility': {
fontSize: 14,
highContrast: false,
reducedMotion: false
},
'keyboard': {
shortcuts: { ... }
}
}
2. Session Store
// Window and workspace state
{
'windows': [
{
id: 'emacs-1',
x: 100,
y: 100,
width: 600,
height: 400,
state: 'normal',
workspace: 1
}
],
'currentWorkspace': 1
}
3. Cache Store
// Temporary cached data with TTL
{
'xpm-render-Afternoon': {
data: 'data:image/png;base64,...',
timestamp: 1234567890,
ttl: 604800000 // 7 days
}
}
Storage Adapter
Unified API that abstracts storage implementation.
Usage
import { storageAdapter } from './utilities/storage-adapter';
// Save data
await storageAdapter.set('theme', themeData);
// Retrieve data
const theme = await storageAdapter.get('theme');
// Remove data
await storageAdapter.remove('theme');
// Clear all
await storageAdapter.clear();
Implementation
class StorageAdapter {
private useIndexedDB: boolean;
async init() {
// Try IndexedDB first
try {
await indexedDBManager.init();
this.useIndexedDB = true;
} catch (error) {
// Fallback to localStorage
this.useIndexedDB = false;
}
}
async set(key: string, value: any) {
if (this.useIndexedDB) {
return indexedDBManager.set(STORES.SETTINGS, key, value);
} else {
localStorage.setItem(key, JSON.stringify(value));
}
}
async get(key: string) {
if (this.useIndexedDB) {
return indexedDBManager.get(STORES.SETTINGS, key);
} else {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
}
}
}
IndexedDB Manager
Low-level IndexedDB operations.
Initialization
class IndexedDBManager {
private db: IDBDatabase | null = null;
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
// Create object stores
if (!db.objectStoreNames.contains(STORES.SETTINGS)) {
db.createObjectStore(STORES.SETTINGS);
}
if (!db.objectStoreNames.contains(STORES.SESSION)) {
db.createObjectStore(STORES.SESSION);
}
if (!db.objectStoreNames.contains(STORES.CACHE)) {
const cacheStore = db.createObjectStore(STORES.CACHE);
cacheStore.createIndex('timestamp', 'timestamp', { unique: false });
}
};
});
}
}
CRUD Operations
// Create/Update
async set(storeName: string, key: string, value: any) {
const tx = this.db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
await store.put(value, key);
await tx.complete;
}
// Read
async get(storeName: string, key: string) {
const tx = this.db.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
return await store.get(key);
}
// Delete
async remove(storeName: string, key: string) {
const tx = this.db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
await store.delete(key);
await tx.complete;
}
// Clear store
async clear(storeName: string) {
const tx = this.db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
await store.clear();
await tx.complete;
}
Cache Management
Cache Strategy
class CacheManager {
private readonly DEFAULT_TTL = 7 * 24 * 60 * 60 * 1000; // 7 days
async set(key: string, data: any, ttl = this.DEFAULT_TTL) {
const cacheEntry = {
data,
timestamp: Date.now(),
ttl,
};
await indexedDBManager.set(STORES.CACHE, key, cacheEntry);
}
async get(key: string) {
const entry = await indexedDBManager.get(STORES.CACHE, key);
if (!entry) return null;
// Check if expired
const age = Date.now() - entry.timestamp;
if (age > entry.ttl) {
await this.remove(key);
return null;
}
return entry.data;
}
async cleanup() {
// Remove expired entries
const tx = this.db.transaction(STORES.CACHE, 'readwrite');
const store = tx.objectStore(STORES.CACHE);
const index = store.index('timestamp');
const cutoff = Date.now() - this.DEFAULT_TTL;
const range = IDBKeyRange.upperBound(cutoff);
const cursor = await index.openCursor(range);
while (cursor) {
await cursor.delete();
cursor = await cursor.continue();
}
}
}
What Gets Cached
- XPM Renders - Parsed backdrop images (7 days TTL)
- Theme Previews - Generated theme thumbnails (30 days TTL)
- VFS Snapshots - Filesystem state (session TTL)
Migration System
localStorage to IndexedDB
async migrateFromLocalStorage() {
const keysToMigrate = [
'cde-settings',
'cde-theme',
'cde-accessibility',
'cde-session'
];
for (const key of keysToMigrate) {
const value = localStorage.getItem(key);
if (value) {
try {
const parsed = JSON.parse(value);
await indexedDBManager.set(STORES.SETTINGS, key, parsed);
localStorage.removeItem(key); // Clean up
} catch (error) {
console.error(`Failed to migrate ${key}:`, error);
}
}
}
}
Storage Quota Management
Check Available Space
async getStorageEstimate() {
if ('storage' in navigator && 'estimate' in navigator.storage) {
const estimate = await navigator.storage.estimate();
return {
usage: estimate.usage || 0,
quota: estimate.quota || 0,
percentage: ((estimate.usage || 0) / (estimate.quota || 1)) * 100
};
}
return null;
}
Handle Quota Exceeded
async handleQuotaExceeded() {
// 1. Clean up cache
await cacheManager.cleanup();
// 2. Remove old session data
await this.cleanOldSessions();
// 3. Notify user
showToast('Storage space low. Old data cleaned up.', 'warning');
}
Version Management
Schema Versioning
// When schema changes, increment DB_VERSION
const DB_VERSION = 2; // Changed from 1
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
const oldVersion = event.oldVersion;
// Migration from v1 to v2
if (oldVersion < 2) {
// Add new index
const cacheStore = db.transaction.objectStore(STORES.CACHE);
cacheStore.createIndex('ttl', 'ttl', { unique: false });
}
};
Performance Considerations
Batch Operations
// Bad: Multiple transactions
for (const item of items) {
await indexedDBManager.set(STORES.CACHE, item.key, item.value);
}
// Good: Single transaction
const tx = db.transaction(STORES.CACHE, 'readwrite');
const store = tx.objectStore(STORES.CACHE);
for (const item of items) {
store.put(item.value, item.key);
}
await tx.complete;
Lazy Loading
// Don't load all data at startup
// Load on-demand instead
async loadThemeData() {
if (!this.themeCache) {
this.themeCache = await indexedDBManager.get(STORES.SETTINGS, 'theme');
}
return this.themeCache;
}
Error Handling
try {
await storageAdapter.set('key', value);
} catch (error) {
if (error.name === 'QuotaExceededError') {
await handleQuotaExceeded();
} else if (error.name === 'InvalidStateError') {
// Database closed, reinitialize
await indexedDBManager.init();
} else {
// Fallback to localStorage
localStorage.setItem('key', JSON.stringify(value));
}
}
Testing
Manual Testing
// In browser console
await indexedDBManager.set(STORES.SETTINGS, 'test', { foo: 'bar' });
const result = await indexedDBManager.get(STORES.SETTINGS, 'test');
console.log(result); // { foo: 'bar' }
Storage Inspector
Use browser DevTools:
- Chrome: Application → Storage → IndexedDB
- Firefox: Storage → IndexedDB
- Safari: Storage → IndexedDB
Best Practices
- Always use Storage Adapter - Don't access IndexedDB directly
- Handle errors gracefully - Always have fallback
- Clean up regularly - Remove expired cache entries
- Batch operations - Use transactions efficiently
- Monitor quota - Check storage usage periodically