forked from hpi-sam/digital-fuesim-manv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
action-reducer.ts
72 lines (69 loc) · 3.1 KB
/
action-reducer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import type { Role } from '../models/utils';
import type { ExerciseState } from '../state';
import type { Constructor, Mutable } from '../utils';
export interface ActionReducer<A extends Action = Action> {
readonly action: Constructor<A>;
readonly reducer: ReducerFunction<InstanceType<this['action']>>;
readonly rights: Role | 'server';
}
/**
* An action is an interface for immutable JSON objects used to update the store in the frontend and
* are send to the backend to apply the changes there too.
*
* The classes themself should only be used to validate the JSON objects in the backend, not to create them.
* Instead you should use the classes solely as interfaces and instantiate them like this:
* ```ts
* const action: RemoveViewport = {
* type: '[Viewport] Remove viewport',
* viewportId: 'some-uuid',
* };
* ```
*
* The constructor of an Action must be callable without any arguments, to allow getting their type-value to
* validate the action objects in the backend.
* The properties of an Action must be decorated with class-validator decorators to allow validating them in the backend.
*/
export interface Action {
readonly type: `[${string}] ${string}`;
}
/**
* A pure function that applies the action on the provided state.
* A [pure function](https://en.wikipedia.org/wiki/Pure_function) is a function that produces the same return value for identical arguments (doesn't use non-deterministic functions like `Date.now()`, `uuid()` or `Math.random()`) and is side-effect free.
*
* @throws {ReducerError} if the action is not applicable on the provided state
*
* It is expected to be used inside [immers produce](https://immerjs.github.io/immer/produce).
*
* Do not use [original()](https://immerjs.github.io/immer/original)!
* `original(draftState)` could be any previous state when multiple reducers are applied (time travel).
*
* You can also call other reducers from within a reducer function (don't create loops).
*
* Example:
* ```ts
* const anAction: ExerciseAction = { type: 'some action' };
* exerciseReducerMap[anAction.type](draftState, anAction);
* ```
*
*
* Be aware that the action is immutable!
* [While TypeScript allows assigning immutable objects to properties of mutable objects](https://github.com/microsoft/TypeScript/issues/13347),
* we have an eslint rule that shields against this.
* You must always clone parts of the action before assigning them to the draftState.
* The same could also apply for objects created inside the reducer function - like with a `Model.create()`, which by default returns an immutable object.
*
* Example:
* ```ts
* const reducerFunction = (draftState, anAction) => {
* draftState.someObject = cloneDeepMutable(anAction.someObject);
* draftState.someOtherObject = cloneDeepMutable(SomeOtherObject.create());
* return draftState;
* }
* ```
*
*/
type ReducerFunction<A extends Action> = (
// These functions can only work with a mutable state object, because we expect them to be executed in immers produce context.
draftState: Mutable<ExerciseState>,
action: A
) => Mutable<ExerciseState>;