@e280/strata 0.0.0-0 → 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 (101) hide show
  1. package/README.md +207 -31
  2. package/package.json +14 -8
  3. package/s/index.ts +3 -3
  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 +46 -99
  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/tree/parts/chronobranch.ts +84 -0
  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 -3
  26. package/x/index.js +3 -2
  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 -100
  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/tree/parts/chronobranch.js +74 -0
  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 -48
  89. package/s/parts/substrata.ts +0 -55
  90. package/s/parts/types.ts +0 -11
  91. package/x/parts/strata.d.ts +0 -10
  92. package/x/parts/strata.js +0 -38
  93. package/x/parts/strata.js.map +0 -1
  94. package/x/parts/substrata.d.ts +0 -13
  95. package/x/parts/substrata.js +0 -42
  96. package/x/parts/substrata.js.map +0 -1
  97. package/x/parts/types.d.ts +0 -7
  98. package/x/parts/utils/process-options.js +0 -4
  99. package/x/parts/utils/process-options.js.map +0 -1
  100. /package/x/{parts → signals/parts}/types.js +0 -0
  101. /package/x/{parts → tree/parts}/utils/process-options.d.ts +0 -0
package/README.md CHANGED
@@ -1,71 +1,247 @@
1
1
 
2
+ ![](https://i.imgur.com/h7FohWa.jpeg)
3
+
4
+ <br/>
5
+
2
6
  # ⛏️ strata
3
7
 
4
- **my 10th state management library, probably**
5
- - 📦 `npm install @e280/strata`
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.
22
+
23
+ <br/>
24
+
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
+ ```
82
+
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*
6
108
  - single-source-of-truth state tree
7
109
  - immutable except for `mutate(fn)` calls
8
- - undo/redo history, cross-tab sync, localStorage persistence
110
+ - localStorage persistence, cross-tab sync, undo/redo history
111
+ - no spooky-dookie proxy magic — just god's honest javascript
9
112
 
10
- ## good state management
11
-
12
- ### establish a strata with some state
113
+ #### `Trunk` is your app's state tree root
13
114
  - better stick to json-friendly serializable data
14
115
  ```ts
15
- import {Strata} from "@e280/strata"
116
+ import {Trunk} from "@e280/strata"
16
117
 
17
- const strata = new Strata({
118
+ const trunk = new Trunk({
18
119
  count: 0,
19
- stuff: {
120
+ snacks: {
20
121
  peanuts: 8,
21
- items: ["hello", "world"],
122
+ bag: ["popcorn", "butter"],
22
123
  },
23
124
  })
24
125
 
25
- strata.state.count // 0
26
- strata.state.stuff.peanuts // 8
126
+ trunk.state.count // 0
127
+ trunk.state.snacks.peanuts // 8
27
128
  ```
28
129
 
29
- ### how mutations work
130
+ #### formal mutations to change state
30
131
  - ⛔ informal mutations are denied
31
132
  ```ts
32
- strata.state.count++ // error is thrown
133
+ trunk.state.count++ // error is thrown
33
134
  ```
34
- - ✅ formal mutation is allowed
135
+ - ✅ formal mutations are allowed
35
136
  ```ts
36
- await strata.mutate(s => s.count++)
137
+ await trunk.mutate(s => s.count++)
37
138
  ```
38
139
 
39
- ### substrata and selectors
40
- - a substrata is a view into a subset of the state tree
140
+ #### `Branch` is a view into a subtree
141
+ - it's a lens, make lots of them, pass 'em around your app
41
142
  ```ts
42
- const stuff = strata.substrata(s => s.stuff)
143
+ const snacks = trunk.branch(s => s.snacks)
43
144
  ```
44
- - run substrata mutations
145
+ - run branch mutations
45
146
  ```ts
46
- await stuff.mutate(s => s.peanuts++)
147
+ await snacks.mutate(s => s.peanuts++)
47
148
  ```
48
- - array mutations are cool, actually
149
+ - array mutations are unironically based, actually
49
150
  ```ts
50
- await stuff.mutate(s => s.items.push("lol"))
151
+ await snacks.mutate(s => s.bag.push("salt"))
51
152
  ```
153
+ - you can branch a branch
52
154
 
53
- ### onMutation events
54
- - 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
55
157
  ```ts
56
- strata.onMutation(s => console.log(s.count))
158
+ trunk.on(s => console.log(s.count))
159
+ ```
160
+ - whereas branch listeners don't care about changes outside their scope
161
+ ```ts
162
+ snacks.on(s => console.log(s.peanuts))
163
+ ```
164
+ - on returns a fn to stop listening
165
+ ```ts
166
+ const stop = trunk.on(s => console.log(s.count))
167
+ stop() // stop listening
57
168
  ```
58
169
 
59
- - substrata listeners don't care about outside changes
170
+ ### only discerning high-class aristocrats are permitted beyond this point
171
+
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)
60
183
  ```ts
61
- stuff.onMutation(s => console.log(s.peanuts))
184
+ import {Kv, StorageDriver} from "@e280/kv"
185
+
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
+ })
62
197
  ```
63
198
 
64
- - onMutation returns a fn to stop listening
199
+ #### `Chronobranch` for undo/redo history
200
+ - first, put a `Chronicle` into your state tree
65
201
  ```ts
66
- const stop = strata.onMutation(s => console.log(s.count))
67
- stop() // stop listening
202
+ const trunk = new Trunk({
203
+ count: 0,
204
+ snacks: Trunk.chronicle({
205
+ peanuts: 8,
206
+ bag: ["popcorn", "butter"],
207
+ }),
208
+ })
209
+ ```
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*
211
+ - second, make a `Chronobranch` which is like a branch, but is concerned with history
212
+ ```ts
213
+ const snacks = trunk.chronobranch(64, s => s.snacks)
214
+ // \
215
+ // how many past snapshots to store
216
+ ```
217
+ - mutations will advance history (undoable/redoable)
218
+ ```ts
219
+ await snacks.mutate(s => s.peanuts = 101)
220
+
221
+ await snacks.undo()
222
+ // back to 8 peanuts
223
+
224
+ await snacks.redo()
225
+ // forward to 101 peanuts
226
+ ```
227
+ - you can check how many undoable or redoable steps are available
228
+ ```ts
229
+ snacks.undoable // 2
230
+ snacks.redoable // 1
68
231
  ```
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)
243
+
244
+ <br/>
69
245
 
70
246
  ## a buildercore e280 project
71
247
  free and open source by https://e280.org/
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "@e280/strata",
3
- "version": "0.0.0-0",
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,5 +1,5 @@
1
1
 
2
- export {Strata} from "./parts/strata.js"
3
- export {Substrata} from "./parts/substrata.js"
4
- export {Options, Mutator, Selector} from "./parts/types.js"
2
+ export * from "./signals/index.js"
3
+ export * from "./tracker/index.js"
4
+ export * from "./tree/index.js"
5
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
+