docs: update README files for core packages with architecture diagrams and usage examples

This commit is contained in:
Firman Ramdhani
2026-05-28 12:20:53 +07:00
parent df229c9984
commit c510feadbb
4 changed files with 255 additions and 87 deletions
+74 -22
View File
@@ -1,4 +1,6 @@
# @repo/core-storage
# Enterprise Storage Engine (`@repo/core-storage`)
[← Back to Root](../../README.md)
The **Enterprise-grade storage engine** for the monorepo.
@@ -6,33 +8,61 @@ This package provides a unified, Promise-based interface for interacting with br
---
## Architecture & Data Flow
```mermaid
graph TD
subgraph Apps ["apps/* (Consumers)"]
UI[React Components / API Interceptors]
end
subgraph Core ["@repo/core-storage (Engine)"]
API[IStorageService API]
REG[[StorageKey & ENCRYPTED_KEYS Registry]]
ENC{{AES Encryption Pipeline}}
LOCAL[LocalStorage Adapter]
IDB[IndexedDB Adapter]
end
subgraph Browser ["Browser APIs"]
B_LOCAL[(localStorage)]
B_IDB[(IndexedDB)]
end
UI -->|getItem / setItem| API
API --> REG
REG -.->|Sensitive Key?| ENC
ENC -.-> LOCAL & IDB
REG -.->|Plain-text Key| LOCAL & IDB
LOCAL <--> B_LOCAL
IDB <--> B_IDB
style Core fill:#f8f9fa,stroke:#ced4da
style ENC fill:#fab005,color:#fff,stroke:#fff
style Browser fill:#e9ecef,stroke:#adb5bd
```
---
## 🎯 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.
* `secureStorage` (localStorage): Ideal for small, synchronous-like data (tokens, user preferences, settings).
* `secureIndexedDB` (IndexedDB): Built for large, asynchronous data (offline drafts, cached API responses, large arrays/blobs) bypassing the 5MB localStorage 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<UserProfile>('user_profile')`). |
| 🩹 **Corrupt Data Resilience** | If parsing or decryption fails (e.g., tampered data), the corrupt entry is safely removed and returns `null`. |
* **Corrupt Data Resilience**: If parsing or decryption fails (e.g., tampered data or changed encryption keys), the corrupt entry is safely removed and returns `null`, preventing the app from crashing.
---
## 🚀 Usage Examples
Because this package is framework-agnostic, these instances can be imported anywhere: React components, Redux/Zustand stores, or Axios interceptors.
### 1. Secure Local Storage (Tokens, Profile)
Use `secureStorage` (or your app's configured instance) for small payloads. If the key is in `ENCRYPTED_KEYS`, it will be encrypted at rest.
Use `secureStorage` for small payloads. If the key is in `ENCRYPTED_KEYS`, it will be encrypted at rest automatically.
```typescript
import { secureStorage, StorageKey } from '@repo/core-storage';
@@ -46,7 +76,7 @@ await secureStorage.setItem(StorageKey.USER_PROFILE, {
role: 'admin'
});
// READ
// READ (Returns null if not found or if decryption fails)
const profile = await secureStorage.getItem<UserProfile>(StorageKey.USER_PROFILE);
if (profile) {
console.log('Welcome back,', profile.name);
@@ -58,7 +88,7 @@ await secureStorage.removeItem(StorageKey.USER_PROFILE);
### 2. IndexedDB (Offline Data, Large Payloads)
Use the pre-configured `secureIndexedDB` (or instantiate your own) for large, asynchronous data. It uses the exact same API and encryption pipeline.
Use the pre-configured `secureIndexedDB` for large, asynchronous data. It uses the exact same `IStorageService` interface and encryption pipeline as local storage.
```typescript
import { secureIndexedDB } from '@repo/core-storage';
@@ -69,7 +99,7 @@ interface DraftData {
lastModified: number;
}
// Save a large draft offline
// Save a large draft offline (No 5MB limit)
await secureIndexedDB.setItem('offline_draft_123', {
id: '123',
content: 'Huge text content...',
@@ -82,9 +112,9 @@ const draft = await secureIndexedDB.getItem<DraftData>('offline_draft_123');
---
## 🔑 Adding New Keys
## 🔑 Adding New Keys (Current Registry Pattern)
To maintain type safety and avoid collisions, **all** `localStorage` keys must be registered in `packages/core-storage/src/storage.key.ts`.
To maintain type safety and avoid collisions across the monorepo, **all** global storage keys must currently be registered in `packages/core-storage/src/storage.key.ts`.
### 1. Register the Key
@@ -111,4 +141,26 @@ export const ENCRYPTED_KEYS: ReadonlySet<string> = new Set<string>([
```
> [!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.
> **Migration Hazard**: 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 gracefully clear the key, which may effectively log them out or reset their local preference.
---
## 🚧 Planned Updates: Inversion of Control (IoC) Refactor
**Current Limitation:** Currently, the `StorageKey` and `ENCRYPTED_KEYS` registries live inside `@repo/core-storage`. This violates the strict **App Autonomy (IoC)** principle established in other core packages (like `@repo/core-events`), as consuming applications must modify the core package to register their app-specific keys.
**Future Architecture Roadmap:** In a future major update, this package will be refactored into a pure Factory/Engine pattern to fully decouple it from application business logic.
1. The core will export a generic `StorageEngine` class or `createStorage()` factory.
2. Apps (`apps/web`, `apps/landing`) will instantiate their own storage engines and define their own key registries and encryption rules autonomously.
*Proposed Future API:*
```typescript
// apps/web/src/lib/storage.ts
import { StorageEngine } from '@repo/core-storage';
export const webStorage = new StorageEngine({
prefix: 'web_erp_',
encryptedKeys: ['access_token', 'user_profile'],
});
```