Managers and Middleware
Reactive Data Client uses the flux store pattern, which is characterized by an easy to understand and debug the store's undirectional data flow. State updates are performed by a reducer function.
In flux architectures, it is critical all functions in the flux loop are pure. Managers provide centralized orchestration of side effects. In other words, they are the means to interface with the world outside RDC.
For instance, NetworkManager orchestrates data fetching and SubscriptionManager keeps track of which resources are subscribed with useLive or useSubscription. By centralizing control, NetworkManager automatically deduplicates fetches, and SubscriptionManager will keep only actively rendered resources updated.
This makes Managers the best way to integrate additional side-effects like metrics and monitoring. They can also be customized to change core behaviors.
Default managers | |
---|---|
NetworkManager | Turns fetch dispatches into network calls |
SubscriptionManager | Handles polling subscriptions |
DevToolsManager | Enables debugging |
Extra managers | |
LogoutManager | Handles HTTP 401 (or other logout conditions) |
Examples
Reactive Data Client improves type-safety and ergonomics by performing dispatches and store access with its Controller
Middleware logging
import type { Manager, Middleware } from '@data-client/core';
export default class LoggingManager implements Manager {
getMiddleware = (): Middleware => controller => next => async action => {
console.log('before', action, controller.getState());
await next(action);
console.log('after', action, controller.getState());
};
cleanup() {}
}
Middleware data stream (push-based)
Adding a manager to process data pushed from the server by websockets or Server Sent Events ensures we can maintain fresh data when the data updates are independent of user action. For example, a trading app's price, or a real-time collaborative editor.
import type { Manager, Middleware } from '@data-client/core';
import type { EndpointInterface } from '@data-client/endpoint';
export default class StreamManager implements Manager {
protected declare middleware: Middleware;
protected declare evtSource: WebSocket | EventSource;
protected declare endpoints: Record<string, EndpointInterface>;
constructor(
evtSource: WebSocket | EventSource,
endpoints: Record<string, EndpointInterface>,
) {
this.evtSource = evtSource;
this.endpoints = endpoints;
this.middleware = controller => {
this.evtSource.onmessage = event => {
try {
const msg = JSON.parse(event.data);
if (msg.type in this.endpoints)
controller.setResponse(
this.endpoints[msg.type],
...msg.args,
msg.data,
);
} catch (e) {
console.error('Failed to handle message');
console.error(e);
}
};
return next => async action => next(action);
};
}
cleanup() {
this.evtSource.close();
}
getMiddleware() {
return this.middleware;
}
}
Controller.setResponse() updates the Reactive Data Client store
with event.data
.