Skip to content

Commit a3b801a

Browse files
committed
internal: Add distinct Manager copilot instruction file
1 parent 3f89f18 commit a3b801a

File tree

4 files changed

+144
-49
lines changed

4 files changed

+144
-49
lines changed

.github/copilot-instructions.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
- Use [@data-client/rest guide](.github/instructions/rest.instructions.md) for REST API definitions.
22
- Use [@data-client/react guide](.github/instructions/react.instructions.md) for React.
33
- Use [@data-client/test guide](.github/instructions/test.instructions.md) when writing tests.
4-
- If you are not sure about file content or codebase structure pertaining to the user’s request, use your tools to read files and gather the relevant information: do NOT guess or make up an answer.
4+
- Use [@data-client/manager guide](.github/instructions/manager.instructions.md) when writing custom Managers for @data-client/react.
5+
- If you are not sure about file content or codebase structure pertaining to the user’s request, use your tools to read files and gather the relevant information: do NOT guess or make up an answer.
6+
- Place API definitions and custom managers in the 'resources' directory.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
---
2+
applyTo: '**/*.ts'
3+
---
4+
# Guide: Using `@data-client/react` Managers for global side effects
5+
6+
[Managers](https://dataclient.io/docs/api/Manager) are singletons that handle global side-effects. Kind of like useEffect() for the central data store.
7+
They interface with the store using [Controller](https://dataclient.io/docs/api/Controller), and [redux middleware](https://redux.js.org/tutorials/fundamentals/part-4-store#middleware) is run in response to [actions](https://dataclient.io/docs/api/Actions).
8+
9+
## Dispatching actions
10+
11+
[Controller](https://dataclient.io/docs/api/Controller) has dispatchers:
12+
ctrl.fetch(), ctrl.fetchIfStale(), ctrl.expireAll(), ctrl.invalidate(), ctrl.invalidateAll(), ctrl.setResponse(), ctrl.set().
13+
14+
```ts
15+
import type { Manager, Middleware } from '@data-client/core';
16+
import CurrentTime from './CurrentTime';
17+
18+
export default class TimeManager implements Manager {
19+
protected declare intervalID?: ReturnType<typeof setInterval>;
20+
21+
middleware: Middleware = controller => {
22+
this.intervalID = setInterval(() => {
23+
controller.set(CurrentTime, { id: 1 }, { id: 1, time: Date.now() });
24+
}, 1000);
25+
26+
return next => async action => next(action);
27+
};
28+
29+
cleanup() {
30+
clearInterval(this.intervalID);
31+
}
32+
}
33+
```
34+
35+
## Reading and Consuming Actions
36+
37+
[Controller](https://dataclient.io/docs/api/Controller) has data accessors:
38+
controller.getResponse(), controller.getState(), controller.get(), controller.getError()
39+
40+
```ts
41+
import type { Manager, Middleware } from '@data-client/react';
42+
import { actionTypes } from '@data-client/react';
43+
44+
export default class LoggingManager implements Manager {
45+
middleware: Middleware = controller => next => async action => {
46+
switch (action.type) {
47+
case actionTypes.SET_RESPONSE:
48+
if (action.endpoint.sideEffect) {
49+
console.info(
50+
`${action.endpoint.name} ${JSON.stringify(action.response)}`,
51+
);
52+
// wait for state update to be committed to React
53+
await next(action);
54+
// get the data from the store, which may be merged with existing state
55+
const { data } = controller.getResponse(
56+
action.endpoint,
57+
...action.args,
58+
controller.getState(),
59+
);
60+
console.info(`${action.endpoint.name} ${JSON.stringify(data)}`);
61+
return;
62+
}
63+
// actions must be explicitly passed to next middleware
64+
default:
65+
return next(action);
66+
}
67+
};
68+
69+
cleanup() {}
70+
}
71+
```
72+
73+
`actionTypes`: FETCH, SET, SET_RESPONSE, RESET, SUBSCRIBE, UNSUBSCRIBE, INVALIDATE, INVALIDATEALL, EXPIREALL
74+
75+
[actions](https://dataclient.io/docs/api/Actions) docs details the action types and their payloads.
76+
77+
## Consuming actions
78+
79+
```ts
80+
import type { Manager, Middleware, EntityInterface } from '@data-client/react';
81+
import { actionTypes } from '@data-client/react';
82+
import isEntity from './isEntity';
83+
84+
export default class CustomSubsManager implements Manager {
85+
protected declare entities: Record<string, EntityInterface>;
86+
87+
middleware: Middleware = controller => next => async action => {
88+
switch (action.type) {
89+
case actionTypes.SUBSCRIBE:
90+
case actionTypes.UNSUBSCRIBE:
91+
const { schema } = action.endpoint;
92+
// only process registered entities
93+
if (schema && isEntity(schema) && schema.key in this.entities) {
94+
if (action.type === actionTypes.SUBSCRIBE) {
95+
this.subscribe(schema.key, action.args[0]?.product_id);
96+
} else {
97+
this.unsubscribe(schema.key, action.args[0]?.product_id);
98+
}
99+
100+
// consume subscription to prevent it from being processed by other managers
101+
return Promise.resolve();
102+
}
103+
default:
104+
return next(action);
105+
}
106+
};
107+
108+
cleanup() {}
109+
110+
subscribe(channel: string, product_id: string) {}
111+
unsubscribe(channel: string, product_id: string) {}
112+
}
113+
```
114+
115+
## Usage
116+
117+
```tsx
118+
import { DataProvider, getDefaultManagers } from '@data-client/react';
119+
import ReactDOM from 'react-dom';
120+
121+
const managers = [...getDefaultManagers(), new MyManager()];
122+
123+
ReactDOM.createRoot(document.body).render(
124+
<DataProvider managers={managers}>
125+
<App />
126+
</DataProvider>,
127+
);
128+
```

.github/instructions/react.instructions.md

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -104,44 +104,10 @@ const todosByUser = useQuery(groupTodoByUser);
104104

105105
## Managers
106106

107-
Customer [managers](https://dataclient.io/docs/api/Manager) allow for global side effect handling.
108-
They interface with the store using `Controller`, and middleware is run in response to actions.
107+
Custom [Managers](https://dataclient.io/docs/api/Manager) allow for global side effect handling.
108+
This is useful for webosckets, SSE, logging, etc.
109109

110-
```ts
111-
import type { Manager, Middleware, EntityInterface } from '@data-client/react';
112-
import { actionTypes } from '@data-client/react';
113-
import isEntity from './isEntity';
114-
115-
export default class CustomSubsManager implements Manager {
116-
protected declare entities: Record<string, EntityInterface>;
117-
118-
middleware: Middleware = controller => next => async action => {
119-
switch (action.type) {
120-
case actionTypes.SUBSCRIBE:
121-
case actionTypes.UNSUBSCRIBE:
122-
const { schema } = action.endpoint;
123-
// only process registered entities
124-
if (schema && isEntity(schema) && schema.key in this.entities) {
125-
if (action.type === actionTypes.SUBSCRIBE) {
126-
this.subscribe(schema.key, action.args[0]?.product_id);
127-
} else {
128-
this.unsubscribe(schema.key, action.args[0]?.product_id);
129-
}
130-
131-
// consume subscription if we use it
132-
return Promise.resolve();
133-
}
134-
default:
135-
return next(action);
136-
}
137-
};
138-
139-
cleanup() {}
140-
141-
subscribe(channel: string, product_id: string) {}
142-
unsubscribe(channel: string, product_id: string) {}
143-
}
144-
```
110+
For more detailed usage, refer to the [Manager Guide](.github/instructions/manager.instructions.md).
145111

146112
## Best Practices & Notes
147113

.github/instructions/rest.instructions.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,24 @@ to represent the data expected.
1616
### Object
1717

1818
- [Entity](https://dataclient.io/rest/api/Entity) - represents a single unique object (denormalized)
19-
- [schema.Union(Entity)](https://dataclient.io/rest/api/Union) - polymorphic objects (A | B)
19+
- [new schema.Union(Entity)](https://dataclient.io/rest/api/Union) - polymorphic objects (A | B)
2020
- `{[key:string]: Schema}` - immutable objects
21-
- `schema.Invalidate(Entity)` - to delete an Entity
21+
- `new schema.Invalidate(Entity)` - to delete an Entity
2222

2323
### List
2424

25-
- [schema.Collection([Entity])](https://dataclient.io/rest/api/Collection) - mutable/growable lists
25+
- [new schema.Collection([Entity])](https://dataclient.io/rest/api/Collection) - mutable/growable lists
2626
- `[Entity]` - immutable lists
27-
- `schema.All(Entity)` - list all Entities of a kind
27+
- `new schema.All(Entity)` - list all Entities of a kind
2828

2929
### Map
3030

31-
- `schema.Collection(schema.Values(Entity))` - mutable/growable maps
32-
- `schema.Values(Entity)` - immutable maps
31+
- `new schema.Collection(schema.Values(Entity))` - mutable/growable maps
32+
- `new schema.Values(Entity)` - immutable maps
3333

3434
### Programmatic
3535

36-
- [schema.Query(Queryable)](https://dataclient.io/rest/api/Query) - memoized programmatic selectors
36+
- [new schema.Query(Queryable)](https://dataclient.io/rest/api/Query) - memoized programmatic selectors
3737

3838
---
3939

@@ -42,16 +42,15 @@ to represent the data expected.
4242
- Every `Entity` subclass **defines defaults** for _all_ non-optional serialised fields.
4343
- Override `pk()` only when the primary key ≠ `id`.
4444
- `pk()` return type is `number | string | undefined`
45-
- `static schema` (optional) for nested schemas or deserilization functions
45+
- Override `Entity.process(value, parent, key, args)` to insert fields based on args/url
46+
- `static schema` (optional) for nested schemas or deserialization functions
4647
- When designing APIs, prefer nesting entities
47-
- always define `static key` as global registry for the Entity
48-
- Use [Entity.fromJS()](https://dataclient.io/rest/api/Entity#fromJS) for defaults.
4948

5049
---
5150

5251
## 3. Resources (`resource()`)
5352

54-
- [resource()](https://dataclient.io/rest/api/resource) creates a collection of `RestEndpoints` for CRUD operations on a common object
53+
- [resource()](https://dataclient.io/rest/api/resource) creates a collection of [RestEndpoints](https://dataclient.io/rest/api/RestEndpoint) for CRUD operations on a common object
5554
- Required fields:
5655
- `path`: path‑to‑regexp template (typed!)
5756
- `schema`: Declarative data shape for a **single** item (typically Entity or Union)

0 commit comments

Comments
 (0)