How to Implement Storage System

Complete guide to implementing our intelligent storage system across the NX monorepo

How to Implement Our Intelligent Storage System

🎯 Overview

This guide provides a complete step-by-step implementation of our intelligent storage system across your Angular applications. Our storage system provides automatic adapter selection, smart redundancy, and zero-configuration intelligence.

Session 166.3 Update: For features that need both local persistence AND backend sync (chat, preferences, drafts, etc.), use the new BackendSyncEngine + syncToBackend(). It provides:

  • autoFlashState() for instant IndexedDB persistence
  • BackendSyncEngine with SyncDescriptor<T> for fire-and-forget backend sync
  • syncToBackend() — one-liner to watch a signal and auto-sync changes
  • Persistent retry queue (survives page refresh, drains on reconnect)
  • Two-signal pattern for race-condition-free backend data merging

Quick example:

const prefs = autoFlashState('user-prefs', { theme: 'light' });
syncToBackend(prefs, { descriptorKey: 'user-prefs', debounceMs: 1000 });
// That's it — instant local + durable backend with retry resilience

For features that DON'T need backend sync (local-only UI state, caches), the patterns below remain the best approach.

🚀 RECOMMENDED: Repository Pattern Approach ⭐ NEW!

Why This is Now Our Best Practice

Quick Start (Repository Pattern) ⭐

Step 1: Define Your Entity

// models/universe.model.ts
import { BaseEntity } from '@gograb-space/storage-system';

export interface Universe extends BaseEntity {
  id: string;
  name: string;
  description?: string;
  spaceId: string;
  status: 'active' | 'archived';
  createdAt: number;
  updatedAt: number;
}

Step 2: Create Repository

// repositories/universe.repository.ts
import { Injectable, inject } from '@angular/core';
import { BaseRepository } from '@gograb-space/storage-system';
import { GG_STORAGE } from '@gograb-space/storage-system/angular';
import { Universe } from '../models/universe.model';

@Injectable({ providedIn: 'root' })
export class UniverseRepository extends BaseRepository<Universe> {
  protected readonly entityName = 'Universe';
  protected readonly baseUrl = '/api/universes';

  constructor() {
    const storage = inject(GG_STORAGE);
    super(storage, {
      cache: {
        maxEntries: 50,       // L1 cache: 50 universes
        ttl: 600000,          // 10 minutes
        enableStats: true
      },
      enableEvents: true
    });
  }
}

Step 3: Use in Service

// services/space.service.ts
@Injectable({ providedIn: 'root' })
export class SpaceService {
  private universeRepo = inject(UniverseRepository);
  
  // ✅ Complete CRUD + caching automatically!
  async loadUniverse(id: string) {
    return firstValueFrom(this.universeRepo.findById(id));
  }
  
  async createUniverse(data: Partial<Universe>) {
    return firstValueFrom(this.universeRepo.create(data));
  }
  
  // ✅ NEW: Advanced querying
  async getActiveUniverses(page: number = 1) {
    return firstValueFrom(
      this.universeRepo.findAll({
        filter: { status: 'active' },
        pagination: { page, pageSize: 10 },
        sort: { field: 'createdAt', direction: 'desc' }
      })
    );
  }
  
  // ✅ NEW: Batch operations (10x faster!)
  async createMultiple(universes: Partial<Universe>[]) {
    return firstValueFrom(this.universeRepo.createMany(universes));
  }
  
  // ✅ NEW: Utility methods
  async countActive() {
    return firstValueFrom(this.universeRepo.count({ status: 'active' }));
  }
}

Step 4: Monitor Cache Performance

// Check cache statistics
const stats = this.universeRepo.getCacheStats();
console.log('Cache hit rate:', stats.hitRate, '%');
console.log('L1 cache size:', stats.size, 'entries');

// Or use signals in template
@Component({
  template: `
    <div class="stats">
      Cache Hit Rate: {{ universeRepo.cacheHitRate() }}%
      Cache Size: {{ universeRepo.cacheSize() }} entries
    </div>
  `
})

Performance: First read 15-20ms (L2), second read 1-2ms (L1 cache hit!) ⚡


🔄 Alternative: HTTP + Mock API Approach

Use when you need realistic API simulation for development:

Why This is Our Preferred Pattern

Quick Start (HTTP + Mock API)

Step 1: Configure Mock API with Intelligent Storage

// app.config.ts
import { mockHttpInterceptor, MockInterceptorUtilsService } from '@mock-api';
import { provideAppInitializer } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([mockHttpInterceptor])),
    
    // Modern app initializer pattern
    provideAppInitializer(() => {
      const utils = inject(MockInterceptorUtilsService);
      return () => utils.configureMockInterceptor({
        enabled: !environment.production,
        persistence: {
          enabled: true,
          storage: 'intelligent',      // 🧠 Uses our intelligent storage!
          namespace: 'your-app-api',
          ttl: 3600000,               // 1 hour cache
          autoCleanup: true
        }
      }, {
        // Your API mock definitions
        'GET /api/data': (req) => ({ status: 200, data: mockData }),
        'POST /api/data': (req) => ({ status: 201, data: req.body })
      });
    })
  ]
};

Step 2: Create Services Using HTTP

// your-feature.service.ts
@Injectable({ providedIn: 'root' })
export class YourFeatureService {
  private http = inject(HttpClient);

  // ✅ RECOMMENDED: HTTP calls automatically cached by intelligent storage
  getData(): Observable<any[]> {
    return this.http.get<any[]>('/api/data');
  }

  saveData(data: any): Observable<any> {
    return this.http.post<any>('/api/data', data);
  }
}

Step 3: Use in Components

// your-component.ts
@Component({
  selector: 'app-your-component',
  template: `
    <div>
      @if (data$ | async; as data) {
        @for (item of data; track item.id) {
          <p>{{ item.name }}</p>
        }
      }
      <button (click)="saveData()">Save</button>
    </div>
  `
})
export class YourComponent {
  private service = inject(YourFeatureService);
  
  data$ = this.service.getData(); // Cached by intelligent storage
  
  saveData() {
    this.service.saveData({ name: 'Test' }).subscribe();
  }
}

🔄 Alternative: Direct Storage Access

Use only for legacy features or special cases where HTTP simulation isn't needed:

Step 1: Add Storage Provider

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideGgStorage } from 'libs/storage-angular/src/lib/providers';
import { GgStorage } from 'libs/storage-core/src/lib/gg-storage';

export const appConfig: ApplicationConfig = {
  providers: [
    // ... other providers

    // ✅ CORRECT: Provide real storage with factory function
    ...provideGgStorage({
      namespace: 'my-app',               // Unique namespace for your app
      strategy: 'balanced',              // 'speed' | 'balanced' | 'durable'  
      cache: { 
        maxItems: 1000,                  // Memory cache size
        ttlMs: 300_000                   // 5 minute cache TTL
      },
      performance: {
        adaptiveStrategy: true,          // Enable intelligent adapter selection
        redundancy: true,                // Enable smart data mirroring
        monitoring: true,                // Enable performance tracking  
        batchProcessing: true,           // Enable operation batching
        microCacheBudget: 100 * 1024    // 100KB micro-cache budget
      }
    }, (config) => new GgStorage(config)),  // 🔑 CRITICAL: Real storage factory

    // ... other providers
  ]
};

Step 2: Use Storage Service

// my-feature.service.ts
import { Injectable, inject } from '@angular/core';
import { StorageService } from 'libs/storage-angular/src/lib/storage.service';

@Injectable({ providedIn: 'root' })
export class MyFeatureService {
  private readonly storage = inject(StorageService);

  // ✅ Simple, intelligent storage usage
  async saveUserData(userData: any): Promise<boolean> {
    // System automatically chooses optimal storage location and strategy
    return await this.storage.put('user/profile', userData);
  }

  async getUserData(): Promise<any> {
    return await this.storage.get('user/profile');
  }

  // ✅ Reactive storage with signals
  userSettings = this.storage.asSignal<UserSettings>('user/settings', { 
    theme: 'light', 
    language: 'en' 
  }).value;
}

Step 3: Use in Components

// my-component.ts
import { Component, inject } from '@angular/core';
import { StorageService } from 'libs/storage-angular/src/lib/storage.service';

@Component({
  selector: 'app-my-component',
  template: `
    <div>
      <p>User: {{ userData()?.name }}</p>
      <button (click)="saveData()">Save</button>
    </div>
  `
})
export class MyComponent {
  private readonly storage = inject(StorageService);
  
  // ✅ Reactive storage with automatic updates
  userData = this.storage.asSignal<any>('user/profile', null).value;

  async saveData() {
    // System handles everything intelligently
    await this.storage.put('user/profile', { name: 'John', email: '[email protected]' });
  }
}

📊 All Available Storage Types (Mock API)

When using HTTP + Mock API approach, you can choose from 5 storage types for persistence:

1. 'intelligent' (RECOMMENDED) 🧠

persistence: {
  enabled: true,
  storage: 'intelligent',      // Uses your libs/storage-core/src/lib/gg-storage intelligent system
  namespace: 'api-cache',
  ttl: 3600000                // 1 hour cache
}

What it does: Routes to optimal adapter (OPFS → Dexie → localStorage) with automatic redundancy, smart caching, and performance monitoring

Performance: First access 15-50ms, cached access **0.2ms**

When to use: 99% of cases - All production features, development with real data


2. 'indexeddb' (Legacy Direct) 💾

persistence: {
  storage: 'indexeddb',
  dbName: 'mock-api-db', 
  storeName: 'responses'
}

What it does: Direct IndexedDB access using Dexie, no intelligence

Performance: ~20-100ms first access, ~2-10ms cached

When to use: Legacy compatibility during migration, direct IndexedDB control needed


3. 'localstorage' (Legacy Direct) 🗂️

persistence: {
  storage: 'localstorage'
}

What it does: Direct localStorage access, limited to ~5-10MB

Performance: ~1-5ms (consistent but limited space)

When to use: Testing predictable localStorage behavior, debugging storage issues


4. 'memory' (Temporary) 🔄

persistence: {
  storage: 'memory'  // Default when no storage specified
}

What it does: In-memory only, data lost on refresh

Performance: ~0.1ms (fastest possible)

When to use: Unit testing, demo presentations, performance benchmarking


5. 'storage-core' (Alias) 📦

persistence: {
  storage: 'storage-core'  // Same as 'intelligent'
}

What it does: Exact same as 'intelligent' (just an alias)

📈 Performance Comparison Table

Storage Type First Access Cached Access Storage Limit Intelligence Persistence
intelligent 15-50ms 0.2ms 20GB+* ✅ Full ✅ Yes
indexeddb 20-100ms 2-10ms ~1GB ❌ None ✅ Yes
localstorage 1-5ms 1-5ms ~5-10MB ❌ None ✅ Yes
memory 0.1ms 0.1ms RAM only ❌ None ❌ No
storage-core 15-50ms 0.2ms 20GB+* ✅ Full ✅ Yes

**20GB+ = OPFS (20GB) + IndexedDB (1GB) + localStorage (10MB) combined intelligently

🎯 When NOT to Use Intelligent Storage

❌ Unit Testing - Use 'memory'

// Unit tests need clean state per test
const testConfig = {
  persistence: {
    storage: 'memory',        // ← Clean state, no side effects
    ttl: 0                   // No expiration needed
  }
};

beforeEach(() => {
  configureMockInterceptor(testConfig, testMocks);
});

❌ Demo Presentations - Use 'memory'

// Demos need fresh data each session
const demoConfig = {
  persistence: {
    storage: 'memory',        // ← Fresh data every demo
    ttl: 3600000             // 1 hour demo session
  },
  delay: [200, 500]          // Realistic delays for demo
};

❌ Performance Benchmarking - Use 'memory'

// Isolate network performance from storage performance
const benchmarkConfig = {
  persistence: {
    storage: 'memory',        // ← No I/O overhead
    ttl: 0
  },
  delay: 0                   // No artificial delays
};

❌ Storage Debugging - Use 'localstorage' or 'indexeddb'

// Force specific adapter to isolate issues
const debugConfig = {
  persistence: {
    storage: 'localstorage',  // ← Force localStorage for debugging
    autoCleanup: false        // Keep data for inspection
  }
};

❌ Legacy Migration - Use 'indexeddb'

// Temporary during migration from old IndexedDB code
const migrationConfig = {
  persistence: {
    storage: 'indexeddb',     // ← Match old database structure
    dbName: 'legacy-app-db',
    storeName: 'legacy-data'
  }
};

🧠 Storage Strategies Explained (Direct Storage Only)

Note: These strategies only apply when using direct storage access (not HTTP + Mock API). For Mock API, use storage type selection above.

Speed Strategy ('speed')

Best for: Performance-critical applications, real-time apps

provideGgStorage({ 
  strategy: 'speed',
  namespace: 'performance-app'
}, (config) => new GgStorage(config))

Balanced Strategy ('balanced') ⭐ Recommended

Best for: General-purpose applications

provideGgStorage({ 
  strategy: 'balanced',
  namespace: 'general-app' 
}, (config) => new GgStorage(config))

Durable Strategy ('durable')

Best for: Critical data applications

provideGgStorage({ 
  strategy: 'durable',
  namespace: 'critical-app'
}, (config) => new GgStorage(config))

📊 Configuration Options

Basic Configuration

...provideGgStorage({
  namespace: 'my-app',                    // REQUIRED: Unique app identifier
  strategy: 'balanced',                   // Storage strategy
  cache: { 
    maxItems: 500,                        // Memory cache limit
    ttlMs: 300_000                        // Cache TTL in milliseconds
  }
}, (config) => new GgStorage(config))

Advanced Configuration

...provideGgStorage({
  namespace: 'advanced-app',
  strategy: 'speed',
  adapterPreference: 'opfs',              // Preferred adapter when available
  cache: { 
    maxItems: 2000, 
    ttlMs: 600_000                        // 10 minute cache
  },
  performance: {
    adaptiveStrategy: true,               // Enable AI-powered decisions
    redundancy: true,                     // Enable smart data mirroring
    monitoring: true,                     // Enable performance tracking
    batchProcessing: true,                // Enable operation batching
    batchSize: 50,                        // Batch size for operations
    batchDelay: 100,                      // Delay between batches (ms)
    microCacheBudget: 200 * 1024         // 200KB micro-cache budget
  },
  sync: {
    adapter: cloudSyncAdapter             // Optional cloud synchronization
  }
}, (config) => new GgStorage(config))

Cloud Sync Configuration

import { FirestoreSyncAdapter } from 'libs/storage-sync/src/index';

const cloudSyncAdapter = new FirestoreSyncAdapter({
  projectId: "your-project-id",
  apiKey: "your-api-key",
  authDomain: "your-project.firebaseapp.com",
  databaseId: 'production-db',
  collection: 'user-data'
});

...provideGgStorage({
  namespace: 'cloud-app',
  strategy: 'balanced',
  sync: { adapter: cloudSyncAdapter }
}, (config) => new GgStorage(config))

🎯 Key Patterns for Intelligence

Use Semantic Keys

// ✅ GOOD: Descriptive keys help intelligent categorization
await storage.put('user/preferences/theme', themeData);     // → Critical: Redundant
await storage.put('auth/session/token', sessionToken);      // → Critical: Triple redundancy
await storage.put('cache/api/users', usersData);            // → Cache: IndexedDB  
await storage.put('temp/upload/file-123', fileData);        // → Temp: OPFS performance
await storage.put('settings/dashboard', dashboardConfig);   // → User-pref: Redundant

// ❌ AVOID: Generic keys that don't convey meaning
await storage.put('data1', themeData);        // System can't optimize
await storage.put('token', sessionToken);     // No intelligent routing

Leverage Intelligent Features

// ✅ Get storage insights and diagnostics
const capabilities = storage.getCapabilities();
console.log('Available adapters:', capabilities);

const performance = storage.getPerformanceStats();
if (performance) {
  console.log('Cache hit rate:', performance.cacheHitRate);
  console.log('Average operation time:', performance.avgDuration);
}

// ✅ Use micro-cache statistics for optimization
const microStats = storage.getMicroCacheStats();
console.log('Micro-cache usage:', microStats.size, '/', microStats.budget);

🚫 Critical Mistakes to Avoid

❌ Mistake 1: Using Mock Storage Factory

// ❌ WRONG: This creates a mock storage that doesn't save anything!
...provideGgStorage({
  namespace: 'my-app',
  strategy: 'balanced'
})
// Missing factory function → Uses mock implementation
// ✅ CORRECT: Always provide real storage factory
...provideGgStorage({
  namespace: 'my-app', 
  strategy: 'balanced'
}, (config) => new GgStorage(config))  // Real storage implementation

❌ Mistake 2: Manual Adapter Switching

// ❌ WRONG: Manual switching interferes with intelligence
storage.switchAdapter('localStorage');     
storage.switchNamespace('different-app');  
// ✅ CORRECT: Trust the intelligent system
// System automatically chooses optimal adapters and handles failures
await storage.put('user/data', userData);  // Intelligence handles everything

❌ Mistake 3: Multiple Storage Configurations

// ❌ WRONG: Creating multiple storage instances
export const appConfig: ApplicationConfig = {
  providers: [
    ...provideGgStorage({ namespace: 'app1' }, factory),
    ...provideGgStorage({ namespace: 'app2' }, factory), // Conflict!
    ...provideMyFeatureStorage({ namespace: 'feature' }) // Different storage!
  ]
};
// ✅ CORRECT: Single storage instance with semantic keys
export const appConfig: ApplicationConfig = {
  providers: [
    ...provideGgStorage({ namespace: 'unified-app' }, factory),
    // Use semantic keys to separate concerns:
    // 'feature1/data', 'feature2/data', etc.
  ]
};

❌ Mistake 4: Blocking Storage Operations

// ❌ WRONG: Synchronous blocking operations
const data = storage.get('user/data');  // Returns Promise, not data!
// ✅ CORRECT: Proper async/await patterns
const data = await storage.get('user/data');

// ✅ BETTER: Use reactive signals for UI
const userData = storage.asSignal('user/data', null).value;

🔍 Debugging and Monitoring

Enable Development Logging

// For development environments
...provideGgStorage({
  namespace: 'dev-app',
  strategy: 'balanced',
  performance: {
    monitoring: true,     // Enable detailed performance logging
    adaptiveStrategy: true
  }
}, (config) => new GgStorage(config))

Check Storage Health

// Get storage diagnostics
const capabilities = storage.getCapabilities();
console.log('Storage capabilities:', capabilities);

// Check current strategy
console.log('Current strategy:', storage.getStrategy());

// Get performance statistics
const stats = storage.getPerformanceStats();
if (stats) {
  console.log('Performance metrics:', stats);
}

// Check micro-cache usage  
const microStats = storage.getMicroCacheStats();
console.log('Micro-cache stats:', microStats);

Common Debug Logs

// These log patterns indicate healthy operation:
// ✅ "🧠 PUT Decision for "user/data": 💾 OPFS storage..."  
// ✅ "⚡ GET "user/data" from memory in 0.10ms"
// ✅ "📦 Micro-cached: user/settings"

// These patterns indicate problems:
// ❌ "All keys found: 0 []" (mock storage or namespace issue)
// ❌ "PUT "key" completed in 0ms, success: true" (mock storage)
// ❌ "Failed to save" followed by "success: true" (mock storage)

📁 File Organization

Recommended Project Structure

my-nx-app/
├── src/
│   ├── app/
│   │   ├── app.config.ts           ← Storage configuration here
│   │   ├── features/
│   │   │   ├── user-management/
│   │   │   │   └── user.service.ts  ← Use 'user/*' keys
│   │   │   └── dashboard/
│   │   │       └── dashboard.service.ts ← Use 'dashboard/*' keys  
│   │   └── shared/
│   │       └── services/
│   │           └── app-storage.service.ts ← Central storage service

Storage Service Patterns

// ✅ Feature-specific storage service  
@Injectable({ providedIn: 'root' })
export class UserManagementStorageService {
  private readonly storage = inject(StorageService);

  // Use semantic key patterns
  async saveProfile(userId: string, profile: UserProfile) {
    return await this.storage.put(`user/${userId}/profile`, profile);
  }

  async savePreferences(userId: string, prefs: UserPreferences) {
    return await this.storage.put(`user/${userId}/preferences`, prefs);
  }

  // Reactive data access
  getUserProfile(userId: string) {
    return this.storage.asSignal<UserProfile>(`user/${userId}/profile`, null).value;
  }
}

🔧 Advanced Usage Patterns

Reactive Data Patterns

@Component({
  selector: 'app-dashboard',
  template: `
    <div class="dashboard">
      <h1>Welcome {{ user()?.name }}</h1>
      <div class="settings">
        Theme: {{ userSettings()?.theme }}
      </div>
    </div>
  `
})
export class DashboardComponent {
  private storage = inject(StorageService);

  // ✅ Reactive signals automatically update UI
  user = this.storage.asSignal<User>('user/profile', null).value;
  userSettings = this.storage.asSignal<Settings>('user/settings', {
    theme: 'light',
    language: 'en'
  }).value;

  async updateTheme(newTheme: string) {
    const currentSettings = this.userSettings();
    if (currentSettings) {
      await this.storage.put('user/settings', {
        ...currentSettings,
        theme: newTheme
      });
      // UI automatically updates via signals!
    }
  }
}

Batch Operations

// ✅ System automatically batches for efficiency
async saveBulkData(items: DataItem[]) {
  const promises = items.map((item, index) => 
    this.storage.put(`bulk/data/${index}`, item)
  );
  
  await Promise.all(promises);
  // System automatically batches these operations for optimal performance
}

Error Handling Patterns

// ✅ Proper error handling with intelligent storage
async saveWithErrorHandling(key: string, data: any) {
  try {
    const success = await this.storage.put(key, data);
    if (success) {
      console.log('✅ Data saved successfully');
      return true;
    } else {
      console.warn('⚠️ Storage returned false - data may not be persisted');
      return false;
    }
  } catch (error) {
    console.error('❌ Storage error:', error);
    // Storage system provides automatic failover, so errors are rare
    return false;
  }
}

🎯 Storage Key Naming Conventions

Our intelligent storage system analyzes key patterns to optimize storage decisions. Follow these conventions:

Critical Data Keys

// These keys trigger intelligent redundancy and prioritization
await storage.put('user/profile', profile);          // User account data
await storage.put('auth/session/token', token);      // Authentication data
await storage.put('settings/app/config', config);    // Application settings
await storage.put('billing/subscription', billing);  // Business-critical data

Cache Data Keys

// These keys are optimized for speed and structured storage
await storage.put('cache/api/users', apiResponse);    // API response cache
await storage.put('cache/search/results', results);   // Search results
await storage.put('cache/images/thumbnails', thumbs); // Media cache

Temporary Data Keys

// These keys are optimized for performance, minimal redundancy
await storage.put('temp/upload/file-123', fileData);   // Temporary files
await storage.put('temp/draft/doc-456', draftData);    // Draft documents
await storage.put('temp/session/state', sessionData);  // Session state

Feature Data Keys

// Use your feature name as namespace
await storage.put('dashboard/widgets/config', widgets);
await storage.put('editor/documents/draft', document);
await storage.put('chat/conversations/history', chat);

🛡️ Best Practices

1. Trust the Intelligence

// ✅ GOOD: Let the system decide automatically
await storage.put('important-data', data);

// ❌ AVOID: Manual management unless debugging
storage.switchAdapter('opfs');
await storage.put('important-data', data);

2. Use Appropriate Data Types

// ✅ GOOD: Structured data with proper typing
interface UserProfile {
  id: string;
  name: string;
  email: string;
  preferences: UserPreferences;
}

await storage.put('user/profile', profile as UserProfile);

// ❌ AVOID: Unstructured or overly complex data
await storage.put('user/data', new ComplexClassInstance()); // May not serialize properly

3. Leverage Reactive Patterns

// ✅ GOOD: Use signals for reactive UI updates
readonly userTheme = this.storage.asSignal<string>('user/theme', 'light').value;

// ❌ AVOID: Manual polling or imperative updates
setInterval(async () => {
  const theme = await this.storage.get('user/theme');
  this.currentTheme = theme;
}, 1000);

4. Handle Offline Scenarios

// ✅ The system handles offline scenarios automatically
await storage.put('user/data', userData);
// Data is cached locally and synced when connection resumes

// Check storage capabilities if needed
const capabilities = storage.getCapabilities();
if (!capabilities.indexedDB) {
  console.log('Limited storage - using localStorage fallback');
}

🔄 Migration from Legacy Storage

From localStorage

// ❌ Old pattern
localStorage.setItem('userData', JSON.stringify(data));
const userData = JSON.parse(localStorage.getItem('userData') || '{}');

// ✅ New pattern  
await storage.put('user/data', data);
const userData = await storage.get('user/data');

From IndexedDB/Dexie

// ❌ Old pattern
await db.users.put({ id: userId, data: userData });
const userData = await db.users.get(userId);

// ✅ New pattern
await storage.put(`users/${userId}`, userData);
const userData = await storage.get(`users/${userId}`);

From Custom Storage Services

// ❌ Old pattern
@Injectable()
export class CustomStorageService {
  async saveToIndexedDB(key: string, data: any) { /* manual IndexedDB */ }
  async saveToLocalStorage(key: string, data: any) { /* manual localStorage */ }
  async saveToCloud(key: string, data: any) { /* manual cloud */ }
}

// ✅ New pattern
@Injectable()
export class FeatureService {
  private storage = inject(StorageService);
  
  async saveData(key: string, data: any) {
    // System handles IndexedDB, localStorage, cloud automatically
    return await this.storage.put(key, data);
  }
}

📋 Simple Implementation Checklist

✅ For New Features (RECOMMENDED - HTTP + Mock API)

// 1. Configure mock API with intelligent storage
import { mockHttpInterceptor, MockInterceptorUtilsService } from '@mock-api';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([mockHttpInterceptor])),
    {
      provide: APP_INITIALIZER,
      multi: true,
      deps: [MockInterceptorUtilsService],
      useFactory: (utils: MockInterceptorUtilsService) => () => {
        utils.configureMockInterceptor({
          persistence: {
            storage: 'intelligent',     // Uses our storage system!
            namespace: 'api-cache'
          }
        }, mockDefinitions);
      }
    }
  ]
};

// 2. Write services using HttpClient
@Injectable()
export class FeatureService {
  private http = inject(HttpClient);
  
  getData() { return this.http.get('/api/data'); }
  saveData(data: any) { return this.http.post('/api/data', data); }
}

✅ For Legacy Features (Direct Storage)

// 1. Provider configuration with real factory
import { GgStorage as CoreGgStorage } from 'libs/storage-core/src/lib/gg-storage';
import type { GgStorage, GgStorageConfig } from 'libs/storage-angular/src/lib/tokens';

export function provideYourFeature() {
  const storageConfig = {
    namespace: 'your-feature',
    strategy: 'durable' as const,
    adapterPreference: 'dexie' as const,
    performance: {
      adaptiveStrategy: true,
      batchProcessing: true,
      microCacheBudget: 10 * 1024
    }
  };

  // Type-safe wrapper that bridges storage-core and storage-angular
  const createStorageWrapper = (config: GgStorageConfig): GgStorage => {
    const coreStorage = new CoreGgStorage(config);
    
    return {
      get: (key: string) => coreStorage.get(key),
      put: (key: string, value: any) => coreStorage.put(key, value),
      remove: (key: string) => coreStorage.remove(key),
      has: (key: string) => coreStorage.has(key),
      list: (options?: any) => coreStorage.list(options),
      clear: (prefix?: string) => coreStorage.clear(prefix),
      getConfig: () => config,
      onEvent: (listener: (event: any) => void) => coreStorage.onEvent(listener),
      getAdapterKind: () => coreStorage.getAdapterKind?.() || 'localStorage',
      switchAdapter: (kind: 'opfs' | 'dexie' | 'localStorage') => coreStorage.switchAdapter(kind)
    } as GgStorage;
  };

  return makeEnvironmentProviders([
    ...provideGgStorage(storageConfig, createStorageWrapper),
    YourFeatureService
  ]);
}

// 2. Simple service implementation
@Injectable()
export class YourFeatureService {
  private readonly storage = inject(GG_STORAGE);

  constructor() {
    console.log('📊 Storage initialized:', {
      adapter: this.storage.adapterKind,
      strategy: this.storage.getStrategy?.()
    });
  }

  // Use semantic keys for intelligence
  async save(id: string, data: any): Promise<boolean> {
    return await this.storage.put(`data/${id}`, data);
  }

  async load(id: string): Promise<any> {
    return await this.storage.get(`data/${id}`);
  }

  async listAll(): Promise<any[]> {
    const keys = await this.storage.list('data/');
    const items = [];
    
    for (const key of keys) {
      const item = await this.storage.get(key);
      if (item) items.push(item);
    }
    
    return items;
  }
}

🎯 Key Principles

✅ DO

  1. Always provide real factory: Use the type-safe wrapper pattern shown above
  2. Use semantic keys: themes/data/theme-123, users/profiles/user-456
  3. Trust the intelligence: Let the system choose adapters and strategies
  4. Keep it simple: Avoid manual forcing, nuclear options, or complex recovery
  5. Configure once: Set up proper config in providers, then trust it

❌ DON'T

  1. Never omit factory: ...provideGgStorage(config) gives you mock storage
  2. Don't force adapters: storage.switchAdapter() fights the intelligence
  3. Don't use nuclear options: Complex forcing indicates design issues
  4. Don't complicate keys: Use semantic, hierarchical keys
  5. Don't bypass the system: Direct localStorage/IndexedDB defeats the purpose

🚀 Migration Pattern

For Existing Broken Services

  1. Fix the provider: Add real storage factory using type-safe wrapper
  2. Simplify the constructor: Remove forcing/nuclear code
  3. Use semantic keys: Replace complex key patterns
  4. Remove recovery code: The intelligent system handles persistence
  5. Test: Verify data actually persists after refresh

📚 Next Steps

  1. Smart Mock API Pattern - RECOMMENDED approach for all features
  2. Common Mistakes - Learn from real-world implementation issues
  3. Theme System Integration - See how we integrated themes
  4. Performance Guide - Optimize your storage usage
  5. Cloud Sync Guide - Add cloud synchronization

🎯 Summary: Choose Your Approach

🚀 RECOMMENDED: HTTP + Mock API

🔧 Alternative: Direct Storage

Remember: Our intelligent storage system is designed to handle complexity automatically. Whether you choose HTTP + Mock API or direct storage, trust the intelligence, use semantic keys, and leverage reactive patterns for the best results!