chizu 0.2.26 → 0.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -97
- package/dist/action/index.d.ts +19 -0
- package/dist/chizu.js +142 -425
- package/dist/chizu.umd.cjs +3 -1
- package/dist/decorators/index.d.ts +6 -0
- package/dist/error/index.d.ts +4 -0
- package/dist/error/types.d.ts +6 -0
- package/dist/hooks/index.d.ts +66 -10
- package/dist/hooks/types.d.ts +2 -0
- package/dist/hooks/utils.d.ts +18 -0
- package/dist/index.d.ts +6 -9
- package/dist/types/index.d.ts +47 -52
- package/dist/use/index.d.ts +5 -0
- package/dist/use/types.d.ts +3 -0
- package/dist/use/utils.d.ts +2 -0
- package/dist/utils/index.d.ts +28 -3
- package/package.json +51 -32
- package/dist/context/index.d.ts +0 -7
- package/dist/context/types.d.ts +0 -2
- package/dist/controller/index.d.ts +0 -3
- package/dist/controller/types.d.ts +0 -30
- package/dist/errors/index.d.ts +0 -8
- package/dist/errors/types.d.ts +0 -13
- package/dist/errors/utils.d.ts +0 -30
- package/dist/module/index.d.ts +0 -4
- package/dist/module/renderer/actions/index.d.ts +0 -3
- package/dist/module/renderer/actions/types.d.ts +0 -19
- package/dist/module/renderer/controller/index.d.ts +0 -4
- package/dist/module/renderer/controller/types.d.ts +0 -11
- package/dist/module/renderer/dispatchers/index.d.ts +0 -12
- package/dist/module/renderer/dispatchers/types.d.ts +0 -18
- package/dist/module/renderer/dispatchers/utils.d.ts +0 -10
- package/dist/module/renderer/elements/index.d.ts +0 -4
- package/dist/module/renderer/elements/types.d.ts +0 -2
- package/dist/module/renderer/elements/utils.d.ts +0 -4
- package/dist/module/renderer/index.d.ts +0 -4
- package/dist/module/renderer/lifecycles/index.d.ts +0 -3
- package/dist/module/renderer/lifecycles/types.d.ts +0 -13
- package/dist/module/renderer/model/index.d.ts +0 -5
- package/dist/module/renderer/model/types.d.ts +0 -15
- package/dist/module/renderer/model/utils.d.ts +0 -8
- package/dist/module/renderer/passive/index.d.ts +0 -1
- package/dist/module/renderer/types.d.ts +0 -10
- package/dist/module/renderer/update/index.d.ts +0 -5
- package/dist/module/renderer/update/types.d.ts +0 -2
- package/dist/module/renderer/utils.d.ts +0 -8
- package/dist/module/types.d.ts +0 -12
- package/dist/utils/produce/index.d.ts +0 -23
- package/dist/utils/produce/utils.d.ts +0 -15
- package/dist/view/index.d.ts +0 -3
- package/dist/view/types.d.ts +0 -16
- /package/dist/{utils/produce/index.test.d.ts → index.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<img src="/media/logo.png" width="475" />
|
|
3
|
+
|
|
4
|
+
[](https://github.com/Wildhoney/Chizu/actions/workflows/checks.yml)
|
|
5
|
+
|
|
3
6
|
</div>
|
|
4
7
|
|
|
5
8
|
Strongly typed React framework using generators and efficiently updated views alongside the publish-subscribe pattern.
|
|
6
9
|
|
|
10
|
+
**[View Live Demo →](https://wildhoney.github.io/Chizu/)**
|
|
11
|
+
|
|
7
12
|
## Contents
|
|
8
13
|
|
|
9
14
|
1. [Benefits](#benefits)
|
|
10
15
|
1. [Getting started](#getting-started)
|
|
11
|
-
1. [
|
|
12
|
-
1. [Distributed actions](#distributed-actions)
|
|
16
|
+
1. [Error handling](#error-handling)
|
|
17
|
+
<!-- 1. [Distributed actions](#distributed-actions)
|
|
13
18
|
1. [Module dispatch](#module-dispatch)
|
|
14
|
-
1. [Associated context](#associated-context)
|
|
19
|
+
1. [Associated context](#associated-context) -->
|
|
15
20
|
|
|
16
21
|
## Benefits
|
|
17
22
|
|
|
@@ -21,86 +26,133 @@ Strongly typed React framework using generators and efficiently updated views al
|
|
|
21
26
|
- Mostly standard JavaScript without quirky rules and exceptions.
|
|
22
27
|
- Clear separation of concerns between business logic and markup.
|
|
23
28
|
- First-class support for skeleton loading using generators.
|
|
24
|
-
- Strongly typed throughout –
|
|
25
|
-
- Avoid vendor lock-in with framework agnostic libraries such as [Shoelace](https://shoelace.style/).
|
|
29
|
+
- Strongly typed throughout – dispatches, models, etc…
|
|
26
30
|
- Easily communicate between actions using distributed actions.
|
|
27
|
-
-
|
|
31
|
+
- Bundled decorators for common action functionality such as consecutive mode.
|
|
28
32
|
|
|
29
33
|
## Getting started
|
|
30
34
|
|
|
31
|
-
Actions are responsible for mutating the state of the view. In the below example the `name` is dispatched from the view to the actions, the state is updated and the view is rendered with the updated value.
|
|
35
|
+
Actions are responsible for mutating the state of the view. In the below example the `name` is dispatched from the view to the actions, the state is updated and the view is rendered with the updated value. We use the `Actions` type to ensure type safety for our actions class.
|
|
32
36
|
|
|
33
37
|
```tsx
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
[Action.Name](name) {
|
|
37
|
-
return module.actions.produce((draft) => {
|
|
38
|
-
draft.name = name;
|
|
39
|
-
});
|
|
40
|
-
},
|
|
41
|
-
};
|
|
38
|
+
const model: Model = {
|
|
39
|
+
name: null,
|
|
42
40
|
};
|
|
41
|
+
|
|
42
|
+
export class Actions {
|
|
43
|
+
static Name = createAction<string>();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default function useNameActions() {
|
|
47
|
+
return useActions<Model, typeof Actions>(
|
|
48
|
+
model,
|
|
49
|
+
class {
|
|
50
|
+
[Actions.Name] = utils.set("name");
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
}
|
|
43
54
|
```
|
|
44
55
|
|
|
45
56
|
```tsx
|
|
46
57
|
export default function Profile(props: Props): React.ReactElement {
|
|
58
|
+
const [model, actions] = useNameActions();
|
|
59
|
+
|
|
47
60
|
return (
|
|
48
|
-
|
|
49
|
-
{
|
|
50
|
-
<>
|
|
51
|
-
<p>Hey {module.model.name}</p>
|
|
61
|
+
<>
|
|
62
|
+
<p>Hey {model.name}</p>
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
</button>
|
|
58
|
-
</>
|
|
59
|
-
)}
|
|
60
|
-
</Scope>
|
|
64
|
+
<button onClick={() => actions.dispatch(Actions.Name, randomName())}>
|
|
65
|
+
Switch profile
|
|
66
|
+
</button>
|
|
67
|
+
</>
|
|
61
68
|
);
|
|
62
69
|
}
|
|
63
70
|
```
|
|
64
71
|
|
|
65
|
-
You can perform asynchronous operations in the action which will cause the associated view to render a second time
|
|
72
|
+
You can perform asynchronous operations in the action which will cause the associated view to render a second time – as we're starting to require more control in our actions we'll move to our own fine-tuned action instead of `utils.set`:
|
|
66
73
|
|
|
67
74
|
```tsx
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
yield module.actions.produce((draft) => {
|
|
72
|
-
draft.name = null;
|
|
73
|
-
});
|
|
75
|
+
const model: Model = {
|
|
76
|
+
name: null,
|
|
77
|
+
};
|
|
74
78
|
|
|
75
|
-
|
|
79
|
+
export class Actions {
|
|
80
|
+
static Name = createAction();
|
|
81
|
+
}
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
export default function useNameActions() {
|
|
84
|
+
const nameAction = useAction<Model, typeof Actions.Name>(async (context) => {
|
|
85
|
+
context.actions.produce((draft) => {
|
|
86
|
+
draft.name = null;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const name = await fetch(/* ... */);
|
|
90
|
+
|
|
91
|
+
context.actions.produce((draft) => {
|
|
92
|
+
draft.name = name;
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return useActions<Model, typeof Actions>(
|
|
97
|
+
model,
|
|
98
|
+
class {
|
|
99
|
+
[Actions.Name] = nameAction;
|
|
80
100
|
},
|
|
81
|
-
|
|
82
|
-
}
|
|
101
|
+
);
|
|
102
|
+
}
|
|
83
103
|
```
|
|
84
104
|
|
|
85
105
|
```tsx
|
|
86
106
|
export default function Profile(props: Props): React.ReactElement {
|
|
107
|
+
const [model, actions] = useNameActions();
|
|
108
|
+
|
|
87
109
|
return (
|
|
88
|
-
|
|
89
|
-
{
|
|
90
|
-
<>
|
|
91
|
-
<p>Hey {module.model.name}</p>
|
|
110
|
+
<>
|
|
111
|
+
<p>Hey {model.name}</p>
|
|
92
112
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)}
|
|
98
|
-
</Scope>
|
|
113
|
+
<button onClick={() => actions.dispatch(Actions.Name)}>
|
|
114
|
+
Switch profile
|
|
115
|
+
</button>
|
|
116
|
+
</>
|
|
99
117
|
);
|
|
100
118
|
}
|
|
101
119
|
```
|
|
102
120
|
|
|
103
|
-
|
|
121
|
+
## Error handling
|
|
122
|
+
|
|
123
|
+
Chizu provides a simple way to catch errors that occur within your actions. You can use the `ActionError` component to wrap your application and provide an error handler. This handler will be called whenever an error is thrown in an action.
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
import { ActionError } from "chizu";
|
|
127
|
+
|
|
128
|
+
const App = () => (
|
|
129
|
+
<ActionError handler={(error) => console.error(error)}>
|
|
130
|
+
<Profile />
|
|
131
|
+
</ActionError>
|
|
132
|
+
);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Handling states
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { Op } from "chizu";
|
|
139
|
+
|
|
140
|
+
// Mark a value as pending with an operation
|
|
141
|
+
context.actions.produce((model) => {
|
|
142
|
+
model.name = context.actions.annotate(Op.Update, "New Name");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Check pending state
|
|
146
|
+
actions.inspect.name.pending(); // true
|
|
147
|
+
|
|
148
|
+
// Get remaining count of pending operations
|
|
149
|
+
actions.inspect.name.remaining(); // 1 (next: actions.inspect.name.draft())
|
|
150
|
+
|
|
151
|
+
// Check specific operation
|
|
152
|
+
actions.inspect.name.is(Op.Update); // true
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
<!-- However in the above example where the name is fetched asynchronously, there is no feedback to the user – we can improve that significantly by using the `module.actions.annotate` and `module.validate` helpers:
|
|
104
156
|
|
|
105
157
|
```tsx
|
|
106
158
|
export default <Actions<Module>>function Actions(module) {
|
|
@@ -142,52 +194,7 @@ export default function ProfileView(props: Props): React.ReactElement {
|
|
|
142
194
|
}
|
|
143
195
|
```
|
|
144
196
|
|
|
145
|
-
## Handling errors
|
|
146
|
-
|
|
147
|
-
Most errors are likely to occur in the actions because the views should be free of side effects. First and foremost it's recommended that errors be encoded into your corresponding module using a library such as [`neverthrow`[(https://github.com/supermacro/neverthrow)] – that way you can effectively identify which properties are fallible and render the DOM accordingly:
|
|
148
|
-
|
|
149
|
-
```tsx
|
|
150
|
-
export default <Actions<Module>>function Actions(module) {
|
|
151
|
-
return {
|
|
152
|
-
*[Action.Name]() {
|
|
153
|
-
yield module.actions.produce((draft) => {
|
|
154
|
-
draft.name = null;
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const name = await fetch(/* ... */);
|
|
158
|
-
|
|
159
|
-
return module.actions.produce((draft) => {
|
|
160
|
-
draft.name = name ? Result.Just(name) : Result.Nothing();
|
|
161
|
-
});
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
};
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
However in eventualities where an error has not been caught in an action then the `Lifecycle.Error` is the next best thing – use it to display a toast message and log it your chosen error log service.
|
|
168
|
-
|
|
169
|
-
Additionally when rendering an error may be thrown which prevents the DOM from updating as you'd expect – perhaps a side effect has delivered an unexpected data structure. In those cases again `Lifecycle.Error` is your friend. When such an error is thrown the component boundary will be switched to `Boundary.Error` which you detect using `module.boundary.is(Boundary.Error)` and switch to an alternative markup that _should_ render, within that you could display a button to attempt recovery – simply call an action again and update the meta to switch the boundary back to `Boundary.Default`:
|
|
170
|
-
|
|
171
|
-
```tsx
|
|
172
|
-
export default <Actions<Module>>function Actions(module) {
|
|
173
|
-
return {
|
|
174
|
-
*[Action.Recover]() {
|
|
175
|
-
yield module.actions.produce((draft) => {
|
|
176
|
-
draft.name = null;
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const name = await fetch(/* ... */);
|
|
180
197
|
|
|
181
|
-
return module.actions.produce((draft, meta) => {
|
|
182
|
-
meta.boundary = Boundary.Default;
|
|
183
|
-
draft.name = name;
|
|
184
|
-
});
|
|
185
|
-
},
|
|
186
|
-
};
|
|
187
|
-
};
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
If the component again throws an error after attempting recovery, it will simply switch back to the `Boundary.Error` again.
|
|
191
198
|
|
|
192
199
|
## Distributed actions
|
|
193
200
|
|
|
@@ -269,4 +276,4 @@ export default function Profile(props: Props): React.ReactElement {
|
|
|
269
276
|
</Scope>
|
|
270
277
|
);
|
|
271
278
|
}
|
|
272
|
-
```
|
|
279
|
+
``` -->
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Action, Payload } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Defines a new action with a given payload type.
|
|
4
|
+
*
|
|
5
|
+
* @template T The type of the payload that the action will carry.
|
|
6
|
+
* @param {string} [name] An optional name for the action, used for debugging purposes.
|
|
7
|
+
* @returns {Payload<T>} A new action object.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createAction<T = never>(name?: string): Payload<T>;
|
|
10
|
+
/**
|
|
11
|
+
* Defines a new distributed action with a given payload type.
|
|
12
|
+
* Distributed actions can be shared across different modules.
|
|
13
|
+
*
|
|
14
|
+
* @template T The type of the payload that the action will carry.
|
|
15
|
+
* @param {string} [name] An optional name for the action, used for debugging purposes.
|
|
16
|
+
* @returns {Payload<T>} A new distributed action object.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createDistributedAction<T = never>(name?: string): Payload<T>;
|
|
19
|
+
export declare function isDistributedAction(action: Action): boolean;
|