168 lines
6.3 KiB
Markdown
168 lines
6.3 KiB
Markdown
# Enterprise Storage Engine (`@repo/core-storage`)
|
|
|
|
[← Back to Root](../../README.md)
|
|
|
|
The **Enterprise-grade storage engine** for the monorepo.
|
|
|
|
This package provides a unified, Factory-based interface for interacting with browser storage (`localStorage` and `IndexedDB`). It enforces strict type safety, **Runtime Validation**, App Autonomy (Inversion of Control), and automatically provides **AES encryption at rest** for sensitive payloads (like access tokens) using `@repo/utils`.
|
|
|
|
---
|
|
|
|
## Architecture & Data Flow
|
|
|
|
```mermaid
|
|
graph TD
|
|
subgraph Apps ["apps/* (App Autonomy)"]
|
|
REG[[AppStorageKey & App Registries]]
|
|
UI[React Components / API Interceptors]
|
|
INST{{Storage Instances}}
|
|
end
|
|
|
|
subgraph Core ["@repo/core-storage (Engine Factories)"]
|
|
API[IStorageService API]
|
|
FAC[createLocalStorage / createIndexedDB]
|
|
VAL{Runtime Gatekeeper}
|
|
ENC{{AES Encryption Pipeline}}
|
|
LOCAL[LocalStorage Adapter]
|
|
IDB[IndexedDB Adapter]
|
|
end
|
|
|
|
subgraph Browser ["Browser APIs (Native)"]
|
|
B_LOCAL[(localStorage)]
|
|
B_IDB[(IndexedDB)]
|
|
end
|
|
|
|
REG -.->|Injects Keys & Config| FAC
|
|
FAC --> INST
|
|
UI -->|getItem / setItem| INST
|
|
INST --> API
|
|
API --> VAL
|
|
|
|
VAL -.->|Valid Key?| ENC
|
|
VAL -.->|Invalid Key!| ERR[Throws Security Exception]
|
|
|
|
ENC -.->|Sensitive Key| LOCAL & IDB
|
|
VAL -.->|Plain-text Key| LOCAL & IDB
|
|
|
|
LOCAL <--> B_LOCAL
|
|
IDB <--> B_IDB
|
|
|
|
%% Styling Subgraphs
|
|
style Apps fill:#e7f5ff,stroke:#74c0fc,stroke-width:2px,color:#1864ab
|
|
style Core fill:#f8f9fa,stroke:#ced4da,stroke-width:2px,color:#495057
|
|
style Browser fill:#f1f3f5,stroke:#ced4da,stroke-width:2px,color:#495057
|
|
|
|
%% Styling Nodes
|
|
style UI fill:#339af0,stroke:#1864ab,color:#fff
|
|
style REG fill:#1864ab,stroke:#1864ab,color:#fff
|
|
style INST fill:#339af0,stroke:#1864ab,color:#fff
|
|
style API fill:#845ef7,stroke:#5f3dc4,color:#fff
|
|
style FAC fill:#845ef7,stroke:#5f3dc4,color:#fff
|
|
|
|
%% Gatekeeper is GREEN (Security Checkpoint), Error is RED
|
|
style VAL fill:#20c997,stroke:#089981,color:#fff
|
|
style ERR fill:#fa5252,stroke:#c92a2a,color:#fff
|
|
|
|
style LOCAL fill:#845ef7,stroke:#5f3dc4,color:#fff
|
|
style IDB fill:#845ef7,stroke:#5f3dc4,color:#fff
|
|
style ENC fill:#fab005,stroke:#e67700,color:#fff
|
|
style B_LOCAL fill:#868e96,stroke:#495057,color:#fff
|
|
style B_IDB fill:#868e96,stroke:#495057,color:#fff
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 Primary Goals & Architectural Principles
|
|
|
|
* **App Autonomy (Inversion of Control)**: The core storage engine does not know about your application's keys. Consuming applications define their own keys, their own `encryptedKeys` sets, and their own `plainTextKeys` sets, injecting them into the factory upon instantiation.
|
|
* **Runtime Gatekeeper (Defensive Programming)**: The engine validates every `setItem`, `getItem`, and `removeItem` operation. If an app attempts to access a key that wasn't explicitly registered in `encryptedKeys` or `plainTextKeys`, the engine will immediately throw a Security Exception to prevent rogue data access/injection.
|
|
* **Dual Backend Strategy**:
|
|
* `createLocalStorage`: Ideal for small, synchronous-like data (tokens, user preferences, settings).
|
|
* `createIndexedDB`: 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 passed in the `encryptedKeys` configuration, the engine handles AES encryption transparently.
|
|
* **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.
|
|
|
|
---
|
|
|
|
## 🚀 App-Level Setup & Usage
|
|
|
|
### 1. Define App Keys and Instantiate (Inversion of Control)
|
|
|
|
In your consuming application (e.g., `apps/web/src/core/storage/index.ts`), define your keys and use the factories to create your instances.
|
|
|
|
```typescript
|
|
// apps/web/src/core/storage/index.ts
|
|
import { createLocalStorage, createIndexedDB } from '@repo/core-storage';
|
|
|
|
// 1. Define Keys
|
|
export const AppStorageKey = {
|
|
USER_PROFILE: 'user_profile',
|
|
ACCESS_TOKEN: 'access_token',
|
|
LOCALE: 'app_locale',
|
|
} as const;
|
|
|
|
export type AppStorageKeyValue = (typeof AppStorageKey)[keyof typeof AppStorageKey];
|
|
|
|
// 2. Classify Keys
|
|
export const ENCRYPTED_KEYS = new Set<AppStorageKeyValue>([
|
|
AppStorageKey.USER_PROFILE,
|
|
AppStorageKey.ACCESS_TOKEN,
|
|
]);
|
|
|
|
export const PLAIN_KEYS = new Set<AppStorageKeyValue>([
|
|
AppStorageKey.LOCALE,
|
|
]);
|
|
|
|
// 3. Instantiate Factories
|
|
export const secureStorage = createLocalStorage<AppStorageKeyValue>({
|
|
encryptedKeys: ENCRYPTED_KEYS,
|
|
plainTextKeys: PLAIN_KEYS
|
|
});
|
|
|
|
export const secureIndexedDB = createIndexedDB<AppStorageKeyValue>({
|
|
dbName: 'eigen_erp_db',
|
|
storeName: 'web_store',
|
|
encryptedKeys: ENCRYPTED_KEYS,
|
|
plainTextKeys: PLAIN_KEYS
|
|
});
|
|
```
|
|
|
|
### 2. Usage in App Components
|
|
|
|
Now, you can import your locally-created instances anywhere in your app.
|
|
|
|
```typescript
|
|
import { secureStorage, AppStorageKey } from '@/core/storage';
|
|
import type { UserProfile } from '@/types';
|
|
|
|
// CREATE / UPDATE
|
|
// Since USER_PROFILE is in ENCRYPTED_KEYS, it is AES-encrypted automatically.
|
|
await secureStorage.setItem(AppStorageKey.USER_PROFILE, {
|
|
id: 1,
|
|
name: 'Firman',
|
|
role: 'admin'
|
|
});
|
|
|
|
// READ (Returns null if not found or if decryption fails)
|
|
const profile = await secureStorage.getItem<UserProfile>(AppStorageKey.USER_PROFILE);
|
|
if (profile) {
|
|
console.log('Welcome back,', profile.name);
|
|
}
|
|
|
|
// DELETE
|
|
await secureStorage.removeItem(AppStorageKey.USER_PROFILE);
|
|
```
|
|
|
|
### 3. The Runtime Gatekeeper
|
|
|
|
If you try to access an unregistered key, the engine protects the app by throwing an error at runtime:
|
|
|
|
```typescript
|
|
// Throws Error: "[Storage Engine] Security Exception: Key 'rogue_key' is not registered..."
|
|
await secureStorage.setItem('rogue_key' as any, 'hacked');
|
|
```
|
|
|
|
---
|
|
|
|
> [!WARNING]
|
|
> **Migration Hazard**: If you move an existing key from `plainTextKeys` to `encryptedKeys` (or vice versa), 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. |