Skip to main content

Managers and Middleware

Reactive Data Client uses the flux store pattern, which is characterized by an easy to understand and debug undirectional data flow. State updates are performed by a reducer function.

Manager flux flowManager flux flow

Reactive Data Client improves type-safety and ergonomics by performing dispatches and store access with its Controller

Finally, everything is orchestrated by Managers. Managers integrate with the flux lifecycle by intercepting and dispatching actions, as well as reading the internal state.

This central orchestration is how Reactive Data Client is able to coordinate with all components, doing things like automatic fetch deduplication, polling fetch coordinating eliminating many cases of overfetching.

It also means Reactive Data Client behavior can be arbitrarily customized by writing your own Managers.

Default managers
NetworkManagerTurns fetch dispatches into network calls
SubscriptionManagerHandles polling subscriptions
DevToolsManagerEnables debugging
Extra managers
LogoutManagerHandles HTTP 401 (or other logout conditions)

Examples

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.