Files
trackgo-fe/packages/core-storage

Enterprise Storage Engine (@repo/core-storage)

← Back to Root

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.


Architecture & Data Flow

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 (Native)"]
        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

    %% Styling Subgraphs (Backgrounds)
    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 App Nodes (Blue)
    style UI fill:#339af0,stroke:#1864ab,color:#fff

    %% Styling Core Nodes (Purple Engine, Green Registry, Gold Encryption)
    style API fill:#845ef7,stroke:#5f3dc4,color:#fff
    style LOCAL fill:#845ef7,stroke:#5f3dc4,color:#fff
    style IDB fill:#845ef7,stroke:#5f3dc4,color:#fff
    style REG fill:#20c997,stroke:#089981,color:#fff
    style ENC fill:#fab005,stroke:#e67700,color:#fff

    %% Styling Browser Nodes (Neutral Gray)
    style B_LOCAL fill:#868e96,stroke:#495057,color:#fff
    style B_IDB fill:#868e96,stroke:#495057,color:#fff

🎯 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, 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.
  • 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 for small payloads. If the key is in ENCRYPTED_KEYS, it will be encrypted at rest automatically.

import { secureStorage, 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 secureStorage.setItem(StorageKey.USER_PROFILE, {
  id: 1,
  name: 'Firman',
  role: 'admin'
});

// 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);
}

// DELETE
await secureStorage.removeItem(StorageKey.USER_PROFILE);

2. IndexedDB (Offline Data, Large Payloads)

Use the pre-configured secureIndexedDB for large, asynchronous data. It uses the exact same IStorageService interface and encryption pipeline as local storage.

import { secureIndexedDB } from '@repo/core-storage';

interface DraftData {
  id: string;
  content: string;
  lastModified: number;
}

// Save a large draft offline (No 5MB limit)
await secureIndexedDB.setItem('offline_draft_123', {
  id: '123',
  content: 'Huge text content...',
  lastModified: Date.now()
});

// Retrieve the draft
const draft = await secureIndexedDB.getItem<DraftData>('offline_draft_123');

🔑 Adding New Keys (Current Registry Pattern)

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

Add your key to the StorageKey object:

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.

export const ENCRYPTED_KEYS: ReadonlySet<string> = new Set<string>([
  StorageKey.ACCESS_TOKEN,
  StorageKey.REFRESH_TOKEN,
  StorageKey.USER_PROFILE,
  StorageKey.MY_NEW_FEATURE, // <--- Now encrypted at rest!
]);

Warning

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:

// 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'],
});