@e280/strata 0.0.0-7 → 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 +27 -24
- 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 +4 -4
- 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 +2 -2
- package/x/tree/parts/utils/setup.js +1 -1
- 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
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
### get in loser, we're managing state
|
|
9
9
|
📦 `npm install @e280/strata`
|
|
10
10
|
🧙♂️ probably my tenth state management library, lol
|
|
11
|
+
💁 it's all about rerendering ui when data changes
|
|
11
12
|
|
|
12
13
|
🚦 **signals** — ephemeral view-level state
|
|
13
14
|
🌳 **tree** — persistent app-level state
|
|
@@ -43,9 +44,6 @@ import {signal, effect, computed} from "@e280/strata"
|
|
|
43
44
|
```ts
|
|
44
45
|
await count(2)
|
|
45
46
|
```
|
|
46
|
-
- **signals are for auto rerendering your ui.**
|
|
47
|
-
components/views will auto rerender when relevant signals change
|
|
48
|
-
— well only if your ui lib is cool and integrates `tracker`.
|
|
49
47
|
|
|
50
48
|
### pick your poison
|
|
51
49
|
- **signals hipster fn syntax**
|
|
@@ -53,6 +51,7 @@ import {signal, effect, computed} from "@e280/strata"
|
|
|
53
51
|
count() // get
|
|
54
52
|
await count(2) // set
|
|
55
53
|
```
|
|
54
|
+
> to achieve this hipster syntax i had to make the implementation so damn cursed, lol 💀
|
|
56
55
|
- **signals get/set syntax**
|
|
57
56
|
```ts
|
|
58
57
|
count.get() // get
|
|
@@ -80,33 +79,36 @@ import {signal, effect, computed} from "@e280/strata"
|
|
|
80
79
|
// 2
|
|
81
80
|
// when count is changed, the effect fn is run
|
|
82
81
|
```
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
|
|
83
|
+
### `signal.derive` and `signal.lazy` are computed signals
|
|
84
|
+
- **signal.derive**
|
|
85
|
+
is for combining signals
|
|
85
86
|
```ts
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
})
|
|
87
|
+
const a = signal(1)
|
|
88
|
+
const b = signal(10)
|
|
89
|
+
const product = signal.derive(() => a() * b())
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
// "recomputed!"
|
|
93
|
-
// 20
|
|
91
|
+
product() // 10
|
|
94
92
|
|
|
95
|
-
|
|
93
|
+
// change a dependency,
|
|
94
|
+
// and the derived signal is automatically updated
|
|
95
|
+
await a.set(2)
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
// "recomputed!"
|
|
99
|
-
// 30
|
|
97
|
+
product() // 20
|
|
100
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!*
|
|
101
104
|
|
|
102
105
|
<br/>
|
|
103
106
|
|
|
104
107
|
## 🌳 tree — *persistent app-level state*
|
|
105
108
|
- single-source-of-truth state tree
|
|
106
109
|
- immutable except for `mutate(fn)` calls
|
|
107
|
-
-
|
|
110
|
+
- localStorage persistence, cross-tab sync, undo/redo history
|
|
108
111
|
- no spooky-dookie proxy magic — just god's honest javascript
|
|
109
|
-
- separate but compatible with signals
|
|
110
112
|
|
|
111
113
|
#### `Trunk` is your app's state tree root
|
|
112
114
|
- better stick to json-friendly serializable data
|
|
@@ -150,28 +152,29 @@ import {signal, effect, computed} from "@e280/strata"
|
|
|
150
152
|
```
|
|
151
153
|
- you can branch a branch
|
|
152
154
|
|
|
153
|
-
#### watch for mutations
|
|
155
|
+
#### `on` to watch for mutations
|
|
154
156
|
- on the trunk, we can listen deeply for mutations within the whole tree
|
|
155
157
|
```ts
|
|
156
|
-
trunk.
|
|
158
|
+
trunk.on(s => console.log(s.count))
|
|
157
159
|
```
|
|
158
160
|
- whereas branch listeners don't care about changes outside their scope
|
|
159
161
|
```ts
|
|
160
|
-
snacks.
|
|
162
|
+
snacks.on(s => console.log(s.peanuts))
|
|
161
163
|
```
|
|
162
|
-
-
|
|
164
|
+
- on returns a fn to stop listening
|
|
163
165
|
```ts
|
|
164
|
-
const stop = trunk.
|
|
166
|
+
const stop = trunk.on(s => console.log(s.count))
|
|
165
167
|
stop() // stop listening
|
|
166
168
|
```
|
|
167
169
|
|
|
168
170
|
### only discerning high-class aristocrats are permitted beyond this point
|
|
169
171
|
|
|
170
172
|
#### `Trunk.setup` for localStorage persistence etc
|
|
173
|
+
- it automatically handles persistence to localStorage and cross-tab synchronization
|
|
171
174
|
- simple setup
|
|
172
175
|
```ts
|
|
173
176
|
const {trunk} = await Trunk.setup({
|
|
174
|
-
version: 1, // 👈 bump whenever
|
|
177
|
+
version: 1, // 👈 bump whenever you change state schema!
|
|
175
178
|
initialState: {count: 0},
|
|
176
179
|
})
|
|
177
180
|
```
|
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
|
+
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
import {Science, test, expect} from "@e280/science"
|
|
2
|
+
import {Science, test, expect, spy} from "@e280/science"
|
|
3
3
|
|
|
4
|
+
import {lazy} from "./parts/lazy.js"
|
|
4
5
|
import {effect} from "./parts/effect.js"
|
|
5
6
|
import {signal} from "./parts/signal.js"
|
|
6
|
-
import {computed} from "./parts/computed.js"
|
|
7
7
|
|
|
8
8
|
export default Science.suite({
|
|
9
9
|
"signal get/set value": test(async() => {
|
|
@@ -53,6 +53,19 @@ export default Science.suite({
|
|
|
53
53
|
expect(runs).is(1)
|
|
54
54
|
}),
|
|
55
55
|
|
|
56
|
+
"signal on circularity forbidden": test(async() => {
|
|
57
|
+
const count = signal(1)
|
|
58
|
+
let runs = 0
|
|
59
|
+
count.on(async() => {
|
|
60
|
+
await count.set(99)
|
|
61
|
+
runs++
|
|
62
|
+
})
|
|
63
|
+
expect(async() => {
|
|
64
|
+
await count.set(2)
|
|
65
|
+
}).throwsAsync()
|
|
66
|
+
expect(runs).is(0)
|
|
67
|
+
}),
|
|
68
|
+
|
|
56
69
|
"effect tracks signal changes": test(async() => {
|
|
57
70
|
const count = signal(1)
|
|
58
71
|
let doubled = 0
|
|
@@ -135,10 +148,10 @@ export default Science.suite({
|
|
|
135
148
|
expect(runs).is(2)
|
|
136
149
|
}),
|
|
137
150
|
|
|
138
|
-
"
|
|
151
|
+
"lazy values": test(async() => {
|
|
139
152
|
const a = signal(2)
|
|
140
153
|
const b = signal(3)
|
|
141
|
-
const sum =
|
|
154
|
+
const sum = lazy(() => a.value + b.value)
|
|
142
155
|
expect(sum.value).is(5)
|
|
143
156
|
|
|
144
157
|
await a.set(5)
|
|
@@ -148,11 +161,93 @@ export default Science.suite({
|
|
|
148
161
|
expect(sum.value).is(12)
|
|
149
162
|
}),
|
|
150
163
|
|
|
151
|
-
"
|
|
164
|
+
"effect reacts to derived changes": test(async() => {
|
|
165
|
+
const a = signal(1)
|
|
166
|
+
const b = signal(10)
|
|
167
|
+
const product = signal.derive(() => a.value * b.value)
|
|
168
|
+
|
|
169
|
+
let mutations = 0
|
|
170
|
+
effect(() => {
|
|
171
|
+
void product.get()
|
|
172
|
+
mutations++
|
|
173
|
+
})
|
|
174
|
+
expect(product.value).is(10)
|
|
175
|
+
expect(mutations).is(1)
|
|
176
|
+
|
|
177
|
+
await a.set(2)
|
|
178
|
+
expect(product.value).is(20)
|
|
179
|
+
expect(mutations).is(2)
|
|
180
|
+
|
|
181
|
+
await a.set(3)
|
|
182
|
+
expect(product.value).is(30)
|
|
183
|
+
expect(mutations).is(3)
|
|
184
|
+
}),
|
|
185
|
+
|
|
186
|
+
"effect doesn't overreact to derived": test(async() => {
|
|
187
|
+
const a = signal(1)
|
|
188
|
+
const b = signal(10)
|
|
189
|
+
const product = signal.derive(() => a.value * b.value)
|
|
190
|
+
|
|
191
|
+
const derivedSpy = spy(() => {})
|
|
192
|
+
product.on(derivedSpy)
|
|
193
|
+
|
|
194
|
+
let mutations = 0
|
|
195
|
+
effect(() => {
|
|
196
|
+
a.get()
|
|
197
|
+
product.get()
|
|
198
|
+
mutations++
|
|
199
|
+
})
|
|
200
|
+
expect(product.value).is(10)
|
|
201
|
+
expect(mutations).is(1)
|
|
202
|
+
expect(derivedSpy.spy.calls.length).is(0)
|
|
203
|
+
|
|
204
|
+
await a.set(2)
|
|
205
|
+
expect(product.value).is(20)
|
|
206
|
+
expect(mutations).is(2)
|
|
207
|
+
expect(derivedSpy.spy.calls.length).is(1)
|
|
208
|
+
}),
|
|
209
|
+
|
|
210
|
+
"derived.on": test(async() => {
|
|
211
|
+
const a = signal(1)
|
|
212
|
+
const b = signal(10)
|
|
213
|
+
const product = signal.derive(() => a.value * b.value)
|
|
214
|
+
expect(product.value).is(10)
|
|
215
|
+
|
|
216
|
+
const mole = spy((_v: number) => {})
|
|
217
|
+
product.on(mole)
|
|
218
|
+
expect(mole.spy.calls.length).is(0)
|
|
219
|
+
|
|
220
|
+
await a.set(2)
|
|
221
|
+
expect(product.value).is(20)
|
|
222
|
+
expect(mole.spy.calls.length).is(1)
|
|
223
|
+
expect(mole.spy.calls[0].args[0]).is(20)
|
|
224
|
+
}),
|
|
225
|
+
|
|
226
|
+
"derived.on not called if result doesn't change": test(async() => {
|
|
227
|
+
const a = signal(1)
|
|
228
|
+
const b = signal(10)
|
|
229
|
+
const product = signal.derive(() => a.value * b.value)
|
|
230
|
+
expect(product.value).is(10)
|
|
231
|
+
|
|
232
|
+
const mole = spy((_v: number) => {})
|
|
233
|
+
product.on(mole)
|
|
234
|
+
expect(mole.spy.calls.length).is(0)
|
|
235
|
+
|
|
236
|
+
await a.set(2)
|
|
237
|
+
expect(product.value).is(20)
|
|
238
|
+
expect(mole.spy.calls.length).is(1)
|
|
239
|
+
expect(mole.spy.calls[0].args[0]).is(20)
|
|
240
|
+
|
|
241
|
+
await a.set(2)
|
|
242
|
+
expect(product.value).is(20)
|
|
243
|
+
expect(mole.spy.calls.length).is(1)
|
|
244
|
+
}),
|
|
245
|
+
|
|
246
|
+
"lazy is lazy": test(async() => {
|
|
152
247
|
const a = signal(1)
|
|
153
248
|
let runs = 0
|
|
154
249
|
|
|
155
|
-
const comp =
|
|
250
|
+
const comp = lazy(() => {
|
|
156
251
|
runs++
|
|
157
252
|
return a.value * 10
|
|
158
253
|
})
|
|
@@ -167,10 +262,10 @@ export default Science.suite({
|
|
|
167
262
|
expect(runs).is(2)
|
|
168
263
|
}),
|
|
169
264
|
|
|
170
|
-
"
|
|
265
|
+
"lazy fn syntax": test(async() => {
|
|
171
266
|
const a = signal(2)
|
|
172
267
|
const b = signal(3)
|
|
173
|
-
const sum =
|
|
268
|
+
const sum = lazy(() => a.value + b.value)
|
|
174
269
|
expect(sum.value).is(5)
|
|
175
270
|
|
|
176
271
|
await a.set(5)
|
package/s/tests.test.ts
CHANGED
|
@@ -47,7 +47,7 @@ await Science.run({
|
|
|
47
47
|
expect(order[2]).is("after")
|
|
48
48
|
}),
|
|
49
49
|
|
|
50
|
-
"branch can include signal value": test
|
|
50
|
+
"branch can include signal value": test(async() => {
|
|
51
51
|
const bingus = signal(101)
|
|
52
52
|
const trunk = new Trunk({count: 1})
|
|
53
53
|
const branch = trunk.branch(s => ({
|