@e280/strata 0.0.0-1 → 0.0.0-10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +165 -45
  2. package/package.json +14 -8
  3. package/s/index.ts +3 -4
  4. package/s/signals/index.ts +6 -0
  5. package/s/signals/parts/derive.ts +29 -0
  6. package/s/signals/parts/effect.ts +23 -0
  7. package/s/signals/parts/lazy.ts +27 -0
  8. package/s/signals/parts/signal.ts +44 -0
  9. package/s/signals/parts/types.ts +11 -0
  10. package/s/signals/parts/units.ts +152 -0
  11. package/s/signals/signals.test.ts +285 -0
  12. package/s/tests.test.ts +45 -286
  13. package/s/tracker/index.ts +3 -0
  14. package/s/tracker/tracker.test.ts +40 -0
  15. package/s/tracker/tracker.ts +73 -0
  16. package/s/tree/index.ts +7 -0
  17. package/s/tree/parts/branch.ts +41 -0
  18. package/s/{parts/chronstrata.ts → tree/parts/chronobranch.ts} +19 -19
  19. package/s/tree/parts/persistence.ts +31 -0
  20. package/s/tree/parts/trunk.ts +72 -0
  21. package/s/tree/parts/types.ts +65 -0
  22. package/s/{parts → tree/parts}/utils/process-options.ts +1 -1
  23. package/s/tree/parts/utils/setup.ts +40 -0
  24. package/s/tree/tree.test.ts +316 -0
  25. package/x/index.d.ts +3 -4
  26. package/x/index.js +3 -4
  27. package/x/index.js.map +1 -1
  28. package/x/signals/index.d.ts +4 -0
  29. package/x/signals/index.js +5 -0
  30. package/x/signals/index.js.map +1 -0
  31. package/x/signals/parts/derive.d.ts +12 -0
  32. package/x/signals/parts/derive.js +12 -0
  33. package/x/signals/parts/derive.js.map +1 -0
  34. package/x/signals/parts/effect.d.ts +5 -0
  35. package/x/signals/parts/effect.js +17 -0
  36. package/x/signals/parts/effect.js.map +1 -0
  37. package/x/signals/parts/lazy.d.ts +10 -0
  38. package/x/signals/parts/lazy.js +12 -0
  39. package/x/signals/parts/lazy.js.map +1 -0
  40. package/x/signals/parts/signal.d.ts +21 -0
  41. package/x/signals/parts/signal.js +18 -0
  42. package/x/signals/parts/signal.js.map +1 -0
  43. package/x/signals/parts/types.d.ts +7 -0
  44. package/x/signals/parts/types.js.map +1 -0
  45. package/x/signals/parts/units.d.ts +43 -0
  46. package/x/signals/parts/units.js +133 -0
  47. package/x/signals/parts/units.js.map +1 -0
  48. package/x/signals/signals.test.d.ts +24 -0
  49. package/x/signals/signals.test.js +230 -0
  50. package/x/signals/signals.test.js.map +1 -0
  51. package/x/tests.test.js +46 -265
  52. package/x/tests.test.js.map +1 -1
  53. package/x/tracker/index.d.ts +1 -0
  54. package/x/tracker/index.js +2 -0
  55. package/x/tracker/index.js.map +1 -0
  56. package/x/tracker/tracker.d.ts +29 -0
  57. package/x/tracker/tracker.js +62 -0
  58. package/x/tracker/tracker.js.map +1 -0
  59. package/x/tracker/tracker.test.d.ts +6 -0
  60. package/x/tracker/tracker.test.js +32 -0
  61. package/x/tracker/tracker.test.js.map +1 -0
  62. package/x/tree/index.d.ts +5 -0
  63. package/x/tree/index.js +6 -0
  64. package/x/tree/index.js.map +1 -0
  65. package/x/tree/parts/branch.d.ts +12 -0
  66. package/x/tree/parts/branch.js +31 -0
  67. package/x/tree/parts/branch.js.map +1 -0
  68. package/x/tree/parts/chronobranch.d.ts +23 -0
  69. package/x/{parts/chronstrata.js → tree/parts/chronobranch.js} +17 -17
  70. package/x/tree/parts/chronobranch.js.map +1 -0
  71. package/x/tree/parts/persistence.d.ts +2 -0
  72. package/x/tree/parts/persistence.js +23 -0
  73. package/x/tree/parts/persistence.js.map +1 -0
  74. package/x/tree/parts/trunk.d.ts +17 -0
  75. package/x/tree/parts/trunk.js +56 -0
  76. package/x/tree/parts/trunk.js.map +1 -0
  77. package/x/tree/parts/types.d.ts +43 -0
  78. package/x/tree/parts/types.js +2 -0
  79. package/x/{parts → tree/parts}/types.js.map +1 -1
  80. package/x/tree/parts/utils/process-options.js +4 -0
  81. package/x/tree/parts/utils/process-options.js.map +1 -0
  82. package/x/tree/parts/utils/setup.d.ts +8 -0
  83. package/x/tree/parts/utils/setup.js +24 -0
  84. package/x/tree/parts/utils/setup.js.map +1 -0
  85. package/x/tree/tree.test.d.ts +37 -0
  86. package/x/tree/tree.test.js +279 -0
  87. package/x/tree/tree.test.js.map +1 -0
  88. package/s/parts/strata.ts +0 -71
  89. package/s/parts/substrata.ts +0 -58
  90. package/s/parts/types.ts +0 -31
  91. package/x/parts/chronstrata.d.ts +0 -23
  92. package/x/parts/chronstrata.js.map +0 -1
  93. package/x/parts/strata.d.ts +0 -14
  94. package/x/parts/strata.js +0 -64
  95. package/x/parts/strata.js.map +0 -1
  96. package/x/parts/substrata.d.ts +0 -15
  97. package/x/parts/substrata.js +0 -45
  98. package/x/parts/substrata.js.map +0 -1
  99. package/x/parts/types.d.ts +0 -19
  100. package/x/parts/utils/process-options.js +0 -4
  101. package/x/parts/utils/process-options.js.map +0 -1
  102. /package/x/{parts → signals/parts}/types.js +0 -0
  103. /package/x/{parts → tree/parts}/utils/process-options.d.ts +0 -0
package/README.md CHANGED
@@ -1,25 +1,121 @@
1
1
 
2
2
  ![](https://i.imgur.com/h7FohWa.jpeg)
3
3
 
4
+ <br/>
5
+
4
6
  # ⛏️ strata
5
7
 
6
- **my 10th state management library, probably**
7
- - 📦 `npm install @e280/strata`
8
- - single-source-of-truth state tree
9
- - immutable except for `mutate(fn)` calls
10
- - no spooky-dookie proxy magic — just god's honest javascript
11
- - undo/redo history, cross-tab sync, localStorage persistence
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
+ 🚦 **signals** ephemeral view-level state
14
+ 🌳 **tree** — persistent app-level state
15
+ 🪄 **tracker** — reactivity integration hub
16
+
17
+ <br/>
18
+
19
+ > [!TIP]
20
+ > incredibly, signals and trees are interoperable.
21
+ > that means, effects and computeds are responsive to changes in tree state.
12
22
 
13
23
  <br/>
14
24
 
15
- ## get in loser, we're managing state
25
+ ## 🚦 signals *ephemeral view-level state*
26
+ ```ts
27
+ import {signal, effect, computed} from "@e280/strata"
28
+ ```
29
+
30
+ ### each signal holds a value
31
+ - **create a signal**
32
+ ```ts
33
+ const count = signal(0)
34
+ ```
35
+ - **read a signal**
36
+ ```ts
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
+ ```
47
+
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
64
+ ```
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**
73
+ ```ts
74
+ effect(() => console.log(count()))
75
+ // 1
76
+ // the system detects 'count' is relevant
77
+
78
+ count.value++
79
+ // 2
80
+ // when count is changed, the effect fn is run
81
+ ```
16
82
 
17
- ### `Strata` is your app's state tree root
83
+ ### `signal.derive` and `signal.lazy` are computed signals
84
+ - **signal.derive**
85
+ is for combining signals
86
+ ```ts
87
+ const a = signal(1)
88
+ const b = signal(10)
89
+ const product = signal.derive(() => a() * b())
90
+
91
+ product() // 10
92
+
93
+ // change a dependency,
94
+ // and the derived signal is automatically updated
95
+ await a.set(2)
96
+
97
+ product() // 20
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!*
104
+
105
+ <br/>
106
+
107
+ ## 🌳 tree — *persistent app-level state*
108
+ - single-source-of-truth state tree
109
+ - immutable except for `mutate(fn)` calls
110
+ - localStorage persistence, cross-tab sync, undo/redo history
111
+ - no spooky-dookie proxy magic — just god's honest javascript
112
+
113
+ #### `Trunk` is your app's state tree root
18
114
  - better stick to json-friendly serializable data
19
115
  ```ts
20
- import {Strata} from "@e280/strata"
116
+ import {Trunk} from "@e280/strata"
21
117
 
22
- const strata = new Strata({
118
+ const trunk = new Trunk({
23
119
  count: 0,
24
120
  snacks: {
25
121
  peanuts: 8,
@@ -27,26 +123,26 @@
27
123
  },
28
124
  })
29
125
 
30
- strata.state.count // 0
31
- strata.state.snacks.peanuts // 8
126
+ trunk.state.count // 0
127
+ trunk.state.snacks.peanuts // 8
32
128
  ```
33
129
 
34
- ### formal mutations to change state
130
+ #### formal mutations to change state
35
131
  - ⛔ informal mutations are denied
36
132
  ```ts
37
- strata.state.count++ // error is thrown
133
+ trunk.state.count++ // error is thrown
38
134
  ```
39
135
  - ✅ formal mutations are allowed
40
136
  ```ts
41
- await strata.mutate(s => s.count++)
137
+ await trunk.mutate(s => s.count++)
42
138
  ```
43
139
 
44
- ### `Substrata` is a view into a subtree
140
+ #### `Branch` is a view into a subtree
45
141
  - it's a lens, make lots of them, pass 'em around your app
46
142
  ```ts
47
- const snacks = strata.substrata(s => s.snacks)
143
+ const snacks = trunk.branch(s => s.snacks)
48
144
  ```
49
- - run substrata mutations
145
+ - run branch mutations
50
146
  ```ts
51
147
  await snacks.mutate(s => s.peanuts++)
52
148
  ```
@@ -54,42 +150,67 @@
54
150
  ```ts
55
151
  await snacks.mutate(s => s.bag.push("salt"))
56
152
  ```
57
- - you can make a substrata of another substrata
153
+ - you can branch a branch
58
154
 
59
- ### onMutation events
60
- - you can listen to global mutations on the strata
155
+ #### `on` to watch for mutations
156
+ - on the trunk, we can listen deeply for mutations within the whole tree
61
157
  ```ts
62
- strata.onMutation(s => console.log(s.count))
158
+ trunk.on(s => console.log(s.count))
63
159
  ```
64
- - substrata listeners don't care about outside changes
160
+ - whereas branch listeners don't care about changes outside their scope
65
161
  ```ts
66
- snacks.onMutation(s => console.log(s.peanuts))
162
+ snacks.on(s => console.log(s.peanuts))
67
163
  ```
68
- - onMutation returns a fn to stop listening
164
+ - on returns a fn to stop listening
69
165
  ```ts
70
- const stop = strata.onMutation(s => console.log(s.count))
166
+ const stop = trunk.on(s => console.log(s.count))
71
167
  stop() // stop listening
72
168
  ```
73
169
 
74
- <br/>
170
+ ### only discerning high-class aristocrats are permitted beyond this point
75
171
 
76
- ## only high-class discerning aristocrats permitted beyond this point
172
+ #### `Trunk.setup` for localStorage persistence etc
173
+ - it automatically handles persistence to localStorage and cross-tab synchronization
174
+ - simple setup
175
+ ```ts
176
+ const {trunk} = await Trunk.setup({
177
+ version: 1, // 👈 bump whenever you change state schema!
178
+ initialState: {count: 0},
179
+ })
180
+ ```
181
+ - uses localStorage by default
182
+ - it's compatible with [`@e280/kv`](https://github.com/e280/kv)
183
+ ```ts
184
+ import {Kv, StorageDriver} from "@e280/kv"
77
185
 
78
- ### `Chronstrata` for undo/redo history
186
+ const kv = new Kv(new StorageDriver())
187
+ const store = kv.store<any>("appState")
188
+
189
+ const {trunk} = await Trunk.setup({
190
+ version: 1,
191
+ initialState: {count: 0},
192
+ persistence: {
193
+ store,
194
+ onChange: StorageDriver.onStorageEvent,
195
+ },
196
+ })
197
+ ```
198
+
199
+ #### `Chronobranch` for undo/redo history
79
200
  - first, put a `Chronicle` into your state tree
80
201
  ```ts
81
- const strata = new Strata({
202
+ const trunk = new Trunk({
82
203
  count: 0,
83
- snacks: Strata.chronicle({
204
+ snacks: Trunk.chronicle({
84
205
  peanuts: 8,
85
206
  bag: ["popcorn", "butter"],
86
207
  }),
87
208
  })
88
209
  ```
89
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*
90
- - second, make a `Chronstrata` which is like a substrata
211
+ - second, make a `Chronobranch` which is like a branch, but is concerned with history
91
212
  ```ts
92
- const snacks = strata.chronstrata(64, s => s.snacks)
213
+ const snacks = trunk.chronobranch(64, s => s.snacks)
93
214
  // \
94
215
  // how many past snapshots to store
95
216
  ```
@@ -105,21 +226,20 @@
105
226
  ```
106
227
  - you can check how many undoable or redoable steps are available
107
228
  ```ts
108
- snacks.undoable // 0
109
-
110
- await snacks.mutate(s => s.peanuts = 101)
111
- await snacks.mutate(s => s.peanuts = 102)
112
- await snacks.mutate(s => s.peanuts = 103)
113
-
114
- snacks.undoable // 3
115
-
116
- await snacks.undo()
117
-
118
229
  snacks.undoable // 2
119
230
  snacks.redoable // 1
120
231
  ```
121
- - chronstrata can have its own substrata — all their mutations advance history
122
- - plz pinky-swear right now, that you won't create a chronstrata under a substrata under a chronstrata
232
+ - chronobranch can have its own branches — all their mutations advance history
233
+ - plz pinky-swear right now, that you won't create a chronobranch under a branch under another chronobranch 💀
234
+
235
+ <br/>
236
+
237
+ ## 🪄 tracker — integrations
238
+ - ```ts
239
+ import {tracker} from "@e280/strata/tracker"
240
+ ```
241
+ - all reactivity is orchestrated by the `tracker`
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)
123
243
 
124
244
  <br/>
125
245
 
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "@e280/strata",
3
- "version": "0.0.0-1",
3
+ "version": "0.0.0-10",
4
4
  "description": "state management",
5
5
  "license": "MIT",
6
6
  "author": "Chase Moskal <chasemoskal@gmail.com>",
7
7
  "type": "module",
8
- "main": "x/index.js",
8
+ "main": "./x/index.js",
9
9
  "files": [
10
10
  "x",
11
11
  "s"
12
12
  ],
13
+ "exports": {
14
+ ".": "./x/index.js",
15
+ "./signals": "./x/signals/index.js",
16
+ "./tracker": "./x/tracker/index.js",
17
+ "./tree": "./x/tree/index.js"
18
+ },
13
19
  "scripts": {
14
20
  "build": "run-s _clean _links _tsc",
15
21
  "test": "node x/tests.test.js",
@@ -22,11 +28,14 @@
22
28
  "_tsc": "tsc",
23
29
  "_tscw": "tsc -w"
24
30
  },
31
+ "dependencies": {
32
+ "@e280/stz": "^0.0.0-35"
33
+ },
25
34
  "devDependencies": {
26
- "@e280/science": "^0.0.5",
27
- "@types/node": "^24.0.3",
35
+ "@e280/science": "^0.0.6",
36
+ "@types/node": "^24.3.0",
28
37
  "npm-run-all": "^4.1.5",
29
- "typescript": "^5.8.3"
38
+ "typescript": "^5.9.2"
30
39
  },
31
40
  "keywords": [
32
41
  "state",
@@ -39,8 +48,5 @@
39
48
  },
40
49
  "bugs": {
41
50
  "url": "https://github.com/e280/strata/issues"
42
- },
43
- "dependencies": {
44
- "@e280/stz": "^0.0.0-24"
45
51
  }
46
52
  }
package/s/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
 
2
- export * from "./parts/chronstrata.js"
3
- export * from "./parts/strata.js"
4
- export * from "./parts/substrata.js"
5
- export * from "./parts/types.js"
2
+ export * from "./signals/index.js"
3
+ export * from "./tracker/index.js"
4
+ export * from "./tree/index.js"
6
5
 
@@ -0,0 +1,6 @@
1
+
2
+ export * from "./parts/lazy.js"
3
+ export * from "./parts/effect.js"
4
+ export * from "./parts/signal.js"
5
+ export * from "./parts/types.js"
6
+
@@ -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
+
@@ -0,0 +1,23 @@
1
+
2
+ import {debounce} from "@e280/stz"
3
+ import {tracker} from "../../tracker/tracker.js"
4
+
5
+ export function effect(collector: () => void, responder: () => void = collector) {
6
+ return collectorEffect(collector, responder).dispose
7
+ }
8
+
9
+ export function collectorEffect<C = void>(collector: () => C, responder: () => void = collector) {
10
+ const {seen, result} = tracker.seen(collector)
11
+ const fn = debounce(0, responder)
12
+
13
+ const disposers: (() => void)[] = []
14
+ const dispose = () => disposers.forEach(d => d())
15
+
16
+ for (const saw of seen) {
17
+ const dispose = tracker.changed(saw, fn)
18
+ disposers.push(dispose)
19
+ }
20
+
21
+ return {result, dispose}
22
+ }
23
+
@@ -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
+
@@ -0,0 +1,44 @@
1
+
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"
8
+
9
+ export type Signal<V> = {
10
+ (): V
11
+ (v: V): Promise<V>
12
+ (v?: V): V | Promise<V>
13
+
14
+ kind: "signal"
15
+
16
+ sneak: V
17
+ value: V
18
+ on: Sub<[V]>
19
+ get(): V
20
+ set(v: V): Promise<V>
21
+ publish(v?: V): Promise<V>
22
+ dispose(): void
23
+ } & SignalCore<V>
24
+
25
+ export function signal<V>(value: V, options: Partial<SignalOptions> = {}) {
26
+ function fn(): V
27
+ function fn(v: V): Promise<void>
28
+ function fn(v?: V): V | Promise<void> {
29
+ return v !== undefined
30
+ ? (fn as any).set(v)
31
+ : (fn as any).get()
32
+ }
33
+
34
+ const o = processSignalOptions(options)
35
+ const core = new SignalCore(value, o)
36
+ Object.setPrototypeOf(fn, SignalCore.prototype)
37
+ Object.assign(fn, core)
38
+
39
+ return fn as Signal<V>
40
+ }
41
+
42
+ signal.lazy = lazy
43
+ signal.derive = derive
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,152 @@
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
+ return v
49
+ }
50
+
51
+ get value() {
52
+ return this.get()
53
+ }
54
+
55
+ set value(v: V) {
56
+ this.set(v)
57
+ }
58
+
59
+ async publish(v = this.get()) {
60
+ if (this._lock)
61
+ throw new Error("forbid circularity")
62
+
63
+ let promise = Promise.resolve()
64
+
65
+ try {
66
+ this._lock = true
67
+ this.sneak = v
68
+ promise = Promise.all([
69
+ tracker.change(this),
70
+ this.on.pub(v),
71
+ ]) as any
72
+ }
73
+ finally {
74
+ this._lock = false
75
+ }
76
+
77
+ await promise
78
+ return v
79
+ }
80
+ }
81
+
82
+ export class LazyCore<V> extends ReadableSignal<V> {
83
+ kind: "lazy" = "lazy"
84
+
85
+ _dirty = false
86
+ _effect: (() => void) | undefined
87
+
88
+ constructor(public _formula: () => V, public _options: SignalOptions) {
89
+ super(undefined as any)
90
+ }
91
+
92
+ get() {
93
+ if (!this._effect) {
94
+ const {result, dispose} = collectorEffect(this._formula, () => this._dirty = true)
95
+ this._effect = dispose
96
+ this.sneak = result
97
+ }
98
+ if (this._dirty) {
99
+ this._dirty = false
100
+
101
+ const v = this._formula()
102
+ const isChanged = !this._options.compare(this.sneak, v)
103
+ if (isChanged) {
104
+ this.sneak = v
105
+ tracker.change(this)
106
+ }
107
+ }
108
+ return super.get()
109
+ }
110
+
111
+ get value() {
112
+ return this.get()
113
+ }
114
+
115
+ dispose() {
116
+ if (this._effect)
117
+ this._effect()
118
+ }
119
+ }
120
+
121
+ export class DerivedCore<V> extends ReactiveSignal<V> {
122
+ static make<V>(that: DerivedCore<V>, formula: () => V, options: SignalOptions) {
123
+ const {result, dispose} = collectorEffect(formula, async() => {
124
+ const value = formula()
125
+ const isChanged = !options.compare(that.sneak, value)
126
+ if (isChanged) {
127
+ that.sneak = value
128
+ await Promise.all([
129
+ tracker.change(that),
130
+ that.on.pub(value),
131
+ ])
132
+ }
133
+ })
134
+ return new this(result, dispose)
135
+ }
136
+
137
+ kind: "derived" = "derived"
138
+
139
+ constructor(initialValue: V, public _effect: () => void) {
140
+ super(initialValue)
141
+ }
142
+
143
+ get value() {
144
+ return this.get()
145
+ }
146
+
147
+ dispose() {
148
+ super.dispose()
149
+ this._effect()
150
+ }
151
+ }
152
+