chizu 0.1.0
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 +217 -0
- package/dist/app/index.d.ts +4 -0
- package/dist/app/types.d.ts +10 -0
- package/dist/chizu.js +361 -0
- package/dist/chizu.umd.cjs +1 -0
- package/dist/controller/index.d.ts +3 -0
- package/dist/controller/types.d.ts +33 -0
- package/dist/index.d.ts +19 -0
- package/dist/model/index.d.ts +2 -0
- package/dist/model/state/index.d.ts +6 -0
- package/dist/module/index.d.ts +4 -0
- package/dist/module/renderer/actions/index.d.ts +3 -0
- package/dist/module/renderer/actions/types.d.ts +21 -0
- package/dist/module/renderer/controller/index.d.ts +3 -0
- package/dist/module/renderer/controller/types.d.ts +11 -0
- package/dist/module/renderer/decorate/index.d.ts +1 -0
- package/dist/module/renderer/dispatchers/index.d.ts +11 -0
- package/dist/module/renderer/dispatchers/types.d.ts +34 -0
- package/dist/module/renderer/dispatchers/utils.d.ts +29 -0
- package/dist/module/renderer/dispatchers/utils.test.d.ts +1 -0
- package/dist/module/renderer/elements/index.d.ts +4 -0
- package/dist/module/renderer/elements/types.d.ts +2 -0
- package/dist/module/renderer/elements/utils.d.ts +4 -0
- package/dist/module/renderer/index.d.ts +4 -0
- package/dist/module/renderer/lifecycles/index.d.ts +3 -0
- package/dist/module/renderer/lifecycles/types.d.ts +11 -0
- package/dist/module/renderer/logger/index.d.ts +5 -0
- package/dist/module/renderer/logger/types.d.ts +10 -0
- package/dist/module/renderer/model/index.d.ts +4 -0
- package/dist/module/renderer/model/types.d.ts +7 -0
- package/dist/module/renderer/mutations/index.d.ts +3 -0
- package/dist/module/renderer/mutations/types.d.ts +11 -0
- package/dist/module/renderer/process/index.d.ts +2 -0
- package/dist/module/renderer/process/types.d.ts +3 -0
- package/dist/module/renderer/queue/index.d.ts +2 -0
- package/dist/module/renderer/queue/types.d.ts +2 -0
- package/dist/module/renderer/types.d.ts +12 -0
- package/dist/module/renderer/update/index.d.ts +5 -0
- package/dist/module/renderer/update/types.d.ts +2 -0
- package/dist/module/types.d.ts +9 -0
- package/dist/types/index.d.ts +59 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/maybe/index.d.ts +11 -0
- package/dist/utils/maybe/index.test.d.ts +1 -0
- package/dist/utils/placeholder/index.d.ts +30 -0
- package/dist/utils/placeholder/index.test.d.ts +1 -0
- package/dist/utils/placeholder/utils.d.ts +10 -0
- package/dist/utils/sleep/index.d.ts +1 -0
- package/dist/view/index.d.ts +3 -0
- package/dist/view/types.d.ts +17 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="/media/logo.png" width="475" />
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
Strongly typed React framework using generators and efficiently updated views alongside the publish-subscribe pattern.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
1. [Benefits](#benefits)
|
|
10
|
+
1. [Getting started](#getting-started)
|
|
11
|
+
1. [Handling errors](#handling-errors)
|
|
12
|
+
|
|
13
|
+
## Benefits
|
|
14
|
+
|
|
15
|
+
- Thoughtful event-driven architecture superset of [React](https://react.dev/).
|
|
16
|
+
- Super efficient with views only re-rendering when absolutely necessary.
|
|
17
|
+
- Built-in support for [optimistic updates](https://medium.com/@kyledeguzmanx/what-are-optimistic-updates-483662c3e171) within components.
|
|
18
|
+
- Mostly standard JavaScript without quirky rules and exceptions.
|
|
19
|
+
- Clear separation of concerns between business logic and markup.
|
|
20
|
+
- First-class support for skeleton loading using generators.
|
|
21
|
+
- Strongly typed throughout – styles, controllers and views.
|
|
22
|
+
- Avoid vendor lock-in with framework agnostic libraries such as [Shoelace](https://shoelace.style/).
|
|
23
|
+
- Easily communicate between controllers using distributed actions.
|
|
24
|
+
- State is mutated sequentially ([FIFO](<https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)>)) and deeply merged for queued mutations.
|
|
25
|
+
|
|
26
|
+
## Getting started
|
|
27
|
+
|
|
28
|
+
Controllers are responsible for mutating the state of the view. In the below example the `name` is dispatched from the view to the controller, the state is updated and the view is rendered once with the updated value.
|
|
29
|
+
|
|
30
|
+
<kbd>Controller</kbd>
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
export default create.controller<Module>((self) => {
|
|
34
|
+
return {
|
|
35
|
+
*[Events.Name](name) {
|
|
36
|
+
return self.actions.produce((draft) => {
|
|
37
|
+
draft.name = name;
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
<kbd>View</kbd>
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
export default create.view<Module>((self) => {
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
<p>Hey {self.model.name}</p>
|
|
51
|
+
|
|
52
|
+
<button
|
|
53
|
+
onClick={() => self.actions.dispatch([Events.Name, randomName()])}
|
|
54
|
+
>
|
|
55
|
+
Switch profile
|
|
56
|
+
</button>
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Fetching the name from an external source using an `actions.io` causes the controller event (`Events.Name`) and associated view to be invoked twice – once with a record of mutations to display a pending state, and then again with the model once it's been mutated.
|
|
63
|
+
|
|
64
|
+
<kbd>Controller</kbd>
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
export default create.controller<Module>((self) => {
|
|
68
|
+
return {
|
|
69
|
+
*[Events.Name]() {
|
|
70
|
+
yield self.actions.io(async () => {
|
|
71
|
+
const name = await fetch(/* ... */);
|
|
72
|
+
|
|
73
|
+
return self.actions.produce((draft) => {
|
|
74
|
+
draft.name = name;
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return self.actions.produce((draft) => {
|
|
79
|
+
draft.name = null;
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
<kbd>View</kbd>
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
export default create.view<Module>((self) => {
|
|
90
|
+
return (
|
|
91
|
+
<>
|
|
92
|
+
<p>Hey {self.model.name}</p>
|
|
93
|
+
|
|
94
|
+
<button onClick={() => self.actions.dispatch([Events.Name])}>
|
|
95
|
+
Switch profile
|
|
96
|
+
</button>
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
As the event is invoked twice, it's important they are idempotent – by encapsulating your side effects in `actions.io` the promises are resolved before invoking the event again with those resolved values.
|
|
103
|
+
|
|
104
|
+
In the above example the name is fetched asynchronously – however there is no feedback to the user, we can improve that by using the `self.actions.placeholder` and `self.validate` helpers:
|
|
105
|
+
|
|
106
|
+
<kbd>Controller</kbd>
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
export default create.controller<Module>((self) => {
|
|
110
|
+
return {
|
|
111
|
+
*[Events.Name]() {
|
|
112
|
+
yield self.actions.io(async () => {
|
|
113
|
+
const name = await fetch(/* ... */);
|
|
114
|
+
|
|
115
|
+
return self.actions.produce((draft) => {
|
|
116
|
+
draft.name = name;
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return self.actions.produce((draft) => {
|
|
121
|
+
draft.name = self.actions.pending(null, State.Updating);
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
<kbd>View</kbd>
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
export default create.view<Module>((self) => {
|
|
132
|
+
return (
|
|
133
|
+
<>
|
|
134
|
+
<p>Hey {self.model.name}</p>
|
|
135
|
+
|
|
136
|
+
{self.validate.name.is(State.Pending) && (
|
|
137
|
+
<p>Switching profiles…</p>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
<button
|
|
141
|
+
disabled={self.validate.name.is(State.Updating)}
|
|
142
|
+
onClick={() => self.actions.dispatch([Events.Name])}
|
|
143
|
+
>
|
|
144
|
+
Switch profile
|
|
145
|
+
</button>
|
|
146
|
+
</>
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Handling Errors
|
|
152
|
+
|
|
153
|
+
Controller actions can throw errors directly or in any of their associated `yield` actions – all unhandled errors are automatically caught and broadcast using the `Lifecycle.Error` action – you can render these [in a toast](https://github.com/fkhadra/react-toastify#readme) or similar UI.
|
|
154
|
+
|
|
155
|
+
You can also customise these errors a little further with your own error `enum` which describes the error type:
|
|
156
|
+
|
|
157
|
+
<kbd>Types</kbd>
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
export const enum Errors {
|
|
161
|
+
UserValidation,
|
|
162
|
+
IncorrectPassword,
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
<kbd>Controller</kbd>
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
export default create.controller<Module>((self) => {
|
|
170
|
+
return {
|
|
171
|
+
*[Events.Name]() {
|
|
172
|
+
yield self.actions.io(async () => {
|
|
173
|
+
const name = await fetch(/* ... */);
|
|
174
|
+
|
|
175
|
+
if (!name) throw new EventError(Errors.UserValidation);
|
|
176
|
+
|
|
177
|
+
return self.actions.produce((draft) => {
|
|
178
|
+
draft.name = name;
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return self.actions.produce((draft) => {
|
|
183
|
+
draft.name = null;
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
However showing a toast message is not always relevant, you may want a more detailed error message such as a user not found message – although you could introduce another property for such errors in your model, you could mark the property as fallible by giving it a `Maybe` type because it then keeps everything nicely associated with the `name` property rather than creating another property:
|
|
191
|
+
|
|
192
|
+
<kbd>Controller</kbd>
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
export default create.controller<Module>((self) => {
|
|
196
|
+
return {
|
|
197
|
+
*[Events.Name]() {
|
|
198
|
+
yield self.actions.io(async () => {
|
|
199
|
+
const name = await fetch(/* ... */);
|
|
200
|
+
|
|
201
|
+
if (!name)
|
|
202
|
+
return self.actions.produce((draft) => {
|
|
203
|
+
draft.name = Maybe.of(new EventError(Errors.UserValidation));
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return self.actions.produce((draft) => {
|
|
207
|
+
draft.name = Maybe.of(name);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return self.actions.produce((draft) => {
|
|
212
|
+
draft.name = Maybe.of(null);
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { default as EventEmitter } from 'eventemitter3';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export type AppOptions = React.ComponentType;
|
|
4
|
+
export type AppContext = {
|
|
5
|
+
appEmitter: EventEmitter;
|
|
6
|
+
};
|
|
7
|
+
export type TreeProps = {
|
|
8
|
+
tree: React.ComponentType;
|
|
9
|
+
};
|
|
10
|
+
export type UseApp = AppContext;
|
package/dist/chizu.js
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
var D = (e) => {
|
|
2
|
+
throw TypeError(e);
|
|
3
|
+
};
|
|
4
|
+
var U = (e, t, r) => t.has(e) || D("Cannot " + r);
|
|
5
|
+
var d = (e, t, r) => (U(e, t, "read from private field"), r ? r.call(e) : t.get(e)), p = (e, t, r) => t.has(e) ? D("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(e) : t.set(e, r), h = (e, t, r, n) => (U(e, t, "write to private field"), n ? n.call(e, r) : t.set(e, r), r);
|
|
6
|
+
import { jsx as M } from "react/jsx-runtime";
|
|
7
|
+
import C from "eventemitter3";
|
|
8
|
+
import * as i from "react";
|
|
9
|
+
import * as k from "react-dom/client";
|
|
10
|
+
const O = i.createContext({
|
|
11
|
+
appEmitter: new C()
|
|
12
|
+
});
|
|
13
|
+
function T() {
|
|
14
|
+
return i.useContext(O);
|
|
15
|
+
}
|
|
16
|
+
function z(e) {
|
|
17
|
+
k.createRoot(document.body).render(/* @__PURE__ */ M(B, { tree: e }));
|
|
18
|
+
}
|
|
19
|
+
function B({ tree: e }) {
|
|
20
|
+
const t = i.useMemo(
|
|
21
|
+
() => ({
|
|
22
|
+
appEmitter: new C()
|
|
23
|
+
}),
|
|
24
|
+
[]
|
|
25
|
+
);
|
|
26
|
+
return /* @__PURE__ */ M(O.Provider, { value: t, children: /* @__PURE__ */ M(e, {}) });
|
|
27
|
+
}
|
|
28
|
+
function J(e) {
|
|
29
|
+
return e;
|
|
30
|
+
}
|
|
31
|
+
function N(e) {
|
|
32
|
+
return e;
|
|
33
|
+
}
|
|
34
|
+
var _ = /* @__PURE__ */ ((e) => (e[e.Idle = 0] = "Idle", e[e.Pending = 1] = "Pending", e[e.Adding = 2] = "Adding", e[e.Removing = 4] = "Removing", e[e.Updating = 8] = "Updating", e[e.Moving = 16] = "Moving", e))(_ || {}), f = /* @__PURE__ */ ((e) => (e.Mount = "lifecycle/mount", e.Tree = "lifecycle/tree", e.Derive = "lifecycle/derive", e.Error = "distributed/lifecycle/error", e.Unmount = "lifecycle/unmount", e))(f || {});
|
|
35
|
+
const q = Symbol("unwrap");
|
|
36
|
+
var g, y, w;
|
|
37
|
+
class P {
|
|
38
|
+
constructor(t, r, n) {
|
|
39
|
+
p(this, g);
|
|
40
|
+
p(this, y);
|
|
41
|
+
p(this, w);
|
|
42
|
+
h(this, g, t), h(this, y, r), h(this, w, n);
|
|
43
|
+
}
|
|
44
|
+
value() {
|
|
45
|
+
return d(this, g);
|
|
46
|
+
}
|
|
47
|
+
state() {
|
|
48
|
+
return d(this, y);
|
|
49
|
+
}
|
|
50
|
+
process() {
|
|
51
|
+
return d(this, w);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
g = new WeakMap(), y = new WeakMap(), w = new WeakMap();
|
|
55
|
+
function x(e, t, r) {
|
|
56
|
+
return new P(e, t, r);
|
|
57
|
+
}
|
|
58
|
+
function I(e, t) {
|
|
59
|
+
return new Proxy(e, {
|
|
60
|
+
get(r, n) {
|
|
61
|
+
const o = Reflect.get(r, n), u = o instanceof P ? o.value() : o;
|
|
62
|
+
return n === q ? r : typeof u == "object" ? I(u, t) : u;
|
|
63
|
+
},
|
|
64
|
+
set(r, n, o) {
|
|
65
|
+
const s = o instanceof P, u = o, c = s ? u.process() : null, l = s ? u.value() : o, a = l == null ? null : l[q] ?? l, v = /^\d+$/.test(String(n));
|
|
66
|
+
return s && c && t.add({
|
|
67
|
+
key: v ? null : n,
|
|
68
|
+
value: typeof a == "object" ? a : r,
|
|
69
|
+
process: c,
|
|
70
|
+
state: u.state()
|
|
71
|
+
}), Reflect.set(r, n, a);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function Q(e, t) {
|
|
76
|
+
function r(n, o = "") {
|
|
77
|
+
return new Proxy(n, {
|
|
78
|
+
get(s, u) {
|
|
79
|
+
const c = Reflect.get(s, u);
|
|
80
|
+
if (typeof u == "symbol") return c;
|
|
81
|
+
if (u === "is") {
|
|
82
|
+
const l = [...t].filter((a) => a.value === s && (a.key === o || !a.key));
|
|
83
|
+
return (a) => l.length === 0 ? !1 : !!([...new Set(
|
|
84
|
+
l.map((R) => R.state)
|
|
85
|
+
)].reduce(
|
|
86
|
+
(R, $) => R | $,
|
|
87
|
+
_.Pending
|
|
88
|
+
) & a);
|
|
89
|
+
}
|
|
90
|
+
return r(typeof c == "object" && c !== null ? c : s, u);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return r(e);
|
|
95
|
+
}
|
|
96
|
+
function W(e) {
|
|
97
|
+
return i.useMemo(
|
|
98
|
+
() => ({
|
|
99
|
+
controller: {
|
|
100
|
+
get model() {
|
|
101
|
+
return e.model.current;
|
|
102
|
+
},
|
|
103
|
+
queue: [],
|
|
104
|
+
events: e.options.props,
|
|
105
|
+
actions: {
|
|
106
|
+
io(t) {
|
|
107
|
+
return t;
|
|
108
|
+
},
|
|
109
|
+
placeholder(t, r) {
|
|
110
|
+
return x(t, r, e.process.current);
|
|
111
|
+
},
|
|
112
|
+
produce(t) {
|
|
113
|
+
return (r) => (t(I(r, e.mutations.current)), r);
|
|
114
|
+
},
|
|
115
|
+
dispatch([t, ...r]) {
|
|
116
|
+
const n = Promise.withResolvers();
|
|
117
|
+
return e.dispatchers.dispatch(t, r, n), n.promise;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
view: {
|
|
122
|
+
get model() {
|
|
123
|
+
return e.model.current;
|
|
124
|
+
},
|
|
125
|
+
get validate() {
|
|
126
|
+
return Q(
|
|
127
|
+
e.model.current,
|
|
128
|
+
e.mutations.current
|
|
129
|
+
);
|
|
130
|
+
},
|
|
131
|
+
events: e.options.props,
|
|
132
|
+
actions: {
|
|
133
|
+
dispatch([t, ...r]) {
|
|
134
|
+
const n = Promise.withResolvers();
|
|
135
|
+
return e.dispatchers.dispatch(t, r, n), n.promise;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}),
|
|
140
|
+
[]
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
function F(e) {
|
|
144
|
+
return i.useMemo(() => {
|
|
145
|
+
const t = e.options.controller(e.actions.controller);
|
|
146
|
+
return Object.entries(t).forEach(([n, o]) => e.dispatchers.attach(n, o)), t;
|
|
147
|
+
}, []);
|
|
148
|
+
}
|
|
149
|
+
function G(e) {
|
|
150
|
+
return (t, r) => async (n = Promise.withResolvers(), o) => {
|
|
151
|
+
if (typeof r != "function") return;
|
|
152
|
+
const s = Symbol(`process/${Math.random()}`);
|
|
153
|
+
e.queue.current.add(n.promise);
|
|
154
|
+
const u = new AbortController();
|
|
155
|
+
try {
|
|
156
|
+
const c = {
|
|
157
|
+
app: e.app,
|
|
158
|
+
task: n,
|
|
159
|
+
process: s,
|
|
160
|
+
ƒ: r,
|
|
161
|
+
abortController: u,
|
|
162
|
+
payload: o,
|
|
163
|
+
props: e
|
|
164
|
+
}, l = H(e, c);
|
|
165
|
+
e.queue.current.size > 1 && await Promise.allSettled([...e.queue.current].slice(0, -1)), l.size > 0 && await K(e, c, l), e.queue.current.delete(n.promise), n.resolve();
|
|
166
|
+
} catch (c) {
|
|
167
|
+
console.log("hmm, error", c), e.app.appEmitter.emit(f.Error, n, [c]);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function H(e, t) {
|
|
172
|
+
const r = /* @__PURE__ */ new Set(), n = t.ƒ(...t.payload);
|
|
173
|
+
for (; ; )
|
|
174
|
+
try {
|
|
175
|
+
const o = n.next();
|
|
176
|
+
if (o.done && typeof o.value == "function") {
|
|
177
|
+
e.process.current = t.process;
|
|
178
|
+
const s = o.value(t.props.model.current);
|
|
179
|
+
return s && (t.props.model.current = s), e.process.current = null, t.props.update.rerender(), r;
|
|
180
|
+
}
|
|
181
|
+
e.process.current = t.process, typeof o.value == "function" && r.add(
|
|
182
|
+
o.value({ signal: t.abortController.signal }).catch((s) => () => {
|
|
183
|
+
e.app.appEmitter.emit(f.Error, t.task, [s]);
|
|
184
|
+
})
|
|
185
|
+
), e.process.current = null;
|
|
186
|
+
} catch (o) {
|
|
187
|
+
return e.app.appEmitter.emit(f.Error, t.task, [o]), /* @__PURE__ */ new Set();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function K(e, t, r) {
|
|
191
|
+
(await Promise.allSettled(r)).forEach((n) => {
|
|
192
|
+
if (e.process.current = t.process, n.status === "rejected")
|
|
193
|
+
return;
|
|
194
|
+
const o = typeof n.value == "function" ? n.value(t.props.model.current) : n;
|
|
195
|
+
o && (t.props.model.current = o), e.process.current = null, e.mutations.current = new Set(
|
|
196
|
+
[...e.mutations.current].filter(
|
|
197
|
+
(s) => s.process !== t.process
|
|
198
|
+
)
|
|
199
|
+
), t.props.update.rerender();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function A(e) {
|
|
203
|
+
return e.startsWith("distributed");
|
|
204
|
+
}
|
|
205
|
+
var E, b;
|
|
206
|
+
class L extends Error {
|
|
207
|
+
constructor(r, n = null) {
|
|
208
|
+
super(String(n));
|
|
209
|
+
p(this, E);
|
|
210
|
+
p(this, b);
|
|
211
|
+
h(this, E, r), h(this, b, n);
|
|
212
|
+
}
|
|
213
|
+
get type() {
|
|
214
|
+
return d(this, E);
|
|
215
|
+
}
|
|
216
|
+
get message() {
|
|
217
|
+
return d(this, b) || "";
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
E = new WeakMap(), b = new WeakMap();
|
|
221
|
+
function V(e) {
|
|
222
|
+
const t = T(), r = G(e);
|
|
223
|
+
return i.useMemo(() => {
|
|
224
|
+
const n = new C(), o = t.appEmitter;
|
|
225
|
+
return {
|
|
226
|
+
attach(s, u) {
|
|
227
|
+
const c = String(s);
|
|
228
|
+
A(c) ? o.on(c, r(s, u)) : n.on(c, r(s, u));
|
|
229
|
+
},
|
|
230
|
+
dispatch(s, u, c) {
|
|
231
|
+
const l = String(s);
|
|
232
|
+
A(l) ? o.emit(l, c, u) : n.emit(l, c, u);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}, []);
|
|
236
|
+
}
|
|
237
|
+
function X() {
|
|
238
|
+
const e = i.useRef(null);
|
|
239
|
+
return i.useMemo(() => ({ customElement: e }), []);
|
|
240
|
+
}
|
|
241
|
+
function Y(e) {
|
|
242
|
+
const t = i.useRef(!1);
|
|
243
|
+
i.useLayoutEffect(() => {
|
|
244
|
+
e.dispatchers.dispatch(f.Mount, []), e.dispatchers.dispatch(f.Tree, [
|
|
245
|
+
e.elements.customElement.current
|
|
246
|
+
]), e.dispatchers.dispatch(f.Derive, [e.options.props]);
|
|
247
|
+
}, [e.options.props]), i.useLayoutEffect(() => {
|
|
248
|
+
if (!t.current)
|
|
249
|
+
return t.current = !0, () => e.dispatchers.dispatch(f.Unmount, []);
|
|
250
|
+
}, []);
|
|
251
|
+
}
|
|
252
|
+
function Z(e) {
|
|
253
|
+
return i.useMemo(
|
|
254
|
+
() => ({
|
|
255
|
+
output(t) {
|
|
256
|
+
const r = e.elements.customElement.current;
|
|
257
|
+
console.groupCollapsed(
|
|
258
|
+
"%cRendered",
|
|
259
|
+
"background: rgb(217, 235, 240); color: rgb(0, 51, 102); border-radius: 2px; padding: 0 5px",
|
|
260
|
+
r
|
|
261
|
+
), console.groupEnd();
|
|
262
|
+
}
|
|
263
|
+
}),
|
|
264
|
+
[]
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
function ee(e) {
|
|
268
|
+
return i.useRef(e.options.model);
|
|
269
|
+
}
|
|
270
|
+
function te() {
|
|
271
|
+
return i.useRef(/* @__PURE__ */ new Set());
|
|
272
|
+
}
|
|
273
|
+
function re() {
|
|
274
|
+
return i.useRef(null);
|
|
275
|
+
}
|
|
276
|
+
function ne() {
|
|
277
|
+
return i.useRef(/* @__PURE__ */ new Set());
|
|
278
|
+
}
|
|
279
|
+
function oe() {
|
|
280
|
+
const [e, t] = i.useReducer((r) => r + 1, 0);
|
|
281
|
+
return i.useMemo(() => ({ hash: e, rerender: t }), [e]);
|
|
282
|
+
}
|
|
283
|
+
function se({
|
|
284
|
+
options: e
|
|
285
|
+
}) {
|
|
286
|
+
const t = T(), r = oe(), n = ne(), o = te(), s = X(), u = re(), c = ee({ options: e }), l = Z({ options: e, elements: s }), a = V({
|
|
287
|
+
app: t,
|
|
288
|
+
options: e,
|
|
289
|
+
update: r,
|
|
290
|
+
model: c,
|
|
291
|
+
logger: l,
|
|
292
|
+
queue: n,
|
|
293
|
+
mutations: o,
|
|
294
|
+
process: u
|
|
295
|
+
}), v = W({
|
|
296
|
+
app: t,
|
|
297
|
+
options: e,
|
|
298
|
+
model: c,
|
|
299
|
+
dispatchers: a,
|
|
300
|
+
mutations: o,
|
|
301
|
+
process: u
|
|
302
|
+
});
|
|
303
|
+
return F({ options: e, dispatchers: a, actions: v }), Y({ options: e, dispatchers: a, elements: s }), i.useMemo(() => (l.output({}), i.createElement(e.name, {
|
|
304
|
+
ref: s.customElement,
|
|
305
|
+
children: e.view(v.view)
|
|
306
|
+
})), [r.hash]);
|
|
307
|
+
}
|
|
308
|
+
function ue(e) {
|
|
309
|
+
return (t) => i.memo(
|
|
310
|
+
(r) => se({
|
|
311
|
+
options: {
|
|
312
|
+
...t,
|
|
313
|
+
name: e.join(""),
|
|
314
|
+
props: r
|
|
315
|
+
}
|
|
316
|
+
}),
|
|
317
|
+
(r, n) => JSON.stringify(r) === JSON.stringify(n)
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
function ce(e) {
|
|
321
|
+
return e;
|
|
322
|
+
}
|
|
323
|
+
var m;
|
|
324
|
+
const S = class S {
|
|
325
|
+
constructor(t) {
|
|
326
|
+
p(this, m);
|
|
327
|
+
h(this, m, t);
|
|
328
|
+
}
|
|
329
|
+
static of(t) {
|
|
330
|
+
return new S(t);
|
|
331
|
+
}
|
|
332
|
+
map(t = (n) => n, r = (n) => n) {
|
|
333
|
+
return d(this, m) == null || d(this, m) instanceof Error ? r(d(this, m)) : t(d(this, m));
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
m = new WeakMap();
|
|
337
|
+
let j = S;
|
|
338
|
+
function ie(e) {
|
|
339
|
+
return new Promise((t) => setTimeout(t, e));
|
|
340
|
+
}
|
|
341
|
+
function le(e) {
|
|
342
|
+
return e ? !!(e && typeof e != "symbol") : Symbol(`pk.${Date.now()}.${crypto.randomUUID()}`);
|
|
343
|
+
}
|
|
344
|
+
function ae(e) {
|
|
345
|
+
return e instanceof L;
|
|
346
|
+
}
|
|
347
|
+
const he = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
348
|
+
__proto__: null,
|
|
349
|
+
isEventError: ae,
|
|
350
|
+
maybe: j,
|
|
351
|
+
pk: le,
|
|
352
|
+
sleep: ie
|
|
353
|
+
}, Symbol.toStringTag, { value: "Module" })), ve = { app: z, module: ue, model: N, view: ce, controller: J };
|
|
354
|
+
export {
|
|
355
|
+
L as EventError,
|
|
356
|
+
f as Lifecycle,
|
|
357
|
+
j as Maybe,
|
|
358
|
+
_ as State,
|
|
359
|
+
ve as create,
|
|
360
|
+
he as utils
|
|
361
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(o,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("react/jsx-runtime"),require("eventemitter3"),require("react"),require("react-dom/client")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","eventemitter3","react","react-dom/client"],a):(o=typeof globalThis<"u"?globalThis:o||self,a(o.Chizu={},o.jsxRuntime,o.EventEmitter3,o.React,o.ReactDOM))})(this,function(o,a,f,j,N){"use strict";var $=o=>{throw TypeError(o)};var B=(o,a,f)=>a.has(o)||$("Cannot "+f);var p=(o,a,f)=>(B(o,a,"read from private field"),f?f.call(o):a.get(o)),y=(o,a,f)=>a.has(o)?$("Cannot add the same private member more than once"):a instanceof WeakSet?a.add(o):a.set(o,f),g=(o,a,f,j)=>(B(o,a,"write to private field"),j?j.call(o,f):a.set(o,f),f);var w,b,E,M,R,v;function C(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const r in e)if(r!=="default"){const n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:()=>e[r]})}}return t.default=e,Object.freeze(t)}const l=C(j),J=C(N),T=l.createContext({appEmitter:new f});function U(){return l.useContext(T)}function L(e){J.createRoot(document.body).render(a.jsx(Q,{tree:e}))}function Q({tree:e}){const t=l.useMemo(()=>({appEmitter:new f}),[]);return a.jsx(T.Provider,{value:t,children:a.jsx(e,{})})}function W(e){return e}function x(e){return e}var S=(e=>(e[e.Idle=0]="Idle",e[e.Pending=1]="Pending",e[e.Adding=2]="Adding",e[e.Removing=4]="Removing",e[e.Updating=8]="Updating",e[e.Moving=16]="Moving",e))(S||{}),h=(e=>(e.Mount="lifecycle/mount",e.Tree="lifecycle/tree",e.Derive="lifecycle/derive",e.Error="distributed/lifecycle/error",e.Unmount="lifecycle/unmount",e))(h||{});const A=Symbol("unwrap");class q{constructor(t,r,n){y(this,w);y(this,b);y(this,E);g(this,w,t),g(this,b,r),g(this,E,n)}value(){return p(this,w)}state(){return p(this,b)}process(){return p(this,E)}}w=new WeakMap,b=new WeakMap,E=new WeakMap;function F(e,t,r){return new q(e,t,r)}function z(e,t){return new Proxy(e,{get(r,n){const u=Reflect.get(r,n),c=u instanceof q?u.value():u;return n===A?r:typeof c=="object"?z(c,t):c},set(r,n,u){const s=u instanceof q,c=u,i=s?c.process():null,d=s?c.value():u,m=d==null?null:d[A]??d,P=/^\d+$/.test(String(n));return s&&i&&t.add({key:P?null:n,value:typeof m=="object"?m:r,process:i,state:c.state()}),Reflect.set(r,n,m)}})}function G(e,t){function r(n,u=""){return new Proxy(n,{get(s,c){const i=Reflect.get(s,c);if(typeof c=="symbol")return i;if(c==="is"){const d=[...t].filter(m=>m.value===s&&(m.key===u||!m.key));return m=>d.length===0?!1:!!([...new Set(d.map(_=>_.state))].reduce((_,ve)=>_|ve,S.Pending)&m)}return r(typeof i=="object"&&i!==null?i:s,c)}})}return r(e)}function H(e){return l.useMemo(()=>({controller:{get model(){return e.model.current},queue:[],events:e.options.props,actions:{io(t){return t},placeholder(t,r){return F(t,r,e.process.current)},produce(t){return r=>(t(z(r,e.mutations.current)),r)},dispatch([t,...r]){const n=Promise.withResolvers();return e.dispatchers.dispatch(t,r,n),n.promise}}},view:{get model(){return e.model.current},get validate(){return G(e.model.current,e.mutations.current)},events:e.options.props,actions:{dispatch([t,...r]){const n=Promise.withResolvers();return e.dispatchers.dispatch(t,r,n),n.promise}}}}),[])}function K(e){return l.useMemo(()=>{const t=e.options.controller(e.actions.controller);return Object.entries(t).forEach(([n,u])=>e.dispatchers.attach(n,u)),t},[])}function V(e){return(t,r)=>async(n=Promise.withResolvers(),u)=>{if(typeof r!="function")return;const s=Symbol(`process/${Math.random()}`);e.queue.current.add(n.promise);const c=new AbortController;try{const i={app:e.app,task:n,process:s,ƒ:r,abortController:c,payload:u,props:e},d=X(e,i);e.queue.current.size>1&&await Promise.allSettled([...e.queue.current].slice(0,-1)),d.size>0&&await Y(e,i,d),e.queue.current.delete(n.promise),n.resolve()}catch(i){console.log("hmm, error",i),e.app.appEmitter.emit(h.Error,n,[i])}}}function X(e,t){const r=new Set,n=t.ƒ(...t.payload);for(;;)try{const u=n.next();if(u.done&&typeof u.value=="function"){e.process.current=t.process;const s=u.value(t.props.model.current);return s&&(t.props.model.current=s),e.process.current=null,t.props.update.rerender(),r}e.process.current=t.process,typeof u.value=="function"&&r.add(u.value({signal:t.abortController.signal}).catch(s=>()=>{e.app.appEmitter.emit(h.Error,t.task,[s])})),e.process.current=null}catch(u){return e.app.appEmitter.emit(h.Error,t.task,[u]),new Set}}async function Y(e,t,r){(await Promise.allSettled(r)).forEach(n=>{if(e.process.current=t.process,n.status==="rejected")return;const u=typeof n.value=="function"?n.value(t.props.model.current):n;u&&(t.props.model.current=u),e.process.current=null,e.mutations.current=new Set([...e.mutations.current].filter(s=>s.process!==t.process)),t.props.update.rerender()})}function k(e){return e.startsWith("distributed")}class I extends Error{constructor(r,n=null){super(String(n));y(this,M);y(this,R);g(this,M,r),g(this,R,n)}get type(){return p(this,M)}get message(){return p(this,R)||""}}M=new WeakMap,R=new WeakMap;function Z(e){const t=U(),r=V(e);return l.useMemo(()=>{const n=new f,u=t.appEmitter;return{attach(s,c){const i=String(s);k(i)?u.on(i,r(s,c)):n.on(i,r(s,c))},dispatch(s,c,i){const d=String(s);k(d)?u.emit(d,i,c):n.emit(d,i,c)}}},[])}function ee(){const e=l.useRef(null);return l.useMemo(()=>({customElement:e}),[])}function te(e){const t=l.useRef(!1);l.useLayoutEffect(()=>{e.dispatchers.dispatch(h.Mount,[]),e.dispatchers.dispatch(h.Tree,[e.elements.customElement.current]),e.dispatchers.dispatch(h.Derive,[e.options.props])},[e.options.props]),l.useLayoutEffect(()=>{if(!t.current)return t.current=!0,()=>e.dispatchers.dispatch(h.Unmount,[])},[])}function re(e){return l.useMemo(()=>({output(t){const r=e.elements.customElement.current;console.groupCollapsed("%cRendered","background: rgb(217, 235, 240); color: rgb(0, 51, 102); border-radius: 2px; padding: 0 5px",r),console.groupEnd()}}),[])}function ne(e){return l.useRef(e.options.model)}function oe(){return l.useRef(new Set)}function ue(){return l.useRef(null)}function se(){return l.useRef(new Set)}function ce(){const[e,t]=l.useReducer(r=>r+1,0);return l.useMemo(()=>({hash:e,rerender:t}),[e])}function ie({options:e}){const t=U(),r=ce(),n=se(),u=oe(),s=ee(),c=ue(),i=ne({options:e}),d=re({options:e,elements:s}),m=Z({app:t,options:e,update:r,model:i,logger:d,queue:n,mutations:u,process:c}),P=H({app:t,options:e,model:i,dispatchers:m,mutations:u,process:c});return K({options:e,dispatchers:m,actions:P}),te({options:e,dispatchers:m,elements:s}),l.useMemo(()=>(d.output({}),l.createElement(e.name,{ref:s.customElement,children:e.view(P.view)})),[r.hash])}function le(e){return t=>l.memo(r=>ie({options:{...t,name:e.join(""),props:r}}),(r,n)=>JSON.stringify(r)===JSON.stringify(n))}function ae(e){return e}const D=class D{constructor(t){y(this,v);g(this,v,t)}static of(t){return new D(t)}map(t=n=>n,r=n=>n){return p(this,v)==null||p(this,v)instanceof Error?r(p(this,v)):t(p(this,v))}};v=new WeakMap;let O=D;function de(e){return new Promise(t=>setTimeout(t,e))}function fe(e){return e?!!(e&&typeof e!="symbol"):Symbol(`pk.${Date.now()}.${crypto.randomUUID()}`)}function me(e){return e instanceof I}const pe=Object.freeze(Object.defineProperty({__proto__:null,isEventError:me,maybe:O,pk:fe,sleep:de},Symbol.toStringTag,{value:"Module"})),he={app:L,module:le,model:x,view:ae,controller:W};o.EventError=I,o.Lifecycle=h,o.Maybe=O,o.State=S,o.create=he,o.utils=pe,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EventError } from '../module/renderer/dispatchers/utils.ts';
|
|
2
|
+
import { Head } from '../module/renderer/types.ts';
|
|
3
|
+
import { Actions, Events, Lifecycle, ModuleDefinition, Queue, State, Values } from '../types/index.ts';
|
|
4
|
+
export type Produce<M extends ModuleDefinition> = void | ((model: M["Model"]) => M["Model"]);
|
|
5
|
+
export type IoHelpers = {
|
|
6
|
+
signal: AbortSignal;
|
|
7
|
+
};
|
|
8
|
+
export type ControllerActions<M extends ModuleDefinition> = {
|
|
9
|
+
io<T>(ƒ: (helpers: IoHelpers) => T): T;
|
|
10
|
+
placeholder<T>(value: T, state: State): T;
|
|
11
|
+
produce(ƒ: (draft: M["Model"]) => void): void;
|
|
12
|
+
dispatch(event: M["Actions"]): Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
export type ControllerArgs<M extends ModuleDefinition> = Readonly<{
|
|
15
|
+
model: Readonly<M["Model"]>;
|
|
16
|
+
queue: Readonly<Queue<M["Actions"]>>;
|
|
17
|
+
events: Readonly<Events<M["Props"]>>;
|
|
18
|
+
actions: Readonly<ControllerActions<M>>;
|
|
19
|
+
}>;
|
|
20
|
+
export type ActionGenerator = Generator<void | Promise<void>, void, void>;
|
|
21
|
+
export type ControllerDefinition<M extends ModuleDefinition> = (controller: ControllerArgs<M>) => ControllerInstance<M>;
|
|
22
|
+
export type ControllerInstance<M extends ModuleDefinition> = {
|
|
23
|
+
[Lifecycle.Mount]?(): ActionGenerator;
|
|
24
|
+
[Lifecycle.Derive]?(props: Values<M["Props"]>): ActionGenerator;
|
|
25
|
+
[Lifecycle.Tree]?(tree: HTMLElement): ActionGenerator;
|
|
26
|
+
[Lifecycle.Error]?(error: Error | EventError): ActionGenerator;
|
|
27
|
+
[Lifecycle.Unmount]?(): ActionGenerator;
|
|
28
|
+
} & Partial<Handlers<M>>;
|
|
29
|
+
type Handlers<M extends ModuleDefinition> = {
|
|
30
|
+
[K in Head<M["Actions"]>]: (payload: Payload<M["Actions"], K>) => ActionGenerator;
|
|
31
|
+
};
|
|
32
|
+
type Payload<A extends Actions, K> = A extends [K, infer P] ? P : never;
|
|
33
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { default as app } from './app/index.tsx';
|
|
2
|
+
import { default as controller } from './controller/index.ts';
|
|
3
|
+
import { default as model } from './model/index.ts';
|
|
4
|
+
import { default as module } from './module/index.tsx';
|
|
5
|
+
import { default as view } from './view/index.ts';
|
|
6
|
+
export { State, Lifecycle } from './types/index.ts';
|
|
7
|
+
export { default as Maybe } from './utils/maybe/index.ts';
|
|
8
|
+
export * as utils from './utils/index.ts';
|
|
9
|
+
export declare const create: {
|
|
10
|
+
app: typeof app;
|
|
11
|
+
module: typeof module;
|
|
12
|
+
model: typeof model;
|
|
13
|
+
view: typeof view;
|
|
14
|
+
controller: typeof controller;
|
|
15
|
+
};
|
|
16
|
+
export { EventError } from './module/renderer/dispatchers/utils.ts';
|
|
17
|
+
export type * as Create from './types/index.ts';
|
|
18
|
+
export type { ViewArgs as Args } from './view/types.ts';
|
|
19
|
+
export type { Pk } from './types/index.ts';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ModuleDefinition } from '../types/index.ts';
|
|
2
|
+
import { Options } from './types.ts';
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
export default function module<M extends ModuleDefinition>(name: TemplateStringsArray): (options: Options<M>) => React.ComponentType<M["Props"]>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { UseApp } from '../../../app/types.ts';
|
|
2
|
+
import { ControllerArgs } from '../../../controller/types.ts';
|
|
3
|
+
import { ModuleDefinition } from '../../../types/index.ts';
|
|
4
|
+
import { ViewArgs } from '../../../view/types.ts';
|
|
5
|
+
import { UseDispatchers } from '../dispatchers/types.ts';
|
|
6
|
+
import { UseModel } from '../model/types.ts';
|
|
7
|
+
import { UseMutations } from '../mutations/types.ts';
|
|
8
|
+
import { UseProcess } from '../process/types.ts';
|
|
9
|
+
import { UseOptions } from '../types.ts';
|
|
10
|
+
export type Props<M extends ModuleDefinition> = {
|
|
11
|
+
app: UseApp;
|
|
12
|
+
options: UseOptions<M>;
|
|
13
|
+
model: UseModel;
|
|
14
|
+
dispatchers: UseDispatchers;
|
|
15
|
+
mutations: UseMutations;
|
|
16
|
+
process: UseProcess;
|
|
17
|
+
};
|
|
18
|
+
export type UseActions<M extends ModuleDefinition> = {
|
|
19
|
+
controller: ControllerArgs<M>;
|
|
20
|
+
view: ViewArgs<M>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ModuleDefinition } from '../../../types/index.ts';
|
|
2
|
+
import { UseActions } from '../actions/types.ts';
|
|
3
|
+
import { UseDispatchers } from '../dispatchers/types.ts';
|
|
4
|
+
import { UseOptions } from '../types.ts';
|
|
5
|
+
import { default as useController } from './index.ts';
|
|
6
|
+
export type Props<M extends ModuleDefinition> = {
|
|
7
|
+
actions: UseActions<M>;
|
|
8
|
+
options: UseOptions<M>;
|
|
9
|
+
dispatchers: UseDispatchers;
|
|
10
|
+
};
|
|
11
|
+
export type UseController = ReturnType<typeof useController>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ModuleDefinition, Task } from '../../../types/index.ts';
|
|
2
|
+
import { Head, Tail } from '../types.ts';
|
|
3
|
+
import { GeneratorFn, Props } from './types.ts';
|
|
4
|
+
/**
|
|
5
|
+
* @param props {Props<M>}
|
|
6
|
+
* @returns { attach: (action: Head<M["Actions"]>, ƒ: F) => void; dispatch: (action: Head<M["Actions"]>, data: Tail<M["Actions"]>) => void; }
|
|
7
|
+
*/
|
|
8
|
+
export default function useDispatchers<M extends ModuleDefinition>(props: Props<M>): {
|
|
9
|
+
attach<F extends GeneratorFn<M>>(action: Head<M["Actions"]>, ƒ: F): void;
|
|
10
|
+
dispatch(action: Head<M["Actions"]>, data: Tail<M["Actions"]>, task?: Task): void;
|
|
11
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { UseApp } from '../../../app/types.ts';
|
|
2
|
+
import { Produce } from '../../../controller/types.ts';
|
|
3
|
+
import { ModuleDefinition } from '../../../types/index.ts';
|
|
4
|
+
import { UseLogger } from '../logger/types.ts';
|
|
5
|
+
import { UseModel } from '../model/types.ts';
|
|
6
|
+
import { UseMutations } from '../mutations/types.ts';
|
|
7
|
+
import { UseProcess } from '../process/types.ts';
|
|
8
|
+
import { UseQueue } from '../queue/types.ts';
|
|
9
|
+
import { Tail, UseOptions } from '../types.ts';
|
|
10
|
+
import { UseUpdate } from '../update/types.ts';
|
|
11
|
+
import { default as useDispatchers } from './index.ts';
|
|
12
|
+
export type Props<M extends ModuleDefinition> = {
|
|
13
|
+
app: UseApp;
|
|
14
|
+
options: UseOptions<M>;
|
|
15
|
+
update: UseUpdate;
|
|
16
|
+
model: UseModel;
|
|
17
|
+
logger: UseLogger;
|
|
18
|
+
queue: UseQueue;
|
|
19
|
+
mutations: UseMutations;
|
|
20
|
+
process: UseProcess;
|
|
21
|
+
};
|
|
22
|
+
export type UseDispatchers = ReturnType<typeof useDispatchers>;
|
|
23
|
+
export type UseDispatchHandlerProps<M extends ModuleDefinition> = Props<M>;
|
|
24
|
+
export type Fn = (...args: any[]) => void;
|
|
25
|
+
export type GeneratorFn<M extends ModuleDefinition> = (...args: any[]) => Generator<any, Produce<M>, any>;
|
|
26
|
+
export type Context<M extends ModuleDefinition> = {
|
|
27
|
+
app: UseApp;
|
|
28
|
+
task: PromiseWithResolvers<void>;
|
|
29
|
+
process: Symbol;
|
|
30
|
+
ƒ: GeneratorFn<M>;
|
|
31
|
+
abortController: AbortController;
|
|
32
|
+
payload: Tail<M["Actions"]>;
|
|
33
|
+
props: UseDispatchHandlerProps<M>;
|
|
34
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ModuleDefinition, Task } from '../../../types/index.ts';
|
|
2
|
+
import { Head, Tail } from '../types.ts';
|
|
3
|
+
import { GeneratorFn, UseDispatchHandlerProps } from './types.ts';
|
|
4
|
+
/**
|
|
5
|
+
* @param props {UseDispatchHandlerProps<M>}
|
|
6
|
+
* @returns {(name: Head<M["Actions"]>, ƒ: GeneratorFn<M>) => (payload: Tail<M["Actions"]>) => Promise<void>}
|
|
7
|
+
*/
|
|
8
|
+
export declare function useDispatcher<M extends ModuleDefinition>(props: UseDispatchHandlerProps<M>): (_name: Head<M["Actions"]>, ƒ: GeneratorFn<M>) => (task: Task | undefined, payload: Tail<M["Actions"]>) => Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Check if the name is a broadcast event.
|
|
11
|
+
*
|
|
12
|
+
* @param name {string}
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
export declare function isBroadcast(name: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Custom error class for IO errors.
|
|
18
|
+
*
|
|
19
|
+
* @class EventError
|
|
20
|
+
* @extends Error
|
|
21
|
+
* @param type {string} - The type of the error.
|
|
22
|
+
* @param message {string} - The error message.
|
|
23
|
+
*/
|
|
24
|
+
export declare class EventError extends Error {
|
|
25
|
+
#private;
|
|
26
|
+
constructor(type: string, message?: null | string);
|
|
27
|
+
get type(): string;
|
|
28
|
+
get message(): string;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ModuleDefinition } from '../../../types/index.ts';
|
|
2
|
+
import { UseDispatchers } from '../dispatchers/types.ts';
|
|
3
|
+
import { UseElements } from '../elements/types.ts';
|
|
4
|
+
import { UseOptions } from '../types.ts';
|
|
5
|
+
import { default as useLifecycles } from './index.ts';
|
|
6
|
+
export type Props<M extends ModuleDefinition> = {
|
|
7
|
+
options: UseOptions<M>;
|
|
8
|
+
elements: UseElements;
|
|
9
|
+
dispatchers: UseDispatchers;
|
|
10
|
+
};
|
|
11
|
+
export type UseLifecycles = ReturnType<typeof useLifecycles>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ModuleDefinition } from '../../../types/index.ts';
|
|
2
|
+
import { UseElements } from '../elements/types.ts';
|
|
3
|
+
import { UseOptions } from '../types.ts';
|
|
4
|
+
import { default as useLogger } from './index.ts';
|
|
5
|
+
export type Props<M extends ModuleDefinition> = {
|
|
6
|
+
options: UseOptions<M>;
|
|
7
|
+
elements: UseElements;
|
|
8
|
+
};
|
|
9
|
+
export type UseLogger = ReturnType<typeof useLogger>;
|
|
10
|
+
export type Metrics = {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { default as useModel } from '.';
|
|
2
|
+
import { ModuleDefinition } from '../../../types/index.ts';
|
|
3
|
+
import { UseOptions } from '../types.ts';
|
|
4
|
+
export type Props<M extends ModuleDefinition> = {
|
|
5
|
+
options: UseOptions<M>;
|
|
6
|
+
};
|
|
7
|
+
export type UseModel = ReturnType<typeof useModel>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { State } from '../../../types/index.ts';
|
|
2
|
+
import { Process } from '../process/types.ts';
|
|
3
|
+
import { default as useMutations } from './index.ts';
|
|
4
|
+
export type UseMutations = ReturnType<typeof useMutations>;
|
|
5
|
+
export type Mutation<T> = {
|
|
6
|
+
key: null | symbol | string;
|
|
7
|
+
value: T;
|
|
8
|
+
state: State;
|
|
9
|
+
process: Process;
|
|
10
|
+
};
|
|
11
|
+
export type Mutations = Set<Mutation<unknown>>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ModuleDefinition } from '../../types/index.ts';
|
|
2
|
+
import { Options } from '../types.ts';
|
|
3
|
+
export type ElementName = string;
|
|
4
|
+
export type Props<M extends ModuleDefinition> = {
|
|
5
|
+
options: UseOptions<M>;
|
|
6
|
+
};
|
|
7
|
+
export type UseOptions<M extends ModuleDefinition> = Options<M> & {
|
|
8
|
+
name: ElementName;
|
|
9
|
+
props: M["Props"];
|
|
10
|
+
};
|
|
11
|
+
export type Head<T extends any[]> = T extends [infer First, ...any[]] ? First : never;
|
|
12
|
+
export type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ControllerDefinition } from '../controller/types.ts';
|
|
2
|
+
import { ModuleDefinition } from '../types/index.ts';
|
|
3
|
+
import { ViewDefinition } from '../view/types.ts';
|
|
4
|
+
export type ElementName = string;
|
|
5
|
+
export type Options<M extends ModuleDefinition> = {
|
|
6
|
+
model: M["Model"];
|
|
7
|
+
view: ViewDefinition<M>;
|
|
8
|
+
controller: ControllerDefinition<M>;
|
|
9
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Head } from '../module/renderer/types.ts';
|
|
2
|
+
export declare const enum Transmit {
|
|
3
|
+
Unicast = "unicast",
|
|
4
|
+
Multicast = "multicast",
|
|
5
|
+
Broadcast = "broadcast"
|
|
6
|
+
}
|
|
7
|
+
export declare const enum State {
|
|
8
|
+
Idle = 0,
|
|
9
|
+
Pending = 1,
|
|
10
|
+
Adding = 2,
|
|
11
|
+
Removing = 4,
|
|
12
|
+
Updating = 8,
|
|
13
|
+
Moving = 16
|
|
14
|
+
}
|
|
15
|
+
export type ActionName = Lifecycle | string | number;
|
|
16
|
+
type ActionPayload = [any, ...any[]];
|
|
17
|
+
export type Actions = [ActionName] | [ActionName, ...ActionPayload];
|
|
18
|
+
export type Model = Record<string, any>;
|
|
19
|
+
export type Parameters = undefined | string;
|
|
20
|
+
export declare const enum Lifecycle {
|
|
21
|
+
Mount = "lifecycle/mount",
|
|
22
|
+
Tree = "lifecycle/tree",
|
|
23
|
+
Derive = "lifecycle/derive",
|
|
24
|
+
Error = "distributed/lifecycle/error",
|
|
25
|
+
Unmount = "lifecycle/unmount"
|
|
26
|
+
}
|
|
27
|
+
export type Name<A extends Actions> = A[0];
|
|
28
|
+
export type Props = Record<string, unknown>;
|
|
29
|
+
export type Module<M extends Model, A extends Actions, P extends Props = {}> = {
|
|
30
|
+
Model: M;
|
|
31
|
+
Actions: A;
|
|
32
|
+
Props: P;
|
|
33
|
+
};
|
|
34
|
+
export type ModuleDefinition = {
|
|
35
|
+
Model: Model;
|
|
36
|
+
Actions: Actions;
|
|
37
|
+
Props: Props;
|
|
38
|
+
};
|
|
39
|
+
type Fns<P extends Props> = {
|
|
40
|
+
[K in keyof P]: P[K] extends (...args: any[]) => any ? P[K] : never;
|
|
41
|
+
};
|
|
42
|
+
type NonFns<P extends Props> = {
|
|
43
|
+
[K in keyof P]: P[K] extends (...args: any[]) => any ? never : P[K];
|
|
44
|
+
};
|
|
45
|
+
export type Events<P extends Props> = {
|
|
46
|
+
[K in keyof Fns<P> as Fns<P>[K] extends never ? never : K]: Fns<P>[K];
|
|
47
|
+
};
|
|
48
|
+
export type Values<P extends Props> = {
|
|
49
|
+
[K in keyof NonFns<P> as NonFns<P>[K] extends never ? never : K]: NonFns<P>[K];
|
|
50
|
+
};
|
|
51
|
+
export type Pk<T> = undefined | Symbol | T;
|
|
52
|
+
export type Queue<A extends ModuleDefinition["Actions"]> = {
|
|
53
|
+
event: Head<A>;
|
|
54
|
+
actions: {
|
|
55
|
+
abort: AbortController["abort"];
|
|
56
|
+
};
|
|
57
|
+
}[];
|
|
58
|
+
export type Task = PromiseWithResolvers<void>;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { EventError } from '../module/renderer/dispatchers/utils.ts';
|
|
2
|
+
import { Pk } from '../types/index.ts';
|
|
3
|
+
export { default as sleep } from './sleep/index.ts';
|
|
4
|
+
export { default as maybe } from './maybe/index.ts';
|
|
5
|
+
export declare function pk<T>(): Symbol;
|
|
6
|
+
export declare function pk<T>(id: Pk<T>): boolean;
|
|
7
|
+
export declare function isEventError(error: Error | EventError): error is EventError;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type Otherwise = null | undefined | Error;
|
|
2
|
+
type Value<T> = Exclude<T, Otherwise>;
|
|
3
|
+
export default class Maybe<T> {
|
|
4
|
+
#private;
|
|
5
|
+
static of<T>(value: T): Maybe<T>;
|
|
6
|
+
constructor(value: T);
|
|
7
|
+
map<U>(unwrap?: never, otherwise?: never): T;
|
|
8
|
+
map<U>(unwrap?: (value: Value<T>) => U, otherwise?: never): T | U;
|
|
9
|
+
map<U, V>(unwrap?: (value: Value<T>) => U, otherwise?: (value: Otherwise) => V): U | V;
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Mutations } from '../../module/renderer/mutations/types.ts';
|
|
2
|
+
import { Process } from '../../module/renderer/process/types.ts';
|
|
3
|
+
import { ModuleDefinition, State } from '../../types/index.ts';
|
|
4
|
+
import { Validator } from '../../view/types.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Create a placeholder for a value that is being processed.
|
|
7
|
+
*
|
|
8
|
+
* @param value {T}
|
|
9
|
+
* @param state {State}
|
|
10
|
+
* @param process {null | Process}
|
|
11
|
+
* @returns {T}
|
|
12
|
+
*/
|
|
13
|
+
export declare function placeholder<T>(value: T, state: State, process: null | Process): T;
|
|
14
|
+
/**
|
|
15
|
+
* Observe a model for changes and track mutations to determine the state of a mutation.
|
|
16
|
+
*
|
|
17
|
+
* @param model {M}
|
|
18
|
+
* @param mutations {Mutations}
|
|
19
|
+
* @returns {M}
|
|
20
|
+
*/
|
|
21
|
+
export declare function observe<M extends ModuleDefinition["Model"]>(model: M, mutations: Mutations): M;
|
|
22
|
+
/**
|
|
23
|
+
* Validate a model against mutations to determine the state of a mutation and provide
|
|
24
|
+
* immediate feedback in the form of optimistic updates.
|
|
25
|
+
*
|
|
26
|
+
* @param model {M}
|
|
27
|
+
* @param mutations {Mutations}
|
|
28
|
+
* @returns {Validator<M>}
|
|
29
|
+
*/
|
|
30
|
+
export declare function validate<M extends ModuleDefinition["Model"]>(model: M, mutations: Mutations): Validator<M>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Process } from '../../module/renderer/process/types.ts';
|
|
2
|
+
import { State } from '../../types/index.ts';
|
|
3
|
+
export declare const unwrap: unique symbol;
|
|
4
|
+
export declare class Placeholder<T> {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(primitive: T, state: State, process: null | Process);
|
|
7
|
+
value(): T;
|
|
8
|
+
state(): State;
|
|
9
|
+
process(): null | Process;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function sleep(ms: number): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Events, Model, ModuleDefinition, State } from '../types/index.ts';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export type Validator<M extends Model> = {
|
|
4
|
+
[P in keyof M]: Validator<M[P]> & {
|
|
5
|
+
is(state: State): boolean;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export type ViewActions<M extends ModuleDefinition> = {
|
|
9
|
+
dispatch(event: M["Actions"]): Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export type ViewArgs<M extends ModuleDefinition> = Readonly<{
|
|
12
|
+
model: Readonly<M["Model"]>;
|
|
13
|
+
events: Readonly<Events<M["Props"]>>;
|
|
14
|
+
validate: Readonly<Validator<M["Model"]>>;
|
|
15
|
+
actions: Readonly<ViewActions<M>>;
|
|
16
|
+
}>;
|
|
17
|
+
export type ViewDefinition<M extends ModuleDefinition> = (actions: ViewArgs<M>) => React.ReactNode;
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chizu",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/chizu.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/chizu.js",
|
|
11
|
+
"require": "./dist/chizu.umd.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"eventemitter3": "^5.0.1",
|
|
16
|
+
"react": "^19.0.0",
|
|
17
|
+
"react-dom": "^19.0.0"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@babel/preset-env": "^7.26.0",
|
|
24
|
+
"@babel/preset-react": "^7.26.3",
|
|
25
|
+
"@babel/preset-typescript": "^7.26.0",
|
|
26
|
+
"@emotion/css": "^11.13.5",
|
|
27
|
+
"@jest/globals": "^29.7.0",
|
|
28
|
+
"@playwright/test": "^1.51.1",
|
|
29
|
+
"@testing-library/dom": "^10.4.0",
|
|
30
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
31
|
+
"@testing-library/react": "^16.2.0",
|
|
32
|
+
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
|
33
|
+
"@types/pubsub-js": "^1.8.6",
|
|
34
|
+
"@types/react": "^19.0.8",
|
|
35
|
+
"@types/react-dom": "^19.0.3",
|
|
36
|
+
"dayjs": "^1.11.13",
|
|
37
|
+
"dexie": "^4.0.11",
|
|
38
|
+
"get-port-cli": "^3.0.0",
|
|
39
|
+
"jest": "^29.7.0",
|
|
40
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
41
|
+
"lucide-react": "^0.503.0",
|
|
42
|
+
"playwright": "^1.51.1",
|
|
43
|
+
"prettier": "^3.4.2",
|
|
44
|
+
"react-test-renderer": "^19.0.0",
|
|
45
|
+
"rollup-plugin-visualizer": "^5.14.0",
|
|
46
|
+
"ts-jest": "^29.2.6",
|
|
47
|
+
"ts-node": "^10.9.2",
|
|
48
|
+
"typescript": "~5.7.3",
|
|
49
|
+
"vite": "^6.0.5",
|
|
50
|
+
"vite-plugin-dts": "^4.5.3",
|
|
51
|
+
"wait-on": "^8.0.3"
|
|
52
|
+
}
|
|
53
|
+
}
|