@muchekechege/pulsesync
v1.3.0
Published
Universal High-performance pull-model sync for React and backend node.js
Downloads
78
Maintainers
Keywords
Readme
PulseSync ⚡️
A high-performance, Pull-Model state synchronization library for React. Specifically engineered for high-frequency data (Trading Bots, WebSockets, Tickers) where traditional React state or "push" models (like standard Redux or MobX) cause UI lag and "data skipping" on mobile devices.
🚀 The Problem: "The Push Bottleneck"
In high-frequency environments (like XAUUSD markets), ticks arrive constantly. Traditional state managers push every update to React:
WebSocket -> Store -> Notify -> React Render -> Browser Paint
On a mobile device, if the "Paint" takes too long, the next tick is blocked. This leads to:
- Skipped Ticks: The WebSocket buffer overflows because the UI thread is stuck.
- UI Stutters: The Garbage Collector (GC) works overtime cleaning up discarded state objects.
- Crashes: Memory pressure builds up until the mobile browser kills the tab.
🛡️ The Solution: "The Pull Model"
PulseSync reverses the flow. It updates a silent internal store instantly ($<0.1ms$) and allows React to pull the data only when the browser is ready to draw a frame:
WebSocket -> Store (Instant Mutation)
Browser Heartbeat -> requestAnimationFrame -> Pull Latest -> React Render
📦 Installation
npm install @muchekechege/pulsesyncusage
1. Create a Store
Define your initial state and initialize the store. This creates a singleton-like instance and a custom hook.
// stores/marketStore.ts
import { createPulseStore } from '@muchekechege/pulsesync';
export const { store, useSync } = createPulseStore({
price: 0,
timeStamp: 0,
isCalculated: false
});
// Helper to update the store outside of the component
export const increment = () => {
store.update({ price: store.getSnapshot().price + 1 });
};
export const decrement = () => {
store.update({ price: store.getSnapshot().price - 1 });
};
2. Silent Updates (High Frequency)
Update the store inside your WebSocket listeners or calculation loops. These updates are "silent"—they do not trigger React's heavy rendering cycle immediately.
// logic/bot.ts
import { store } from './stores/marketStore';
socket.on('message', (data) => {
// Ultra-fast mutation using Object.assign
// Marks state as 'dirty' to trigger a sync on the next frame
store.update({
price: data.p,
timeStamp: data.d
});
});3. Consume in React
Use the useSync hook with a selector. The component will only re-render if the specific slice of data you selected has changed during the last "heartbeat".
import { useSync } from './stores/marketStore';
const PriceDisplay = () => {
// Only pulls 'price'. Ignores updates to 'timeStamp'.
// Default equality is strict (a === b)
const price = useSync(state => state.price);
return <h1>${price.toFixed(2)}</h1>;
};4. Advanced: Custom Equality
For selectors that return arrays or objects, pass a custom equality function as a second argument to useSync to prevent unnecessary renders.
const orders = useSync(
state => state.orders,
(prev, next) => prev.length === next.length // Custom comparison
);Backend usage
On the backend, you treat the store object as a state-persistent buffer. In a Node.js environment, this is particularly powerful for handling high-frequency data (like market ticks) because it batches updates before triggering expensive operations like database writes or WebSocket broadcasts.
Here is how you initialize and use it to store and monitor data exactly like you would in a frontend state.
1. Initialization and "State" Logic
On the backend, you typically use the store returned by createPulseStore. This instance will now use setTimeout internally to provide the "Pulse".
// backend/marketStore.ts
import { createPulseStore } from '@muchekechege/pulsesync';
// 1. Initialize with your starting state
export const { store } = createPulseStore({
ticks: [] as number[],
lastMatch: null as number | null,
isBotActive: false,
connectionStatus: 'disconnected'
});
/**
* Logic to update state (Same way you do in React, but on the Server)
*/
export const updateMarketData = (newDigit: number) => {
const currentTicks = store.getSnapshot().ticks;
// Keep only the last 500 ticks for analysis
const updatedTicks = [...currentTicks, newDigit].slice(-500);
store.update({
ticks: updatedTicks,
connectionStatus: 'connected'
});
};2. Using the "Pulse" for Side Effects
Instead of a useSync hook (which is for UI), on the backend you use the _subscribe method. This acts like a "Global Watcher" that fires whenever the state is marked as "dirty".
// backend/tradingBot.ts
import { store } from './marketStore';
// Subscribe to state changes to execute bot logic
store._subscribe(() => {
const state = store.getSnapshot();
// Example: Run your "Match Hunter" logic here
// Because of PulseSync, this only runs once every 16ms,
// even if 100 digits arrived in that timeframe.
if (state.ticks.length > 10) {
console.log(`[PULSE] Analyzing ${state.ticks.length} ticks. Current status: ${state.connectionStatus}`);
// runMatchHunter(state.ticks);
}
});3. Real-World Usage: High-Frequency Data Feed
This is how you would bridge an incoming data stream (like a WebSocket from a broker) to your PulseSync state.
// backend/server.ts
import { updateMarketData, store } from './marketStore';
import { createServer } from 'http';
import { Server } from 'socket.io';
const io = new Server(createServer());
// A. Handle incoming raw data (Mutation)
const rawDataStream = someBrokerAPI.connect();
rawDataStream.on('tick', (digit: number) => {
// Update the state as fast as the data arrives
updateMarketData(digit);
});
// B. Broadcast processed state to clients (The Pulse)
// We only broadcast to connected clients at the Pulse rate (e.g. 16ms)
// to save bandwidth and prevent "CPU Shock" on the user's browser.
store._subscribe(() => {
const latestState = store.getSnapshot();
io.emit('market_update', latestState);
});Important Note:
On the backend, remember that store is a singleton in memory. If your server restarts, the state will reset to initialState. For long-term persistence, you would typically save the snapshot to a database inside the _subscribe block.
Key Comparison: Frontend vs. Backend Usage
| Action | Frontend (React) | Backend (Node.js) |
| :------------- | :------------------------------ | :----------------------------- |
| Storage | createPulseStore(initial) | createPulseStore(initial) |
| Updating | store.update({ key: val }) | store.update({ key: val }) |
| Accessing | useSync(selector) | store.getSnapshot() |
| Reactivity | Triggers Component Render | Triggers _subscribe callback |
| Pulse Sync | Synced to Screen Refresh (60Hz) | Synced to setTimeout (16ms) |
🔥 Key Performance Features
- Zero-Allocation Mutations: Internally uses
Object.assignto update the state object. This prevents the creation of millions of temporary objects, keeping the Garbage Collector silent and avoiding mobile "hiccups". - isDirty CPU Shock Absorber: The internal heartbeat loop only notifies listeners if an update was actually triggered via
store.update(). This saves massive CPU cycles during market "quiet" periods. - Global Heartbeat: Uses a single
requestAnimationFrameloop for all active hooks. No matter how many components you have, there is only ever one sync loop running. - Backpressure Handling: If the device heats up or the CPU is throttled, the UI naturally drops from 60fps to 30fps, but the WebSocket logic continues at full speed in the background.
💡 Best Practices
- Leaf Components: Use
useSyncin the smallest possible component. Instead of one large Dashboard hook, use small hooks inPriceLabel,DigitBox, andStatusIcon. - Simple Selectors: Keep your selectors simple (e.g.,
s => s.value). If you need complex derived math, perform it inside thestore.update()phase, not in the UI hook. - Cleanup: PulseSync automatically stops its internal heartbeat when the last component unmounts, ensuring zero memory leaks.
