Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: Manager.getMiddleware() -> Manager.middleware #3164

Merged
merged 1 commit into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/five-apes-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@data-client/react': patch
'@data-client/core': patch
---

Manager.getMiddleware() -> Manager.middleware

`getMiddleware()` is still supported to make this change non-breaking
7 changes: 7 additions & 0 deletions .changeset/new-garlics-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@data-client/react': patch
---

Move manager lifecycle logic from DataStore to DataProvider

This has no behavioral change, but creates a better seperation of concerns.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ const totalVotesForUser = useQuery(queryTotalVotes, { userId });

```ts
class LoggingManager implements Manager {
getMiddleware = (): Middleware => controller => next => async action => {
middleware: Middleware => controller => next => async action => {
console.log('before', action, controller.getState());
await next(action);
console.log('after', action, controller.getState());
Expand Down
60 changes: 30 additions & 30 deletions docs/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,29 +455,29 @@ export default class StreamManager implements Manager {
) {
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);
middleware: 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;
}
}
```

Expand Down Expand Up @@ -581,10 +581,10 @@ const currentTimeInterceptor: Interceptor = {
path: '/api/currentTime/:id',
}),
response({ id }) {
return ({
return {
id,
updatedAt: new Date().toISOString(),
});
};
},
delay: () => 150,
};
Expand Down Expand Up @@ -621,15 +621,15 @@ const incrementInterceptor: Interceptor = {
## Demo

<Tabs
defaultValue="todo"
values={[
{ label: 'Todo', value: 'todo' },
{ label: 'GitHub', value: 'github' },
{ label: 'NextJS SSR', value: 'nextjs' },
]}
groupId="Demos"
>
<TabItem value="todo">
defaultValue="todo"
values={[
{ label: 'Todo', value: 'todo' },
{ label: 'GitHub', value: 'github' },
{ label: 'NextJS SSR', value: 'nextjs' },
]}
groupId="Demos"

> <TabItem value="todo">

<iframe
loading="lazy"
Expand All @@ -651,7 +651,7 @@ const incrementInterceptor: Interceptor = {

[![Explore on GitHub](https://badgen.net/badge/icon/github?icon=github&label)](https://github.com/reactive/data-client/tree/master/examples/github-app)
</TabItem>
<TabItem value="nextjs">
<TabItem value="nextjs">

<iframe
loading="lazy"
Expand All @@ -666,4 +666,4 @@ const incrementInterceptor: Interceptor = {

<p style={{textAlign: 'center'}}>
<Link className="button button--secondary" to="/demos">More Demos</Link>
</p>
</p>
47 changes: 22 additions & 25 deletions docs/core/api/Controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ and retrieval performance.

`Controller` is provided:

- [Managers](./Manager.md) as the first argument in [Manager.getMiddleware()](./Manager.md#getmiddleware)
- React with [useController()](./useController.md)
- [Unit testing hooks](../guides/unit-testing-hooks.md) with [renderDataClient()](./makeRenderDataClient.md#controller)
- [Managers](./Manager.md) as the first argument in [Manager.middleware](./Manager.md#middleware)
- React with [useController()](./useController.md)
- [Unit testing hooks](../guides/unit-testing-hooks.md) with [renderDataClient()](./makeRenderDataClient.md#controller)

```ts
class Controller {
Expand Down Expand Up @@ -366,7 +366,11 @@ function UserName() {
Updates any [Queryable](/rest/api/schema#queryable) [Schema](/rest/api/schema#schema-overview).

```ts
ctrl.set(Todo, { id: '5' }, { id: '5', title: 'tell me friends how great Data Client is' });
ctrl.set(
Todo,
{ id: '5' },
{ id: '5', title: 'tell me friends how great Data Client is' },
);
```

Functions can be used in the value when derived data is used. This [prevents race conditions](https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state).
Expand Down Expand Up @@ -546,32 +550,25 @@ import type { Manager, Middleware, actionTypes } from '@data-client/core';
import type { EndpointInterface } from '@data-client/endpoint';

export default class MyManager implements Manager {
protected declare middleware: Middleware;
constructor() {
this.middleware = controller => {
return next => async action => {
if (action.type === actionTypes.FETCH_TYPE) {
console.log('The existing response of the requested fetch');
console.log(
controller.getResponse(
action.endpoint,
...(action.meta.args as Parameters<typeof action.endpoint>),
controller.getState(),
).data,
);
}
next(action);
};
middleware: Middleware = controller => {
return next => async action => {
if (action.type === actionTypes.FETCH_TYPE) {
console.log('The existing response of the requested fetch');
console.log(
controller.getResponse(
action.endpoint,
...(action.meta.args as Parameters<typeof action.endpoint>),
controller.getState(),
).data,
);
}
next(action);
};
}
};

cleanup() {
this.websocket.close();
}

getMiddleware<T extends StreamManager>(this: T) {
return this.middleware;
}
}
```

Expand Down
14 changes: 7 additions & 7 deletions docs/core/api/Manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The default managers orchestrate the complex asynchronous behavior that <abbr ti
provides out of the box. These can easily be configured with [getDefaultManagers()](./getDefaultManagers.md), and
extended with your own custom `Managers`.

Managers must implement [getMiddleware()](#getmiddleware), which hooks them into the central store's
Managers must implement [middleware](#middleware), which hooks them into the central store's
[control flow](#control-flow). Additionally, [cleanup()](#cleanup) and [init()](#init) hook into the
store's lifecycle for setup/teardown behaviors.

Expand All @@ -32,17 +32,17 @@ type Dispatch = (action: ActionTypes) => Promise<void>;
type Middleware = (controller: Controller) => (next: Dispatch) => Dispatch;

interface Manager {
getMiddleware(): Middleware;
middleware: Middleware;
cleanup(): void;
init?: (state: State<any>) => void;
}
```

## Lifecycle

### getMiddleware()
### middleware

getMiddleware() returns a function that very similar to a [redux middleware](https://redux.js.org/advanced/middleware).
`middleware` is very similar to a [redux middleware](https://redux.js.org/advanced/middleware).
The only differences is that the `next()` function returns a `Promise`. This promise resolves when the reducer update is
[committed](https://indepth.dev/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react/#general-algorithm)
when using &lt;DataProvider /\>. This is necessary since the commit phase is asynchronously scheduled. This enables building
Expand Down Expand Up @@ -246,7 +246,7 @@ import CurrentTime from './CurrentTime';
export default class TimeManager implements Manager {
protected declare intervalID?: ReturnType<typeof setInterval>;

getMiddleware = (): Middleware => controller => {
middleware: Middleware => controller => {
this.intervalID = setInterval(() => {
controller.set(CurrentTime, { id: 1 }, { id: 1, time: Date.now() });
}, 1000);
Expand All @@ -273,7 +273,7 @@ import type { Manager, Middleware } from '@data-client/react';
import { actionTypes } from '@data-client/react';

export default class LoggingManager implements Manager {
getMiddleware = (): Middleware => controller => next => async action => {
middleware: Middleware => controller => next => async action => {
switch (action.type) {
case actionTypes.SET_RESPONSE_TYPE:
if (action.endpoint.sideEffect) {
Expand Down Expand Up @@ -326,7 +326,7 @@ import isEntity from './isEntity';
export default class CustomSubsManager implements Manager {
protected declare entities: Record<string, EntityInterface>;

getMiddleware = (): Middleware => controller => next => async action => {
middleware: Middleware => controller => next => async action => {
switch (action.type) {
case actionTypes.SUBSCRIBE_TYPE:
case actionTypes.UNSUBSCRIBE_TYPE:
Expand Down
2 changes: 1 addition & 1 deletion docs/core/api/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: TypeScript Types

```typescript
interface Manager<Actions = ActionTypes> {
getMiddleware(): Middleware<Actions>;
middleware: Middleware<Actions>;
cleanup(): void;
init?: (state: State<any>) => void;
}
Expand Down
50 changes: 20 additions & 30 deletions docs/core/concepts/managers.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ Reactive Data Client uses the [flux store](https://facebookarchive.github.io/flu
characterized by an easy to [understand and debug](../getting-started/debugging.md) the store's [undirectional data flow](<https://en.wikipedia.org/wiki/Unidirectional_Data_Flow_(computer_science)>). State updates are performed by a [reducer function](https://github.com/reactive/data-client/blob/master/packages/core/src/state/reducer/createReducer.ts#L19).

<ThemedImage
alt="Manager flux flow"
sources={{
alt="Manager flux flow"
sources={{
light: useBaseUrl('/img/flux-full.png'),
dark: useBaseUrl('/img/flux-full-dark.png'),
}}
Expand All @@ -37,12 +37,11 @@ will keep only actively rendered resources updated.
This makes [Managers](../api/Manager.md) the best way to integrate additional side-effects like metrics and monitoring.
They can also be customized to change core behaviors.


| Default managers | |
| Default managers | |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------ |
| [NetworkManager](../api/NetworkManager.md) | Turns fetch dispatches into network calls |
| [SubscriptionManager](../api/SubscriptionManager.md) | Handles polling [subscriptions](../getting-started/data-dependency.md#subscriptions) |
| [DevToolsManager](../api/DevToolsManager.md) | Enables [debugging](../getting-started/debugging.md) |
| [DevToolsManager](../api/DevToolsManager.md) | Enables [debugging](../getting-started/debugging.md) |
| Extra managers |
| [LogoutManager](../api/LogoutManager.md) | Handles HTTP `401` (or other logout conditions) |

Expand All @@ -57,7 +56,7 @@ its [Controller](../api/Controller.md)
import type { Manager, Middleware } from '@data-client/core';

export default class LoggingManager implements Manager {
getMiddleware = (): Middleware => controller => next => async action => {
middleware: Middleware => controller => next => async action => {
console.log('before', action, controller.getState());
await next(action);
console.log('after', action, controller.getState());
Expand All @@ -80,7 +79,6 @@ import { Controller, actionTypes } from '@data-client/react';
import type { Entity } from '@data-client/rest';

export default class StreamManager implements Manager {
protected declare middleware: Middleware;
protected declare evtSource: WebSocket | EventSource;
protected declare entities: Record<string, typeof Entity>;

Expand All @@ -90,35 +88,27 @@ export default class StreamManager implements Manager {
) {
this.evtSource = evtSource;
this.entities = entities;
}

// highlight-start
this.middleware = controller => {
this.evtSource.onmessage = event => {
try {
const msg = JSON.parse(event.data);
if (msg.type in this.endpoints)
controller.set(
this.entities[msg.type],
...msg.args,
msg.data,
);
} catch (e) {
console.error('Failed to handle message');
console.error(e);
}
};
return next => async action => next(action);
// highlight-start
middleware: Middleware = controller => {
this.evtSource.onmessage = event => {
try {
const msg = JSON.parse(event.data);
if (msg.type in this.endpoints)
controller.set(this.entities[msg.type], ...msg.args, msg.data);
} catch (e) {
console.error('Failed to handle message');
console.error(e);
}
};
// highlight-end
}
return next => async action => next(action);
};
// highlight-end

cleanup() {
this.evtSource.close();
}

getMiddleware() {
return this.middleware;
}
}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/core/guides/redux.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ You should only use ONE provider; nested another provider will override the prev

:::info Note

Because `Reactive Data Client` [manager middlewares](../api/Manager.md#getmiddleware) return promises,
Because `Reactive Data Client` [manager middlewares](../api/Manager.md#middleware) return promises,
all redux middlewares are placed after the [Managers](../concepts/managers.md).

If you need a middlware to run before the managers, you will need to wrap it in a [manager](../api/Manager.md).
Expand Down
Loading
Loading