Files
trackgo-fe/packages/core-storage

Enterprise Storage Engine (@repo/core-storage)

← Back to Root

This package provides an Offline-First Storage Engine using PouchDB, tailored for Enterprise React applications. It is built to seamlessly sync with remote CouchDB instances, providing full fault tolerance and offline capabilities.

High-Level Overview

Our storage architecture enforces strict Inversion of Control (IoC). The core engine (@repo/core-storage) is a pure factory—it knows absolutely nothing about your business domains, data models, or specific databases. Consuming applications (like apps/web) dictate the rules by injecting their specific configurations and generic types into the storage engine.

graph TD
    subgraph UI ["Consuming App (apps/*)"]
        COMP["React Components / Forms"]
    end

    subgraph CoreStorage ["@repo/core-storage Engine"]
        MGR["PouchDatabaseManager Factory"]
        L_SALES[("Local PouchDB: Sales")]
        L_INV[("Local PouchDB: Inventory")]
    end

    subgraph RemoteServer ["CouchDB Cluster (Cloud/On-Prem)"]
        R_SALES[("Remote CouchDB: sales_db")]
        R_INV[("Remote CouchDB: inventory_db")]
    end

    COMP -->|Read / Write| L_SALES
    COMP -->|Read / Write| L_INV
    MGR -->|Instantiates Multi-DB| L_SALES
    MGR -->|Instantiates Multi-DB| L_INV

    L_SALES <-->|Native Sync Live and Retry| R_SALES
    L_INV <-->|Native Sync Live and Retry| R_INV

    %% Styling Nodes
    style MGR fill:#339af0,stroke:#1864ab,color:#fff
    style L_SALES fill:#845ef7,stroke:#5f3dc4,color:#fff
    style L_INV fill:#845ef7,stroke:#5f3dc4,color:#fff
    style R_SALES fill:#fab005,stroke:#e67700,color:#fff
    style R_INV fill:#fab005,stroke:#e67700,color:#fff

Core Concepts & Usage

1. Initialization & Registration (PouchDatabaseManager)

The PouchDatabaseManager acts as the IoC Factory. Apps use it to register and initialize multiple discrete PouchDB databases using a PouchConfig.

Why we use this pattern: Instead of scattering raw database instantiations across the codebase, the manager centralizes connections. If a database is requested twice, the manager efficiently returns the exact same instance.

import { PouchDatabaseManager } from '@repo/core-storage';
import type { Item } from './types';

export const dbManager = new PouchDatabaseManager();

// Register a strictly-typed database with bi-directional sync
export const itemDB = dbManager.register<Item>({
  localName: 'items_db',
  remoteUrl: 'http://admin:password@localhost:5984/items_db' 
});

2. CRUD & Queries (PouchDatabaseWrapper)

When you register a database, you receive a strictly typed PouchDatabaseWrapper. This wrapper abstracts away the raw PouchDB API, giving developers clean, Promise-based helper methods without ever needing to pass dbName or complex identifiers repeatedly.

Method Description
create(data) Inserts a new document. PouchDB will auto-generate an _id if omitted.
update(id, data) Automatically fetches the latest _rev to merge the payload, preventing conflict errors.
delete(id) Automatically fetches the latest _rev to safely remove the document.
getOne(id) Retrieves a single document by its _id.
getAll() Retrieves all documents, automatically filtering out internal _design/ docs.
find(options) Queries using MongoDB-style selectors (via pouchdb-find).

Example of find() with Selectors: Instead of pulling all documents into memory and filtering them with JavaScript, we leverage native MongoDB-style selectors for performance:

const expensiveItems = await itemDB.find({
  selector: {
    price: { $gt: 100 },
    category: 'electronics'
  }
});

3. Real-Time Reactivity (The onChange Pub/Sub Pattern)

CRITICAL CONCEPT: We do not expose the raw db.changes() feed directly to React components. Instead, the PouchDatabaseWrapper utilizes a clean Pub/Sub abstraction via the .onChange(callback) method.

Why we use this pattern:

  1. Memory Safety: Direct bindings to PouchDB's raw changes feed often lead to zombie listeners and memory leaks. The .onChange() returns an unsubscribe function natively tailored for React's useEffect cleanup block.
  2. Connection Efficiency: It maintains a single WebSocket/Polling connection to the database under the hood. Multiple React components can subscribe to the same wrapper without opening dozens of parallel database connections.
import { useEffect, useCallback, useState } from 'react';
import { itemDB } from '../core/db';

export function InventoryList() {
  const [items, setItems] = useState([]);

  const loadData = useCallback(async () => {
    const data = await itemDB.getAll();
    setItems(data);
  }, []);

  useEffect(() => {
    // 1. Initial Load
    loadData();

    // 2. Subscribe to local mutations AND remote CouchDB syncs
    const unsubscribe = itemDB.onChange(() => {
      console.log('Database updated locally or remotely. Refreshing...');
      loadData();
    });

    // 3. Prevent memory leaks!
    return () => {
      unsubscribe();
    };
  }, [loadData]);

  // UI rendering...
}

4. CouchDB Sync & CORS Troubleshooting

By providing a remoteUrl to the manager, the engine automatically handles bi-directional synchronization in the background (live: true, retry: true). If the server goes down, the local app will continue working seamlessly and sync automatically when the connection is restored.

Warning

CORS Infinite Retries & Preflight Failures If your browser blocks the synchronization with a CORS error, you will see PouchDB enter an infinite retry loop in the network tab.

Do NOT try to fix this in the frontend code! This is exclusively a CouchDB server configuration issue. You must enable CORS directly on the CouchDB instance by editing its local.ini or using its dashboard configuration to allow origins, credentials, and headers.