# @repo/core-events
[← Back to Root](../../README.md)
## Overview
`@repo/core-events` is the **decoupled Nervous System** of the ERP. It provides a highly performant, strictly typed Event Bus powered by `mitt` and React hooks.
By routing communication through a centralized event bus, we achieve:
- **Zero Coupling**: Publishers and subscribers don't need to import or know about each other.
- **Extreme Performance**: Components can subscribe to high-frequency data streams (like WebSockets) and update their own local state *without* triggering massive React tree re-renders.
- **Memory Safety**: The provided `useAppEvent` hook automatically handles subscription cleanup on component unmount, preventing the most common source of memory leaks in SPA architectures.
- **Strict Contracts**: The `AppEvents` registry enforces payload shapes at compile-time, ensuring publishers and subscribers always agree on the data contract.
---
## Architecture
```mermaid
graph TD
subgraph Publishers
A[Cashier UI]
B[Profile Settings]
C[WebSocket Client]
end
subgraph Core
E((Event Bus
mitt))
R[[AppEvents
Registry]] -.-> E
end
subgraph Subscribers
X[Electron IPC Bridge]
Y[IndexedDB Sync]
Z[Stock Grid Row]
end
A -- "DEVICE:PRINT_RECEIPT" --> E
B -- "AUTH:PROFILE_UPDATED" --> E
C -- "WS:STOCK_UPDATE" --> E
E -.-> X
E -.-> Y
E -.-> Z
style E fill:#4263eb,color:#fff,stroke:#fff
style R fill:#2b8a3e,color:#fff,stroke:#fff
```
---
## Defining Events
Every event in the system MUST be registered in `src/events.registry.ts`. This provides a single source of truth and full autocomplete across the codebase.
To add a new event, simply extend the `AppEvents` type:
```typescript
// packages/core-events/src/events.registry.ts
export interface CheckoutPayload {
orderId: string;
total: number;
}
export type AppEvents = {
// Existing events...
'DEVICE:PRINT_RECEIPT': PrintReceiptPayload;
// Your new event:
'STORE:CHECKOUT_COMPLETED': CheckoutPayload;
};
```
---
## Usage Examples
Here are three real-world architectural patterns powered by the Event Bus.
### Example 1: Hardware Abstraction (Cross-Platform)
**Problem**: The web app needs to print receipts. If running in a browser, it should use `window.print()`. If running in the Electron wrapper, it must use the secure IPC bridge (`window.electronAPI.print()`). We don't want the UI components cluttered with platform-detection logic.
**Solution**: The UI publishes a blind event. A headless listener handles the platform routing.
**Publisher (Cashier UI)**:
```tsx
import { usePublishEvent } from '@repo/core-events';
export function CashierUI() {
const publish = usePublishEvent();
const handlePrint = () => {
// Fire and forget. Zero knowledge of how printing actually happens.
publish('DEVICE:PRINT_RECEIPT', {
receiptId: 'RCP-123',
items: [...],
total: 45.00,
cashierName: 'Firman'
});
};
return ;
}
```
**Subscriber (Headless Listener)**:
```tsx
import { useAppEvent } from '@repo/core-events';
export function PrinterListener() {
useAppEvent('DEVICE:PRINT_RECEIPT', (payload) => {
const isElectron = typeof window !== 'undefined' && !!window.electronAPI;
if (isElectron) {
// Route via secure Electron IPC bridge
window.electronAPI.print({ silent: true });
} else {
// Fallback to standard browser print dialog
window.print();
}
});
return null; // Renders nothing
}
```
---
### Example 2: Extreme Performance (High-Frequency Data)
**Problem**: A massive data grid (1,000+ rows) receives 50 WebSocket updates per second. If the parent grid holds the state and passes it down via props, React will attempt to re-render all 1,000 rows 50 times a second, crushing the browser.
**Solution**: The parent grid renders empty rows. Each row subscribes to the event bus and filters updates so it only re-renders when its specific data changes.
**Parent Grid (Never re-renders)**:
```tsx
export function LiveStockGrid() {
// Generates 1000 IDs once. No stock data is stored here!
const stockIds = generateStockIds(1000);
return (