Intelligent Adapter Selection - How It Works

Complete guide to the storage-system's autonomous adapter selection mechanism

🧠 Intelligent Adapter Selection - How It Works

Core Philosophy: The storage-system is fully autonomous - you never manually specify which adapter to use. The system analyzes data characteristics and makes optimal decisions automatically.


⚡ TL;DR

// ✅ CORRECT: Let the system decide
await storage.put('user/profile', userData);
// System automatically analyzes:
// - Size: 15KB (medium)
// - Key pattern: 'user/*' (critical)
// - Decision: IndexedDB + localStorage redundancy

// ❌ WRONG: Never manually specify adapter
const eventData: StorageEventData = {
  type: 'put',
  key: 'universe_123',
  adapter: 'opfs' // ❌ DON'T DO THIS - defeats intelligent system!
};

Rule: Never set the adapter field manually - it's populated automatically by the storage engine based on intelligent analysis.


🏗️ How Adapter Selection Works

Step 1: Data Analysis (Automatic)

When you call storage.put(), the system analyzes:

// File: gg-storage.ts, line 1055-1080
private makeDecision<T>(key: string, value: T, operation: 'put' | 'get' | 'remove'): PolicyDecision {
  const request = {
    key,
    size: this.estimateSize(value),  // 🔍 Calculate data size
    operation,
    strategy: this.strategy,          // 'speed' | 'balanced' | 'durable'
    capabilities: this.capabilities,   // What adapters are available
    flags: {
      redundancy: this.config.performance?.redundancy,
      monitoring: this.config.performance?.monitoring
    }
  };

  // Use adaptive engine for enhanced decision making
  if (this.config.performance?.adaptiveStrategy !== false) {
    return this.adaptivePolicyEngine.makeAdaptiveDecision(request);
  }

  // Fallback to standard policy engine
  return decide(request);
}

Step 2: Policy Engine Decision (Automatic)

The policy engine (decide() function in policy-engine.ts) analyzes:

// File: policy-engine.ts, line 184-268
export function decide(request: PolicyRequest): PolicyDecision {
  const { key, size, strategy, capabilities } = request;

  // 🔍 Classify key based on patterns
  const keyType = classifyKey(key);
  // Returns: 'critical' | 'user-preference' | 'cache' | 'temp' | 'data'

  // 🔍 Analyze size
  const isSmall = size < 8 * 1024;      // < 8KB
  const isMedium = size < 64 * 1024;    // 8KB - 64KB
  const isLarge = size >= 64 * 1024;    // 64KB+
  const isHuge = size >= 512 * 1024;    // 512KB+

  // 🧠 Make intelligent decisions
  const useMicroCache = strategy === 'speed' && isSmall && (isCritical || isUserPref);
  const useOPFS = capabilities.opfs && !isTemp;
  
  // Dexie decision logic...
  // Write mode logic...
  // Redundancy logic...
  
  return {
    target: { opfs, dexie, localStorage },
    redundancy: { localStorage, dexie },
    writeMode: 'await-opfs' | 'queue-opfs' | 'dexie-only',
    ttlMs,
    priority,
    // ... etc
  };
}

Step 3: Key Classification (Automatic)

// File: policy-engine.ts, line 123-149
export function classifyKey(key: string): KeyType {
  const baseKey = getBaseKey(key); // Extracts base from scoped keys

  // 🔍 Critical system data - needs redundancy
  if (baseKey.startsWith('auth/') || 
      baseKey.startsWith('session/') || 
      baseKey.includes('critical/')) {
    return 'critical';
  }

  // 🔍 User preferences - important but not critical
  if (baseKey.startsWith('user/') || 
      baseKey.startsWith('profile/') || 
      baseKey.includes('settings/')) {
    return 'user-preference';
  }

  // 🔍 Cache data - can be regenerated
  if (baseKey.startsWith('cache/') || baseKey.includes('cached/')) {
    return 'cache';
  }

  // 🔍 Temporary data - short-lived
  if (baseKey.startsWith('temp/') || baseKey.startsWith('tmp/')) {
    return 'temp';
  }

  return 'data'; // Default
}

📊 Decision Matrix (Automatic)

The system makes these decisions automatically based on data characteristics:

By Data Size

Size Classification Primary Adapter Micro-Cache Redundancy
< 8KB Small IndexedDB ✅ Yes (if critical) localStorage
8KB - 64KB Medium IndexedDB ❌ No IndexedDB
64KB - 512KB Large OPFS ❌ No IndexedDB index
> 512KB Huge OPFS ❌ No IndexedDB index

By Key Pattern

Key Pattern Classification Storage Redundancy
auth/* Critical IndexedDB ✅ localStorage + Dexie
session/* Critical IndexedDB ✅ localStorage + Dexie
user/* User Preference IndexedDB ✅ localStorage (if small)
profile/* User Preference IndexedDB ✅ localStorage (if small)
cache/* Cache IndexedDB ❌ None
temp/* Temporary localStorage ❌ None
universe_* Data OPFS/IndexedDB Depends on size

By Strategy

Strategy Small Data Medium Data Large Data Write Mode
speed localStorage (micro) IndexedDB OPFS (queued) Background writes
balanced IndexedDB IndexedDB OPFS (immediate) Await writes
durable IndexedDB + localStorage IndexedDB + localStorage OPFS (immediate) Await + verify

🎯 Real-World Examples

Example 1: Universe Creation

// Creating a new universe
const universe = {
  id: 'universe_123',
  name: 'My Universe',
  galaxyIds: [],
  metadata: { /* ... */ }
};

// ✅ CORRECT: Let system decide
await storage.put('universe_universe_123', universe);

// 🧠 System analyzes:
// - Key: 'universe_universe_123' → type: 'data'
// - Size: ~2KB → 'small'
// - Strategy: 'balanced' (default)
// - Decision: IndexedDB (no redundancy needed for non-critical data)

Example 2: User Authentication

// Storing auth token
const authToken = { 
  token: 'jwt_token_here',
  expiresAt: Date.now() + 3600000 
};

// ✅ CORRECT: Let system decide
await storage.put('auth/session/token', authToken);

// 🧠 System analyzes:
// - Key: 'auth/session/token' → type: 'critical'
// - Size: ~200 bytes → 'small'
// - Strategy: 'balanced'
// - Decision: IndexedDB + localStorage redundancy (critical data!)

Example 3: Large Document

// Storing a large PDF document
const pdfData = new Uint8Array(2 * 1024 * 1024); // 2MB

// ✅ CORRECT: Let system decide
await storage.put('documents/large-file', pdfData);

// 🧠 System analyzes:
// - Key: 'documents/large-file' → type: 'data'
// - Size: 2MB → 'huge'
// - Strategy: 'balanced'
// - Decision: OPFS (optimal for large files) + IndexedDB index

Example 4: Temporary Cache

// Caching API response
const apiResponse = { /* API data */ };

// ✅ CORRECT: Let system decide
await storage.put('cache/api/users', apiResponse);

// 🧠 System analyzes:
// - Key: 'cache/api/users' → type: 'cache'
// - Size: ~5KB → 'small'
// - Strategy: 'balanced'
// - Decision: IndexedDB only (no redundancy for cache)

🚫 Common Mistakes

❌ Mistake 1: Manually Specifying Adapter

// ❌ WRONG: Defeats intelligent system
const eventData: StorageEventData = {
  type: 'put',
  key: 'universe_123',
  adapter: 'opfs', // DON'T DO THIS!
  timestamp: Date.now()
};

// ✅ CORRECT: Let system populate adapter field
const eventData: StorageEventData = {
  type: 'put',
  key: 'universe_123',
  timestamp: Date.now()
  // adapter is optional - storage-system fills it in automatically
};

❌ Mistake 2: Assuming Data Goes to Specific Adapter

// ❌ WRONG: Assuming small data goes to localStorage
await storage.put('user/settings', settings);
// Reality: Goes to IndexedDB + localStorage redundancy

// ✅ CORRECT: Trust the system
await storage.put('user/settings', settings);
// System decides based on size, pattern, strategy

❌ Mistake 3: Pre-Optimizing Storage Location

// ❌ WRONG: Trying to optimize manually
if (dataSize > 1024 * 1024) {
  await opfsStorage.put(key, value); // Manually choosing OPFS
} else {
  await indexedDBStorage.put(key, value);
}

// ✅ CORRECT: Let system optimize
await storage.put(key, value);
// System makes better decisions based on:
// - Current adapter load
// - Cache hit rates
// - Historical patterns
// - Available capacity

🤖 AI-Powered Adaptive Learning

The system gets smarter over time via the AdaptivePolicyEngine:

// File: intelligence/adaptive-policy-engine.ts

export class AdaptivePolicyEngine {
  /**
   * Make adaptive decision based on historical data + policy engine
   */
  makeAdaptiveDecision(request: PolicyRequest): PolicyDecision {
    // 1. Get base decision from policy engine
    const baseDecision = decide(request);

    // 2. Check historical performance for this key
    const history = this.getKeyHistory(request.key);

    // 3. Adjust decision based on patterns
    if (history.accessCount > 100 && history.avgDuration > 10) {
      // Frequently accessed but slow? Promote to faster storage!
      baseDecision.target.localStorage = 'micro';
    }

    // 4. Learn from this operation
    this.recordOperationResult(request.key, request.operation, {
      duration: 0, // Will be filled after operation
      success: true,
      cacheHit: false,
      adapter: this.determineAdapterFromDecision(baseDecision)
    });

    return baseDecision;
  }
}

Result: The system learns which keys are "hot" and automatically promotes them to faster storage!


📈 Performance Intelligence

Automatic Optimization Examples

Scenario 1: User profile accessed frequently

// First access: 20ms (IndexedDB)
const profile = await storage.get('user/profile');

// After 10 accesses, system learns it's "hot"
// Next access: 0.3ms (promoted to RAM cache automatically)
const profileFast = await storage.get('user/profile');

Scenario 2: Storage quota filling up

// System detects: IndexedDB at 85% capacity
// Automatic migration triggered:
// - Large items migrate to OPFS
// - IndexedDB space freed
// - No user intervention needed

Scenario 3: Network conditions change

// Online: Sync to MongoDB in background
// Offline: Queue changes locally
// Online again: Auto-sync queued changes
// All automatic - no manual intervention!

🎯 Best Practices

✅ DO: Trust the System

// The system makes better decisions than manual code
await storage.put(key, value);
await storage.get(key);
await storage.remove(key);

✅ DO: Use Semantic Key Names

// Good key names help the classifier
await storage.put('auth/session/token', token);        // → Critical
await storage.put('user/preferences/theme', theme);    // → User preference
await storage.put('cache/api/response', data);         // → Cache
await storage.put('temp/upload/progress', progress);   // → Temporary

✅ DO: Configure Strategy, Not Adapters

// app.config.ts
provideIntelligentStorage({
  namespace: 'my-app',
  strategy: 'balanced',  // 🎯 Configure behavior, not implementation
  performance: {
    adaptiveStrategy: true,  // ✅ Enable AI learning
    redundancy: true,        // ✅ Enable smart redundancy
    monitoring: true         // ✅ Enable performance tracking
  }
})

❌ DON'T: Manually Specify Adapters

// ❌ WRONG: Bypasses intelligence
const eventData: StorageEventData = {
  adapter: 'opfs' // Defeats the entire intelligent system!
};

// ❌ WRONG: Manual adapter selection
if (dataSize > threshold) {
  await opfsAdapter.write(key, value);
} else {
  await indexedDBAdapter.write(key, value);
}

// ✅ CORRECT: Trust the policy engine
await storage.put(key, value);

🔬 Under the Hood

The Decision Pipeline

graph TD
    A[storage.put(key, value)] --> B[estimateSize(value)]
    B --> C[makeDecision()]
    C --> D[classifyKey(key)]
    C --> E[Check capabilities]
    C --> F[Apply strategy]
    D --> G{Key Type?}
    G -->|critical| H[IndexedDB + localStorage]
    G -->|user-preference| I[IndexedDB + localStorage if small]
    G -->|cache| J[IndexedDB only]
    G -->|temp| K[localStorage only]
    G -->|data| L{Size?}
    L -->|Small| M[IndexedDB]
    L -->|Medium| N[IndexedDB]
    L -->|Large| O[OPFS + IndexedDB index]
    L -->|Huge| P[OPFS + IndexedDB index + CAS]

Example Decision Flow

// Input
await storage.put('universe_123456', {
  id: 'universe_123456',
  name: 'My Universe',
  galaxyIds: ['galaxy1', 'galaxy2'],
  metadata: { /* ... */ }
});

// Step 1: Estimate size
size = JSON.stringify(value).length * 2; // ~3KB

// Step 2: Classify key
classifyKey('universe_123456');
// → 'data' (doesn't match critical/user/cache/temp patterns)

// Step 3: Analyze size
// 3KB < 8KB → isSmall = true

// Step 4: Check strategy
// strategy = 'balanced' (default)

// Step 5: Make decision
return {
  target: {
    opfs: false,           // Not large enough for OPFS
    dexie: 'inline',       // Store in IndexedDB directly
    localStorage: 'none'   // No micro-cache (not critical)
  },
  redundancy: {
    localStorage: false,   // Not critical
    dexie: false
  },
  writeMode: 'dexie-only',
  priority: 'normal'
};

// Result: Universe saved to IndexedDB

🎨 Storage Event Data

What StorageEventData Is For

The StorageEventData interface is for event metadata, not for controlling storage:

export interface StorageEventData {
  type: StorageEventType;      // 'put' | 'get' | 'delete' | 'clear'
  key?: string;                // Which key was affected
  value?: unknown;             // (Optional) The value
  adapter?: StorageAdapterType; // ✅ FILLED BY SYSTEM (not by you!)
  timestamp: number;           // When the event occurred
  error?: string;              // Error message if failed
}

Purpose: Track what happened, not control what should happen.

How to Use Storage Events

// ✅ CORRECT: Listen to events, don't control them
storage.onEvent((event) => {
  console.log(`Storage event: ${event.type} on ${event.key}`);
  console.log(`Adapter used: ${event.adapter}`); // ✅ Read the adapter decision
  
  // React to the event
  if (event.type === 'put' && event.key === 'universe_123') {
    // Refresh UI, etc.
  }
});

// ❌ WRONG: Don't create events manually with adapter specified
const fakeEvent: StorageEventData = {
  type: 'put',
  adapter: 'opfs', // ❌ This overrides the intelligent decision!
  timestamp: Date.now()
};

🔧 When Adapter Field is Populated

The adapter field in StorageEventData is populated automatically by the storage engine when it emits events:

// File: gg-storage.ts, line 469-487
if (primarySuccess) {
  this.cache.set(key, value);
  
  // 🔥 Emit event with adapter information
  this.emit({ type: 'put', key });
  // Note: this.emit() is internal - it knows which adapter was used
  
  // System can extend this to include adapter info in the future
  // For now, consumers don't need to know the adapter - 
  // they just react to the event
}

Current Implementation

Currently, emit() emits a simple StorageEvent:

type StorageEvent = { 
  type: 'put' | 'delete' | 'clear' | 'remove'; 
  key?: string 
};

Then consumers convert it to StorageEventData by adding metadata:

// ✅ CORRECT: Add only metadata you know (timestamp)
this.storage.onEvent((event) => {
  const eventData: StorageEventData = {
    ...event,
    type: event.type === 'remove' ? 'delete' : event.type,
    timestamp: Date.now()
    // adapter is optional - system fills it in during emission if needed
  };
  
  void this.handleStorageEvent(eventData);
});

💡 Key Insights

1. Adapter is an Output, Not an Input

// Think of 'adapter' as a READ-ONLY field
interface StorageEventData {
  adapter?: StorageAdapterType; // "What adapter was used" not "Use this adapter"
}

2. Intelligence Happens During write/read, Not During Events

// Intelligence applied HERE ↓
await storage.put('universe_123', universe);
// Decision made: "IndexedDB is optimal for this data"

// Event emitted HERE ↓
// Just notifies: "Data was stored, adapter was IndexedDB"
storage.onEvent((e) => {
  // React to the fact that data changed
  // Don't try to control where it goes
});

3. System Learns and Adapts

// First 10 accesses to 'user/settings'
// Average: 15ms (IndexedDB)

// System learns: "This is hot data"
// Promotion: Moves to RAM cache

// Next 100 accesses to 'user/settings'
// Average: 0.3ms (RAM cache)

// All automatic - no code changes needed!

🎓 Summary

Core Principles

  1. Never manually specify adapters - The system is smarter than manual code
  2. Use semantic key patterns - Help the classifier understand your data
  3. Configure strategy, not implementation - Set 'speed'/'balanced'/'durable', not 'use OPFS'
  4. Trust the intelligence - The system learns and optimizes over time

Why This Matters

The Golden Rule

// 🎯 THE GOLDEN RULE 🎯
// Your job: Tell the system WHAT to store
// System's job: Decide HOW to store it

await storage.put(key, value); // ✅ That's it!

📚 Related Documentation


Remember: The storage-system is a Ferrari - you don't need to tell it how to shift gears, just where you want to go! 🏎️


Created: November 19, 2025 - 12:00 PM
Session ID: 11.25.1.2
Status: ✅ Complete