@e280/strata 0.0.0-6 → 0.0.0-8
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 +80 -56
- package/package.json +5 -5
- package/s/signals/index.ts +2 -1
- package/s/signals/parts/derive.ts +29 -0
- package/s/signals/parts/effect.ts +3 -3
- package/s/signals/parts/lazy.ts +27 -0
- package/s/signals/parts/signal.ts +21 -43
- package/s/signals/parts/types.ts +11 -0
- package/s/signals/parts/units.ts +150 -0
- package/s/signals/signals.test.ts +103 -8
- package/s/tests.test.ts +1 -1
- package/s/tree/index.ts +1 -1
- package/s/tree/parts/branch.ts +17 -40
- package/s/tree/parts/chronobranch.ts +13 -13
- package/s/tree/parts/trunk.ts +27 -29
- package/s/tree/parts/types.ts +18 -6
- package/s/tree/parts/utils/setup.ts +8 -8
- package/s/tree/tree.test.ts +77 -68
- package/x/signals/index.d.ts +2 -1
- package/x/signals/index.js +2 -1
- package/x/signals/index.js.map +1 -1
- package/x/signals/parts/derive.d.ts +12 -0
- package/x/signals/parts/derive.js +12 -0
- package/x/signals/parts/derive.js.map +1 -0
- package/x/signals/parts/effect.d.ts +2 -2
- package/x/signals/parts/effect.js +2 -2
- package/x/signals/parts/effect.js.map +1 -1
- package/x/signals/parts/lazy.d.ts +10 -0
- package/x/signals/parts/lazy.js +12 -0
- package/x/signals/parts/lazy.js.map +1 -0
- package/x/signals/parts/signal.d.ts +11 -8
- package/x/signals/parts/signal.js +8 -37
- package/x/signals/parts/signal.js.map +1 -1
- package/x/signals/parts/types.d.ts +7 -0
- package/x/signals/parts/types.js +2 -0
- package/x/signals/parts/types.js.map +1 -0
- package/x/signals/parts/units.d.ts +43 -0
- package/x/signals/parts/units.js +131 -0
- package/x/signals/parts/units.js.map +1 -0
- package/x/signals/signals.test.d.ts +8 -3
- package/x/signals/signals.test.js +87 -8
- package/x/signals/signals.test.js.map +1 -1
- package/x/tests.test.js +1 -1
- package/x/tests.test.js.map +1 -1
- package/x/tree/index.d.ts +1 -1
- package/x/tree/index.js +1 -1
- package/x/tree/index.js.map +1 -1
- package/x/tree/parts/branch.d.ts +4 -7
- package/x/tree/parts/branch.js +11 -30
- package/x/tree/parts/branch.js.map +1 -1
- package/x/tree/parts/chronobranch.d.ts +4 -4
- package/x/tree/parts/chronobranch.js +12 -12
- package/x/tree/parts/chronobranch.js.map +1 -1
- package/x/tree/parts/trunk.d.ts +5 -5
- package/x/tree/parts/trunk.js +18 -29
- package/x/tree/parts/trunk.js.map +1 -1
- package/x/tree/parts/types.d.ts +12 -6
- package/x/tree/parts/utils/setup.d.ts +3 -3
- package/x/tree/parts/utils/setup.js +6 -6
- package/x/tree/parts/utils/setup.js.map +1 -1
- package/x/tree/tree.test.d.ts +7 -7
- package/x/tree/tree.test.js +76 -68
- package/x/tree/tree.test.js.map +1 -1
- package/s/signals/parts/computed.ts +0 -53
- package/x/signals/parts/computed.d.ts +0 -14
- package/x/signals/parts/computed.js +0 -41
- package/x/signals/parts/computed.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
<br/>
|
|
5
5
|
|
|
6
6
|
# ⛏️ strata
|
|
7
|
-
📦 `npm install @e280/strata`
|
|
8
|
-
probably my tenth state management library, lol
|
|
9
|
-
|
|
10
|
-
<br/>
|
|
11
7
|
|
|
12
8
|
### get in loser, we're managing state
|
|
9
|
+
📦 `npm install @e280/strata`
|
|
10
|
+
🧙♂️ probably my tenth state management library, lol
|
|
11
|
+
💁 it's all about rerendering ui when data changes
|
|
12
|
+
|
|
13
13
|
🚦 **signals** — ephemeral view-level state
|
|
14
14
|
🌳 **tree** — persistent app-level state
|
|
15
15
|
🪄 **tracker** — reactivity integration hub
|
|
@@ -23,70 +23,91 @@
|
|
|
23
23
|
<br/>
|
|
24
24
|
|
|
25
25
|
## 🚦 signals — *ephemeral view-level state*
|
|
26
|
-
|
|
26
|
+
```ts
|
|
27
|
+
import {signal, effect, computed} from "@e280/strata"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### each signal holds a value
|
|
31
|
+
- **create a signal**
|
|
27
32
|
```ts
|
|
28
|
-
|
|
33
|
+
const count = signal(0)
|
|
29
34
|
```
|
|
30
|
-
- **
|
|
35
|
+
- **read a signal**
|
|
31
36
|
```ts
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
count() // 0
|
|
38
|
+
```
|
|
39
|
+
- **set a signal**
|
|
40
|
+
```ts
|
|
41
|
+
count(1)
|
|
42
|
+
```
|
|
43
|
+
- **set a signal, and await effect propagation**
|
|
44
|
+
```ts
|
|
45
|
+
await count(2)
|
|
46
|
+
```
|
|
38
47
|
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
### pick your poison
|
|
49
|
+
- **signals hipster fn syntax**
|
|
50
|
+
```ts
|
|
51
|
+
count() // get
|
|
52
|
+
await count(2) // set
|
|
53
|
+
```
|
|
54
|
+
> to achieve this hipster syntax i had to make the implementation so damn cursed, lol 💀
|
|
55
|
+
- **signals get/set syntax**
|
|
56
|
+
```ts
|
|
57
|
+
count.get() // get
|
|
58
|
+
await count.set(2) // set
|
|
59
|
+
```
|
|
60
|
+
- **signals .value accessor syntax**
|
|
61
|
+
```ts
|
|
62
|
+
count.value // get
|
|
63
|
+
count.value = 2 // set
|
|
41
64
|
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- three ways to set it
|
|
51
|
-
```ts
|
|
52
|
-
await count(2)
|
|
53
|
-
await count.set(2)
|
|
54
|
-
count.value = 2
|
|
55
|
-
```
|
|
56
|
-
- using `await` here allows you to wait for downstream effects to finish
|
|
57
|
-
- **effects are run when the relevant signals change**
|
|
65
|
+
value pattern is nice for this vibe
|
|
66
|
+
```ts
|
|
67
|
+
count.value++
|
|
68
|
+
count.value += 1
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### effects
|
|
72
|
+
- **effects run when the relevant signals change**
|
|
58
73
|
```ts
|
|
59
74
|
effect(() => console.log(count()))
|
|
60
|
-
//
|
|
75
|
+
// 1
|
|
76
|
+
// the system detects 'count' is relevant
|
|
61
77
|
|
|
62
78
|
count.value++
|
|
63
|
-
//
|
|
79
|
+
// 2
|
|
80
|
+
// when count is changed, the effect fn is run
|
|
64
81
|
```
|
|
65
|
-
|
|
82
|
+
|
|
83
|
+
### `signal.derive` and `signal.lazy` are computed signals
|
|
84
|
+
- **signal.derive**
|
|
85
|
+
is for combining signals
|
|
66
86
|
```ts
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
})
|
|
87
|
+
const a = signal(1)
|
|
88
|
+
const b = signal(10)
|
|
89
|
+
const product = signal.derive(() => a() * b())
|
|
71
90
|
|
|
72
|
-
|
|
73
|
-
// "recomputed!"
|
|
74
|
-
// 30
|
|
91
|
+
product() // 10
|
|
75
92
|
|
|
76
|
-
|
|
93
|
+
// change a dependency,
|
|
94
|
+
// and the derived signal is automatically updated
|
|
95
|
+
await a.set(2)
|
|
77
96
|
|
|
78
|
-
|
|
79
|
-
// "recomputed!"
|
|
80
|
-
// 40
|
|
97
|
+
product() // 20
|
|
81
98
|
```
|
|
99
|
+
- **signal.lazy**
|
|
100
|
+
is for making special optimizations.
|
|
101
|
+
it's like derive, except it cannot trigger effects,
|
|
102
|
+
because it's so lazy it only computes the value on read, and only when necessary.
|
|
103
|
+
> ⚠️ *i repeat: lazy signals cannot trigger effects!*
|
|
82
104
|
|
|
83
105
|
<br/>
|
|
84
106
|
|
|
85
107
|
## 🌳 tree — *persistent app-level state*
|
|
86
|
-
- `@e280/strata/tree`
|
|
87
108
|
- single-source-of-truth state tree
|
|
88
109
|
- immutable except for `mutate(fn)` calls
|
|
89
|
-
-
|
|
110
|
+
- localStorage persistence, cross-tab sync, undo/redo history
|
|
90
111
|
- no spooky-dookie proxy magic — just god's honest javascript
|
|
91
112
|
|
|
92
113
|
#### `Trunk` is your app's state tree root
|
|
@@ -131,28 +152,29 @@
|
|
|
131
152
|
```
|
|
132
153
|
- you can branch a branch
|
|
133
154
|
|
|
134
|
-
#### watch for mutations
|
|
155
|
+
#### `on` to watch for mutations
|
|
135
156
|
- on the trunk, we can listen deeply for mutations within the whole tree
|
|
136
157
|
```ts
|
|
137
|
-
trunk.
|
|
158
|
+
trunk.on(s => console.log(s.count))
|
|
138
159
|
```
|
|
139
160
|
- whereas branch listeners don't care about changes outside their scope
|
|
140
161
|
```ts
|
|
141
|
-
snacks.
|
|
162
|
+
snacks.on(s => console.log(s.peanuts))
|
|
142
163
|
```
|
|
143
|
-
-
|
|
164
|
+
- on returns a fn to stop listening
|
|
144
165
|
```ts
|
|
145
|
-
const stop = trunk.
|
|
166
|
+
const stop = trunk.on(s => console.log(s.count))
|
|
146
167
|
stop() // stop listening
|
|
147
168
|
```
|
|
148
169
|
|
|
149
170
|
### only discerning high-class aristocrats are permitted beyond this point
|
|
150
171
|
|
|
151
172
|
#### `Trunk.setup` for localStorage persistence etc
|
|
173
|
+
- it automatically handles persistence to localStorage and cross-tab synchronization
|
|
152
174
|
- simple setup
|
|
153
175
|
```ts
|
|
154
176
|
const {trunk} = await Trunk.setup({
|
|
155
|
-
version: 1, // 👈 bump whenever
|
|
177
|
+
version: 1, // 👈 bump whenever you change state schema!
|
|
156
178
|
initialState: {count: 0},
|
|
157
179
|
})
|
|
158
180
|
```
|
|
@@ -186,7 +208,7 @@
|
|
|
186
208
|
})
|
|
187
209
|
```
|
|
188
210
|
- *big-brain moment:* the whole chronicle *itself* is stored in the state.. serializable.. think persistence — user can close their project, reopen, and their undo/redo history is still chillin' — *brat girl summer*
|
|
189
|
-
- second, make a `Chronobranch` which is like a branch
|
|
211
|
+
- second, make a `Chronobranch` which is like a branch, but is concerned with history
|
|
190
212
|
```ts
|
|
191
213
|
const snacks = trunk.chronobranch(64, s => s.snacks)
|
|
192
214
|
// \
|
|
@@ -207,13 +229,15 @@
|
|
|
207
229
|
snacks.undoable // 2
|
|
208
230
|
snacks.redoable // 1
|
|
209
231
|
```
|
|
210
|
-
- chronobranch can have its own
|
|
232
|
+
- chronobranch can have its own branches — all their mutations advance history
|
|
211
233
|
- plz pinky-swear right now, that you won't create a chronobranch under a branch under another chronobranch 💀
|
|
212
234
|
|
|
213
235
|
<br/>
|
|
214
236
|
|
|
215
237
|
## 🪄 tracker — integrations
|
|
216
|
-
-
|
|
238
|
+
- ```ts
|
|
239
|
+
import {tracker} from "@e280/strata/tracker"
|
|
240
|
+
```
|
|
217
241
|
- all reactivity is orchestrated by the `tracker`
|
|
218
242
|
- if you are integrating a new state object, or a new view layer that needs to react to state changes, just read [tracker.ts](./s/tracker/tracker.ts)
|
|
219
243
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e280/strata",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-8",
|
|
4
4
|
"description": "state management",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chase Moskal <chasemoskal@gmail.com>",
|
|
@@ -29,9 +29,12 @@
|
|
|
29
29
|
"_tsc": "tsc",
|
|
30
30
|
"_tscw": "tsc -w"
|
|
31
31
|
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@e280/stz": "^0.0.0-28"
|
|
34
|
+
},
|
|
32
35
|
"devDependencies": {
|
|
33
36
|
"@e280/science": "^0.0.5",
|
|
34
|
-
"@types/node": "^24.0.
|
|
37
|
+
"@types/node": "^24.0.10",
|
|
35
38
|
"npm-run-all": "^4.1.5",
|
|
36
39
|
"typescript": "^5.8.3"
|
|
37
40
|
},
|
|
@@ -46,8 +49,5 @@
|
|
|
46
49
|
},
|
|
47
50
|
"bugs": {
|
|
48
51
|
"url": "https://github.com/e280/strata/issues"
|
|
49
|
-
},
|
|
50
|
-
"dependencies": {
|
|
51
|
-
"@e280/stz": "^0.0.0-27"
|
|
52
52
|
}
|
|
53
53
|
}
|
package/s/signals/index.ts
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
import {Sub} from "@e280/stz"
|
|
3
|
+
import {SignalOptions} from "./types.js"
|
|
4
|
+
import {DerivedCore, processSignalOptions} from "./units.js"
|
|
5
|
+
|
|
6
|
+
export type DerivedSignal<V> = {
|
|
7
|
+
(): V
|
|
8
|
+
kind: "derived"
|
|
9
|
+
|
|
10
|
+
sneak: V
|
|
11
|
+
on: Sub<[V]>
|
|
12
|
+
get(): V
|
|
13
|
+
get value(): V
|
|
14
|
+
dispose(): void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function derive<V>(formula: () => V, options: Partial<SignalOptions> = {}) {
|
|
18
|
+
function fn(): V {
|
|
19
|
+
return (fn as any).value
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const o = processSignalOptions(options)
|
|
23
|
+
const core = DerivedCore.make<V>(fn as any, formula, o)
|
|
24
|
+
Object.setPrototypeOf(fn, DerivedCore.prototype)
|
|
25
|
+
Object.assign(fn, core)
|
|
26
|
+
|
|
27
|
+
return fn as DerivedSignal<V>
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {debounce} from "@e280/stz"
|
|
3
3
|
import {tracker} from "../../tracker/tracker.js"
|
|
4
4
|
|
|
5
|
-
export function effect
|
|
6
|
-
return
|
|
5
|
+
export function effect(collector: () => void, responder: () => void = collector) {
|
|
6
|
+
return collectorEffect(collector, responder).dispose
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function
|
|
9
|
+
export function collectorEffect<C = void>(collector: () => C, responder: () => void = collector) {
|
|
10
10
|
const {seen, result} = tracker.seen(collector)
|
|
11
11
|
const fn = debounce(0, responder)
|
|
12
12
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
import {SignalOptions} from "./types.js"
|
|
3
|
+
import {LazyCore, processSignalOptions} from "./units.js"
|
|
4
|
+
|
|
5
|
+
export type LazySignal<V> = {
|
|
6
|
+
(): V
|
|
7
|
+
kind: "lazy"
|
|
8
|
+
|
|
9
|
+
sneak: V
|
|
10
|
+
get(): V
|
|
11
|
+
get value(): V
|
|
12
|
+
dispose(): void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function lazy<V>(formula: () => V, options: Partial<SignalOptions> = {}) {
|
|
16
|
+
function fn(): V {
|
|
17
|
+
return (fn as any).value
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const o = processSignalOptions(options)
|
|
21
|
+
const core = new LazyCore<V>(formula, o)
|
|
22
|
+
Object.setPrototypeOf(fn, LazyCore.prototype)
|
|
23
|
+
Object.assign(fn, core)
|
|
24
|
+
|
|
25
|
+
return fn as LazySignal<V>
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {Sub} from "@e280/stz"
|
|
3
|
+
|
|
4
|
+
import {lazy} from "./lazy.js"
|
|
5
|
+
import {derive} from "./derive.js"
|
|
6
|
+
import {SignalOptions} from "./types.js"
|
|
7
|
+
import {processSignalOptions, SignalCore} from "./units.js"
|
|
4
8
|
|
|
5
9
|
export type Signal<V> = {
|
|
6
10
|
(): V
|
|
7
11
|
(v: V): Promise<void>
|
|
8
12
|
(v?: V): V | Promise<void>
|
|
9
|
-
} & SignalCore<V>
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
const core = new SignalCore(value)
|
|
14
|
+
kind: "signal"
|
|
13
15
|
|
|
16
|
+
sneak: V
|
|
17
|
+
value: V
|
|
18
|
+
on: Sub<[V]>
|
|
19
|
+
get(): V
|
|
20
|
+
set(v: V): Promise<void>
|
|
21
|
+
publish(v?: V): Promise<void>
|
|
22
|
+
dispose(): void
|
|
23
|
+
} & SignalCore<V>
|
|
24
|
+
|
|
25
|
+
export function signal<V>(value: V, options: Partial<SignalOptions> = {}) {
|
|
14
26
|
function fn(): V
|
|
15
27
|
function fn(v: V): Promise<void>
|
|
16
28
|
function fn(v?: V): V | Promise<void> {
|
|
@@ -19,48 +31,14 @@ export function signal<V>(value: V) {
|
|
|
19
31
|
: (fn as any).get()
|
|
20
32
|
}
|
|
21
33
|
|
|
34
|
+
const o = processSignalOptions(options)
|
|
35
|
+
const core = new SignalCore(value, o)
|
|
22
36
|
Object.setPrototypeOf(fn, SignalCore.prototype)
|
|
23
37
|
Object.assign(fn, core)
|
|
24
38
|
|
|
25
39
|
return fn as Signal<V>
|
|
26
40
|
}
|
|
27
41
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
published: Promise<V>
|
|
31
|
-
|
|
32
|
-
constructor(public sneak: V) {
|
|
33
|
-
this.published = Promise.resolve(sneak)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
get() {
|
|
37
|
-
tracker.see(this)
|
|
38
|
-
return this.sneak
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async set(v: V) {
|
|
42
|
-
if (v !== this.sneak)
|
|
43
|
-
await this.publish(v)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
get value() {
|
|
47
|
-
return this.get()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
set value(v: V) {
|
|
51
|
-
this.set(v)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async publish(v = this.get()) {
|
|
55
|
-
this.sneak = v
|
|
56
|
-
await Promise.all([
|
|
57
|
-
tracker.change(this),
|
|
58
|
-
this.published = this.on.pub(v).then(() => v),
|
|
59
|
-
])
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
dispose() {
|
|
63
|
-
this.on.clear()
|
|
64
|
-
}
|
|
65
|
-
}
|
|
42
|
+
signal.lazy = lazy
|
|
43
|
+
signal.derive = derive
|
|
66
44
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
import {Signal} from "./signal.js"
|
|
3
|
+
import {LazySignal} from "./lazy.js"
|
|
4
|
+
import {DerivedSignal} from "./derive.js"
|
|
5
|
+
|
|
6
|
+
export type Signaloid<V> = Signal<V> | DerivedSignal<V> | LazySignal<V>
|
|
7
|
+
|
|
8
|
+
export type SignalOptions = {
|
|
9
|
+
compare: (a: any, b: any) => boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
|
|
2
|
+
import {sub} from "@e280/stz"
|
|
3
|
+
import {SignalOptions} from "./types.js"
|
|
4
|
+
import {collectorEffect} from "./effect.js"
|
|
5
|
+
import {tracker} from "../../tracker/tracker.js"
|
|
6
|
+
|
|
7
|
+
const defaultSignalOptions: SignalOptions = {
|
|
8
|
+
compare: (a, b) => a === b
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function processSignalOptions(options: Partial<SignalOptions> = {}) {
|
|
12
|
+
return {...defaultSignalOptions, ...options}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class ReadableSignal<V> {
|
|
16
|
+
constructor(public sneak: V) {}
|
|
17
|
+
|
|
18
|
+
get() {
|
|
19
|
+
tracker.see(this)
|
|
20
|
+
return this.sneak
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get value() {
|
|
24
|
+
return this.get()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class ReactiveSignal<V> extends ReadableSignal<V> {
|
|
29
|
+
on = sub<[V]>()
|
|
30
|
+
|
|
31
|
+
dispose() {
|
|
32
|
+
this.on.clear()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class SignalCore<V> extends ReactiveSignal<V> {
|
|
37
|
+
kind: "signal" = "signal"
|
|
38
|
+
_lock = false
|
|
39
|
+
|
|
40
|
+
constructor(sneak: V, public _options: SignalOptions) {
|
|
41
|
+
super(sneak)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async set(v: V) {
|
|
45
|
+
const isChanged = !this._options.compare(this.sneak, v)
|
|
46
|
+
if (isChanged)
|
|
47
|
+
await this.publish(v)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get value() {
|
|
51
|
+
return this.get()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
set value(v: V) {
|
|
55
|
+
this.set(v)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async publish(v = this.get()) {
|
|
59
|
+
if (this._lock)
|
|
60
|
+
throw new Error("forbid circularity")
|
|
61
|
+
|
|
62
|
+
let promise = Promise.resolve()
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
this._lock = true
|
|
66
|
+
this.sneak = v
|
|
67
|
+
promise = Promise.all([
|
|
68
|
+
tracker.change(this),
|
|
69
|
+
this.on.pub(v),
|
|
70
|
+
]) as any
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
this._lock = false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return promise
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class LazyCore<V> extends ReadableSignal<V> {
|
|
81
|
+
kind: "lazy" = "lazy"
|
|
82
|
+
|
|
83
|
+
_dirty = false
|
|
84
|
+
_effect: (() => void) | undefined
|
|
85
|
+
|
|
86
|
+
constructor(public _formula: () => V, public _options: SignalOptions) {
|
|
87
|
+
super(undefined as any)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get() {
|
|
91
|
+
if (!this._effect) {
|
|
92
|
+
const {result, dispose} = collectorEffect(this._formula, () => this._dirty = true)
|
|
93
|
+
this._effect = dispose
|
|
94
|
+
this.sneak = result
|
|
95
|
+
}
|
|
96
|
+
if (this._dirty) {
|
|
97
|
+
this._dirty = false
|
|
98
|
+
|
|
99
|
+
const v = this._formula()
|
|
100
|
+
const isChanged = !this._options.compare(this.sneak, v)
|
|
101
|
+
if (isChanged) {
|
|
102
|
+
this.sneak = v
|
|
103
|
+
tracker.change(this)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return super.get()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get value() {
|
|
110
|
+
return this.get()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
dispose() {
|
|
114
|
+
if (this._effect)
|
|
115
|
+
this._effect()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export class DerivedCore<V> extends ReactiveSignal<V> {
|
|
120
|
+
static make<V>(that: DerivedCore<V>, formula: () => V, options: SignalOptions) {
|
|
121
|
+
const {result, dispose} = collectorEffect(formula, async() => {
|
|
122
|
+
const value = formula()
|
|
123
|
+
const isChanged = !options.compare(that.sneak, value)
|
|
124
|
+
if (isChanged) {
|
|
125
|
+
that.sneak = value
|
|
126
|
+
await Promise.all([
|
|
127
|
+
tracker.change(that),
|
|
128
|
+
that.on.pub(value),
|
|
129
|
+
])
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
return new this(result, dispose)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
kind: "derived" = "derived"
|
|
136
|
+
|
|
137
|
+
constructor(initialValue: V, public _effect: () => void) {
|
|
138
|
+
super(initialValue)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get value() {
|
|
142
|
+
return this.get()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
dispose() {
|
|
146
|
+
super.dispose()
|
|
147
|
+
this._effect()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|