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.
Files changed (52) hide show
  1. package/README.md +104 -97
  2. package/dist/action/index.d.ts +19 -0
  3. package/dist/chizu.js +142 -425
  4. package/dist/chizu.umd.cjs +3 -1
  5. package/dist/decorators/index.d.ts +6 -0
  6. package/dist/error/index.d.ts +4 -0
  7. package/dist/error/types.d.ts +6 -0
  8. package/dist/hooks/index.d.ts +66 -10
  9. package/dist/hooks/types.d.ts +2 -0
  10. package/dist/hooks/utils.d.ts +18 -0
  11. package/dist/index.d.ts +6 -9
  12. package/dist/types/index.d.ts +47 -52
  13. package/dist/use/index.d.ts +5 -0
  14. package/dist/use/types.d.ts +3 -0
  15. package/dist/use/utils.d.ts +2 -0
  16. package/dist/utils/index.d.ts +28 -3
  17. package/package.json +51 -32
  18. package/dist/context/index.d.ts +0 -7
  19. package/dist/context/types.d.ts +0 -2
  20. package/dist/controller/index.d.ts +0 -3
  21. package/dist/controller/types.d.ts +0 -30
  22. package/dist/errors/index.d.ts +0 -8
  23. package/dist/errors/types.d.ts +0 -13
  24. package/dist/errors/utils.d.ts +0 -30
  25. package/dist/module/index.d.ts +0 -4
  26. package/dist/module/renderer/actions/index.d.ts +0 -3
  27. package/dist/module/renderer/actions/types.d.ts +0 -19
  28. package/dist/module/renderer/controller/index.d.ts +0 -4
  29. package/dist/module/renderer/controller/types.d.ts +0 -11
  30. package/dist/module/renderer/dispatchers/index.d.ts +0 -12
  31. package/dist/module/renderer/dispatchers/types.d.ts +0 -18
  32. package/dist/module/renderer/dispatchers/utils.d.ts +0 -10
  33. package/dist/module/renderer/elements/index.d.ts +0 -4
  34. package/dist/module/renderer/elements/types.d.ts +0 -2
  35. package/dist/module/renderer/elements/utils.d.ts +0 -4
  36. package/dist/module/renderer/index.d.ts +0 -4
  37. package/dist/module/renderer/lifecycles/index.d.ts +0 -3
  38. package/dist/module/renderer/lifecycles/types.d.ts +0 -13
  39. package/dist/module/renderer/model/index.d.ts +0 -5
  40. package/dist/module/renderer/model/types.d.ts +0 -15
  41. package/dist/module/renderer/model/utils.d.ts +0 -8
  42. package/dist/module/renderer/passive/index.d.ts +0 -1
  43. package/dist/module/renderer/types.d.ts +0 -10
  44. package/dist/module/renderer/update/index.d.ts +0 -5
  45. package/dist/module/renderer/update/types.d.ts +0 -2
  46. package/dist/module/renderer/utils.d.ts +0 -8
  47. package/dist/module/types.d.ts +0 -12
  48. package/dist/utils/produce/index.d.ts +0 -23
  49. package/dist/utils/produce/utils.d.ts +0 -15
  50. package/dist/view/index.d.ts +0 -3
  51. package/dist/view/types.d.ts +0 -16
  52. /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
+ [![Checks](https://github.com/Wildhoney/Chizu/actions/workflows/checks.yml/badge.svg)](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. [Handling errors](#handling-errors)
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 &ndash; styles, actions and views.
25
- - Avoid vendor lock-in with framework agnostic libraries such as [Shoelace](https://shoelace.style/).
29
+ - Strongly typed throughout &ndash; dispatches, models, etc&hellip;
26
30
  - Easily communicate between actions using distributed actions.
27
- - State is mutated sequentially ([FIFO](<https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)>)) and deeply merged for queued mutations.
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
- export default <Actions<Module>>function Actions(module) {
35
- return {
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
- <Scope<Module> using={{ model, actions, props }}>
49
- {(module) => (
50
- <>
51
- <p>Hey {module.model.name}</p>
61
+ <>
62
+ <p>Hey {model.name}</p>
52
63
 
53
- <button
54
- onClick={() => module.actions.dispatch([Action.Name, randomName()])}
55
- >
56
- Switch profile
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 &ndash; as we're starting to require more control in our actions we&apos;ll move to our own fine-tuned action instead of `utils.set`:
66
73
 
67
74
  ```tsx
68
- export default <Actions<Module>>function Actions(module) {
69
- return {
70
- async *[Action.Name]() {
71
- yield module.actions.produce((draft) => {
72
- draft.name = null;
73
- });
75
+ const model: Model = {
76
+ name: null,
77
+ };
74
78
 
75
- const name = await fetch(/* ... */);
79
+ export class Actions {
80
+ static Name = createAction();
81
+ }
76
82
 
77
- return module.actions.produce((draft) => {
78
- draft.name = name;
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
- <Scope<Module> using={{ model, actions, props }}>
89
- {(module) => (
90
- <>
91
- <p>Hey {module.model.name}</p>
110
+ <>
111
+ <p>Hey {model.name}</p>
92
112
 
93
- <button onClick={() => module.actions.dispatch([Action.Name])}>
94
- Switch profile
95
- </button>
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
- However in the above example where the name is fetched asynchronously, there is no feedback to the user &ndash; we can improve that significantly by using the `module.actions.annotate` and `module.validate` helpers:
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 &ndash; 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)] &ndash; 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 &ndash; 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 &ndash; 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 &ndash; 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;