# @repo/core-storage The **Enterprise-grade storage engine** for the monorepo. This package provides a unified, Promise-based interface for interacting with browser storage (`localStorage` and `IndexedDB`). It enforces strict type safety, prevents key collisions, and automatically provides **AES encryption at rest** for sensitive payloads (like access tokens) using `@repo/utils`. --- ## 🎯 Primary Goals & Separation of Concerns * **Separation from `@repo/core-api`**: Storage is a fundamental primitive. While the API client uses storage (to retrieve tokens), storage itself does not need to know about HTTP requests. * **Dual Backend Strategy**: * `secureStorage` (localStorage): Ideal for small, synchronous-like data (tokens, user preferences). * `IndexedDBService`: Built for large, asynchronous data (offline drafts, cached API responses, blobs) without the 5MB size limit. * **Security by Default**: Developers don't need to manually encrypt/decrypt data. If a key is marked as sensitive, the library handles AES encryption transparently. --- ## ✨ Key Features | Feature | Description | |---|---| | 🔒 **Selective Encryption** | Uses `@repo/utils` `EncryptionUtils` to automatically AES-encrypt payloads whose keys are listed in `ENCRYPTED_KEYS`. | | 🛡️ **Type-Safe Keys** | All keys must be registered in `storage.key.ts`. Prevents typos and key collisions across the monorepo. | | 🔄 **Unified Promise API** | Both `localStorage` and `IndexedDB` implement the same async `IStorageService` interface. | | 🧬 **Strict Generics** | Read and write operations enforce payload types via generics (e.g., `getItem('user_profile')`). | | 🩹 **Corrupt Data Resilience** | If parsing or decryption fails (e.g., tampered data), the corrupt entry is safely removed and returns `null`. | --- ## 🚀 Usage Examples ### 1. Secure Local Storage (Tokens, Profile) Use `demoSecureStorage` (or your app's configured instance) for small payloads. If the key is in `ENCRYPTED_KEYS`, it will be encrypted at rest. ```typescript import { demoSecureStorage, StorageKey } from '@repo/core-storage'; import type { UserProfile } from '@/types'; // CREATE / UPDATE // If StorageKey.USER_PROFILE is in ENCRYPTED_KEYS, this is AES-encrypted automatically. await demoSecureStorage.setItem(StorageKey.USER_PROFILE, { id: 1, name: 'Firman', role: 'admin' }); // READ const profile = await demoSecureStorage.getItem(StorageKey.USER_PROFILE); if (profile) { console.log('Welcome back,', profile.name); } // DELETE await demoSecureStorage.removeItem(StorageKey.USER_PROFILE); ``` ### 2. IndexedDB (Offline Data, Large Payloads) Use the pre-configured `demoIndexedDB` (or instantiate your own) for large, asynchronous data. It uses the exact same API and encryption pipeline. ```typescript import { demoIndexedDB } from '@repo/core-storage'; interface DraftData { id: string; content: string; lastModified: number; } // Save a large draft offline await demoIndexedDB.setItem('offline_draft_123', { id: '123', content: 'Huge text content...', lastModified: Date.now() }); // Retrieve the draft const draft = await demoIndexedDB.getItem('offline_draft_123'); ``` --- ## 🔑 Adding New Keys To maintain type safety and avoid collisions, **all** `localStorage` keys must be registered in `packages/core-storage/src/storage.key.ts`. ### 1. Register the Key Add your key to the `StorageKey` object: ```typescript export const StorageKey = { // ... existing keys MY_NEW_FEATURE: 'my_new_feature_key', } as const; ``` ### 2. Define Encryption (If Needed) If the data stored under this key is sensitive (e.g., PII, tokens, financials), add it to the `ENCRYPTED_KEYS` set. ```typescript export const ENCRYPTED_KEYS: ReadonlySet = new Set([ StorageKey.ACCESS_TOKEN, StorageKey.REFRESH_TOKEN, StorageKey.USER_PROFILE, StorageKey.MY_NEW_FEATURE, // <--- Now encrypted at rest! ]); ``` > [!WARNING] > If you add an existing plain-text key to `ENCRYPTED_KEYS`, existing users will experience a parsing failure on their next session (because the library expects encrypted data but finds plain text). The resilient parser will catch this and clear the key, effectively logging them out or resetting the preference.