@e280/strata 0.0.0-5 → 0.0.0-7
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 +140 -39
- package/package.json +9 -2
- package/s/index.ts +3 -5
- package/s/signals/index.ts +5 -0
- package/s/signals/parts/computed.ts +53 -0
- package/s/signals/parts/effect.ts +23 -0
- package/s/signals/parts/signal.ts +66 -0
- package/s/signals/signals.test.ts +181 -0
- package/s/tests.test.ts +45 -286
- package/s/tracker/index.ts +3 -0
- package/s/tracker/tracker.test.ts +40 -0
- package/s/tracker/tracker.ts +73 -0
- package/s/tree/index.ts +7 -0
- package/s/{parts/substrata.ts → tree/parts/branch.ts} +11 -10
- package/s/{parts/chronstrata.ts → tree/parts/chronobranch.ts} +8 -8
- package/s/{parts/strata.ts → tree/parts/trunk.ts} +14 -13
- package/s/{parts → tree/parts}/types.ts +8 -8
- package/s/{parts → tree/parts}/utils/setup.ts +9 -9
- package/s/tree/tree.test.ts +307 -0
- package/x/index.d.ts +3 -5
- package/x/index.js +3 -5
- package/x/index.js.map +1 -1
- package/x/signals/index.d.ts +3 -0
- package/x/signals/index.js +4 -0
- package/x/signals/index.js.map +1 -0
- package/x/signals/parts/computed.d.ts +14 -0
- package/x/signals/parts/computed.js +41 -0
- package/x/signals/parts/computed.js.map +1 -0
- package/x/signals/parts/effect.d.ts +5 -0
- package/x/signals/parts/effect.js +17 -0
- package/x/signals/parts/effect.js.map +1 -0
- package/x/signals/parts/signal.d.ts +18 -0
- package/x/signals/parts/signal.js +47 -0
- package/x/signals/parts/signal.js.map +1 -0
- package/x/signals/signals.test.d.ts +18 -0
- package/x/signals/signals.test.js +144 -0
- package/x/signals/signals.test.js.map +1 -0
- package/x/tests.test.js +46 -265
- package/x/tests.test.js.map +1 -1
- package/x/tracker/index.d.ts +1 -0
- package/x/tracker/index.js +2 -0
- package/x/tracker/index.js.map +1 -0
- package/x/tracker/tracker.d.ts +29 -0
- package/x/tracker/tracker.js +62 -0
- package/x/tracker/tracker.js.map +1 -0
- package/x/tracker/tracker.test.d.ts +6 -0
- package/x/tracker/tracker.test.js +32 -0
- package/x/tracker/tracker.test.js.map +1 -0
- package/x/tree/index.d.ts +5 -0
- package/x/tree/index.js +6 -0
- package/x/tree/index.js.map +1 -0
- package/x/tree/parts/branch.d.ts +15 -0
- package/x/{parts/substrata.js → tree/parts/branch.js} +10 -9
- package/x/tree/parts/branch.js.map +1 -0
- package/x/{parts/chronstrata.d.ts → tree/parts/chronobranch.d.ts} +6 -6
- package/x/{parts/chronstrata.js → tree/parts/chronobranch.js} +6 -6
- package/x/tree/parts/chronobranch.js.map +1 -0
- package/x/tree/parts/persistence.js.map +1 -0
- package/x/tree/parts/trunk.d.ts +17 -0
- package/x/{parts/strata.js → tree/parts/trunk.js} +13 -12
- package/x/tree/parts/trunk.js.map +1 -0
- package/x/{parts → tree/parts}/types.d.ts +8 -8
- package/x/{parts → tree/parts}/types.js.map +1 -1
- package/x/tree/parts/utils/process-options.js.map +1 -0
- package/x/tree/parts/utils/setup.d.ts +8 -0
- package/x/{parts → tree/parts}/utils/setup.js +8 -8
- package/x/tree/parts/utils/setup.js.map +1 -0
- package/x/tree/tree.test.d.ts +37 -0
- package/x/tree/tree.test.js +271 -0
- package/x/tree/tree.test.js.map +1 -0
- package/x/parts/chronstrata.js.map +0 -1
- package/x/parts/persistence.js.map +0 -1
- package/x/parts/strata.d.ts +0 -17
- package/x/parts/strata.js.map +0 -1
- package/x/parts/substrata.d.ts +0 -15
- package/x/parts/substrata.js.map +0 -1
- package/x/parts/utils/process-options.js.map +0 -1
- package/x/parts/utils/setup.d.ts +0 -8
- package/x/parts/utils/setup.js.map +0 -1
- /package/s/{parts → tree/parts}/persistence.ts +0 -0
- /package/s/{parts → tree/parts}/utils/process-options.ts +0 -0
- /package/x/{parts → tree/parts}/persistence.d.ts +0 -0
- /package/x/{parts → tree/parts}/persistence.js +0 -0
- /package/x/{parts → tree/parts}/types.js +0 -0
- /package/x/{parts → tree/parts}/utils/process-options.d.ts +0 -0
- /package/x/{parts → tree/parts}/utils/process-options.js +0 -0
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
|
|
2
2
|
import {debounce} from "@e280/stz"
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {State, SetupOptions} from "../types.js"
|
|
4
|
+
import {Trunk} from "../trunk.js"
|
|
6
5
|
import {localPersistence} from "../persistence.js"
|
|
6
|
+
import {Treestate, SetupOptions} from "../types.js"
|
|
7
7
|
|
|
8
|
-
export async function
|
|
8
|
+
export async function trunkSetup<S extends Treestate>(options: SetupOptions<S>) {
|
|
9
9
|
const {
|
|
10
10
|
version,
|
|
11
11
|
initialState,
|
|
12
12
|
saveDebounceTime = 500,
|
|
13
|
-
persistence = localPersistence("
|
|
13
|
+
persistence = localPersistence("strataTree"),
|
|
14
14
|
} = options
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const trunk = new Trunk<S>(initialState)
|
|
17
17
|
|
|
18
18
|
async function load() {
|
|
19
19
|
const pickle = await persistence.store.get()
|
|
20
20
|
if (pickle && pickle.version === version)
|
|
21
|
-
await
|
|
21
|
+
await trunk.overwrite(pickle.state)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const save = debounce(saveDebounceTime, async() => persistence.store.set({
|
|
25
25
|
version,
|
|
26
|
-
state:
|
|
26
|
+
state: trunk.state,
|
|
27
27
|
}))
|
|
28
28
|
|
|
29
29
|
// persistence: initial load from store
|
|
30
30
|
await load()
|
|
31
31
|
|
|
32
32
|
// persistence: save to store
|
|
33
|
-
|
|
33
|
+
trunk.watch(save)
|
|
34
34
|
|
|
35
35
|
// cross-tab sync
|
|
36
36
|
const dispose = persistence.onChange(load)
|
|
37
37
|
|
|
38
|
-
return {
|
|
38
|
+
return {trunk, load, save, dispose}
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
|
|
2
|
+
import {Science, expect} from "@e280/science"
|
|
3
|
+
import {Trunk} from "./parts/trunk.js"
|
|
4
|
+
|
|
5
|
+
export default Science.suite({
|
|
6
|
+
"strata": Science.suite({
|
|
7
|
+
"get state": Science.test(async() => {
|
|
8
|
+
const strata = new Trunk({count: 0})
|
|
9
|
+
expect(strata.state.count).is(0)
|
|
10
|
+
}),
|
|
11
|
+
|
|
12
|
+
"state is immutable": Science.test(async() => {
|
|
13
|
+
const strata = new Trunk({count: 0})
|
|
14
|
+
expect(() => strata.state.count++).throws()
|
|
15
|
+
}),
|
|
16
|
+
|
|
17
|
+
"run a proper mutation": Science.test(async() => {
|
|
18
|
+
const strata = new Trunk({count: 0})
|
|
19
|
+
expect(strata.state.count).is(0)
|
|
20
|
+
await strata.mutate(state => state.count++)
|
|
21
|
+
expect(strata.state.count).is(1)
|
|
22
|
+
await strata.mutate(state => state.count++)
|
|
23
|
+
expect(strata.state.count).is(2)
|
|
24
|
+
}),
|
|
25
|
+
|
|
26
|
+
"forbidden mutation nesting": Science.test(async() => {
|
|
27
|
+
const strata = new Trunk({count: 0})
|
|
28
|
+
await expect(async() => {
|
|
29
|
+
let promise!: Promise<any>
|
|
30
|
+
await strata.mutate(() => {
|
|
31
|
+
promise = strata.mutate(() => {})
|
|
32
|
+
})
|
|
33
|
+
await promise
|
|
34
|
+
}).throwsAsync()
|
|
35
|
+
}),
|
|
36
|
+
|
|
37
|
+
"state after mutation is frozen": Science.test(async () => {
|
|
38
|
+
const strata = new Trunk({x: 1})
|
|
39
|
+
await strata.mutate(s => { s.x = 2 })
|
|
40
|
+
expect(() => strata.state.x = 3).throws()
|
|
41
|
+
}),
|
|
42
|
+
|
|
43
|
+
"watch is published": Science.test(async() => {
|
|
44
|
+
const strata = new Trunk({count: 0})
|
|
45
|
+
let mutationCount = 0
|
|
46
|
+
strata.watch.sub(() => {mutationCount++})
|
|
47
|
+
await strata.mutate(state => state.count++)
|
|
48
|
+
expect(mutationCount).is(1)
|
|
49
|
+
}),
|
|
50
|
+
|
|
51
|
+
"watch is debounced": Science.test(async() => {
|
|
52
|
+
const strata = new Trunk({count: 0})
|
|
53
|
+
let mutationCount = 0
|
|
54
|
+
strata.watch.sub(() => {mutationCount++})
|
|
55
|
+
const promise = strata.mutate(state => state.count++)
|
|
56
|
+
expect(mutationCount).is(0)
|
|
57
|
+
await promise
|
|
58
|
+
expect(mutationCount).is(1)
|
|
59
|
+
}),
|
|
60
|
+
|
|
61
|
+
"watch is fired when array item is pushed": Science.test(async() => {
|
|
62
|
+
const strata = new Trunk({items: ["hello", "world"]})
|
|
63
|
+
let mutationCount = 0
|
|
64
|
+
strata.watch.sub(() => {mutationCount++})
|
|
65
|
+
await strata.mutate(state => state.items.push("lol"))
|
|
66
|
+
expect(mutationCount).is(1)
|
|
67
|
+
expect(strata.state.items.length).is(3)
|
|
68
|
+
}),
|
|
69
|
+
|
|
70
|
+
"prevent mutation loops": Science.test(async() => {
|
|
71
|
+
const strata = new Trunk({count: 0})
|
|
72
|
+
let mutationCount = 0
|
|
73
|
+
strata.watch.sub(async() => {
|
|
74
|
+
mutationCount++
|
|
75
|
+
if (mutationCount > 100)
|
|
76
|
+
return
|
|
77
|
+
await strata.mutate(s => s.count++)
|
|
78
|
+
})
|
|
79
|
+
await expect(async() => {
|
|
80
|
+
await strata.mutate(state => state.count++)
|
|
81
|
+
}).throwsAsync()
|
|
82
|
+
expect(mutationCount).is(1)
|
|
83
|
+
}),
|
|
84
|
+
}),
|
|
85
|
+
|
|
86
|
+
"substrata": Science.suite({
|
|
87
|
+
"get state": Science.test(async() => {
|
|
88
|
+
const strata = new Trunk({count: 0, sub: {rofls: 0}})
|
|
89
|
+
const substrata = strata.branch(s => s.sub)
|
|
90
|
+
expect(substrata.state.rofls).is(0)
|
|
91
|
+
}),
|
|
92
|
+
|
|
93
|
+
"nullable selector": Science.test(async () => {
|
|
94
|
+
const strata = new Trunk({
|
|
95
|
+
a: {b: 0} as (null | {b: number}),
|
|
96
|
+
})
|
|
97
|
+
const a = strata.branch(s => s.a)
|
|
98
|
+
expect(strata.state.a?.b).is(0)
|
|
99
|
+
expect(a.state?.b).is(0)
|
|
100
|
+
await a.mutate(a => { a!.b = 1 })
|
|
101
|
+
expect(strata.state.a?.b).is(1)
|
|
102
|
+
expect(a.state?.b).is(1)
|
|
103
|
+
await strata.mutate(s => s.a = null)
|
|
104
|
+
expect(strata.state.a?.b).is(undefined)
|
|
105
|
+
expect(a.state?.b).is(undefined)
|
|
106
|
+
}),
|
|
107
|
+
|
|
108
|
+
"composition": Science.test(async () => {
|
|
109
|
+
const strata = new Trunk({a: {b: {c: 0}}})
|
|
110
|
+
const a = strata.branch(s => s.a)
|
|
111
|
+
const b = a.branch(s => s.b)
|
|
112
|
+
expect(strata.state.a.b.c).is(0)
|
|
113
|
+
expect(b.state.c).is(0)
|
|
114
|
+
}),
|
|
115
|
+
|
|
116
|
+
"deep mutations": Science.test(async () => {
|
|
117
|
+
const strata = new Trunk({a: {b: {c: 0}}})
|
|
118
|
+
const a = strata.branch(s => s.a)
|
|
119
|
+
const b = a.branch(s => s.b)
|
|
120
|
+
await b.mutate(b => { b.c = 101 })
|
|
121
|
+
expect(strata.state.a.b.c).is(101)
|
|
122
|
+
expect(a.state.b.c).is(101)
|
|
123
|
+
expect(b.state.c).is(101)
|
|
124
|
+
await a.mutate(a => { a.b = {c: 102} })
|
|
125
|
+
expect(strata.state.a.b.c).is(102)
|
|
126
|
+
expect(a.state.b.c).is(102)
|
|
127
|
+
expect(b.state.c).is(102)
|
|
128
|
+
await strata.mutate(s => { s.a = {b: {c: 103}} })
|
|
129
|
+
expect(strata.state.a.b.c).is(103)
|
|
130
|
+
expect(a.state.b.c).is(103)
|
|
131
|
+
expect(b.state.c).is(103)
|
|
132
|
+
}),
|
|
133
|
+
|
|
134
|
+
"watch ignores outside mutations": Science.test(async() => {
|
|
135
|
+
const strata = new Trunk({a: {x: 0}, b: {x: 0}})
|
|
136
|
+
const a = strata.branch(s => s.a)
|
|
137
|
+
const b = strata.branch(s => s.b)
|
|
138
|
+
let counted = 0
|
|
139
|
+
b.watch.sub(() => {counted++})
|
|
140
|
+
await a.mutate(a => a.x = 1)
|
|
141
|
+
expect(counted).is(0)
|
|
142
|
+
}),
|
|
143
|
+
|
|
144
|
+
"forbid submutation in mutation": Science.test(async() => {
|
|
145
|
+
const strata = new Trunk({a: {b: 0}})
|
|
146
|
+
const a = strata.branch(s => s.a)
|
|
147
|
+
await expect(async() => {
|
|
148
|
+
let promise!: Promise<any>
|
|
149
|
+
await strata.mutate(() => {
|
|
150
|
+
promise = a.mutate(() => {})
|
|
151
|
+
})
|
|
152
|
+
await promise
|
|
153
|
+
}).throwsAsync()
|
|
154
|
+
}),
|
|
155
|
+
|
|
156
|
+
"forbid mutation in submutation": Science.test(async() => {
|
|
157
|
+
const strata = new Trunk({a: {b: 0}})
|
|
158
|
+
const a = strata.branch(s => s.a)
|
|
159
|
+
await expect(async() => {
|
|
160
|
+
let promise!: Promise<any>
|
|
161
|
+
await a.mutate(() => {
|
|
162
|
+
promise = strata.mutate(() => {})
|
|
163
|
+
})
|
|
164
|
+
await promise
|
|
165
|
+
}).throwsAsync()
|
|
166
|
+
}),
|
|
167
|
+
}),
|
|
168
|
+
|
|
169
|
+
"chronstrata": (() => {
|
|
170
|
+
const setup = () => {
|
|
171
|
+
const strata = new Trunk({
|
|
172
|
+
chron: Trunk.chronicle({count: 0}),
|
|
173
|
+
})
|
|
174
|
+
const chron = strata.chronobranch(64, s => s.chron)
|
|
175
|
+
return {strata, chron}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return Science.suite({
|
|
179
|
+
"get state": Science.test(async() => {
|
|
180
|
+
const {chron} = setup()
|
|
181
|
+
expect(chron.state.count).is(0)
|
|
182
|
+
}),
|
|
183
|
+
|
|
184
|
+
"mutate": Science.test(async() => {
|
|
185
|
+
const {chron} = setup()
|
|
186
|
+
expect(chron.state.count).is(0)
|
|
187
|
+
await chron.mutate(s => s.count++)
|
|
188
|
+
expect(chron.state.count).is(1)
|
|
189
|
+
await chron.mutate(s => s.count++)
|
|
190
|
+
expect(chron.state.count).is(2)
|
|
191
|
+
}),
|
|
192
|
+
|
|
193
|
+
"undoable/redoable": Science.test(async() => {
|
|
194
|
+
const {chron} = setup()
|
|
195
|
+
expect(chron.undoable).is(0)
|
|
196
|
+
expect(chron.redoable).is(0)
|
|
197
|
+
expect(chron.state.count).is(0)
|
|
198
|
+
await chron.mutate(s => s.count++)
|
|
199
|
+
expect(chron.undoable).is(1)
|
|
200
|
+
await chron.mutate(s => s.count++)
|
|
201
|
+
expect(chron.undoable).is(2)
|
|
202
|
+
await chron.undo()
|
|
203
|
+
expect(chron.undoable).is(1)
|
|
204
|
+
expect(chron.redoable).is(1)
|
|
205
|
+
await chron.undo()
|
|
206
|
+
expect(chron.undoable).is(0)
|
|
207
|
+
expect(chron.redoable).is(2)
|
|
208
|
+
await chron.redo()
|
|
209
|
+
expect(chron.undoable).is(1)
|
|
210
|
+
expect(chron.redoable).is(1)
|
|
211
|
+
}),
|
|
212
|
+
|
|
213
|
+
"undo": Science.test(async() => {
|
|
214
|
+
const {chron} = setup()
|
|
215
|
+
await chron.mutate(s => s.count++)
|
|
216
|
+
await chron.undo()
|
|
217
|
+
expect(chron.state.count).is(0)
|
|
218
|
+
}),
|
|
219
|
+
|
|
220
|
+
"redo": Science.test(async() => {
|
|
221
|
+
const {chron} = setup()
|
|
222
|
+
await chron.mutate(s => s.count++)
|
|
223
|
+
await chron.undo()
|
|
224
|
+
expect(chron.state.count).is(0)
|
|
225
|
+
await chron.redo()
|
|
226
|
+
expect(chron.state.count).is(1)
|
|
227
|
+
}),
|
|
228
|
+
|
|
229
|
+
"undo/redo well ordered": Science.test(async() => {
|
|
230
|
+
const {chron} = setup()
|
|
231
|
+
await chron.mutate(s => s.count++)
|
|
232
|
+
await chron.mutate(s => s.count++)
|
|
233
|
+
await chron.mutate(s => s.count++)
|
|
234
|
+
expect(chron.state.count).is(3)
|
|
235
|
+
|
|
236
|
+
await chron.undo()
|
|
237
|
+
expect(chron.state.count).is(2)
|
|
238
|
+
|
|
239
|
+
await chron.undo()
|
|
240
|
+
expect(chron.state.count).is(1)
|
|
241
|
+
|
|
242
|
+
await chron.redo()
|
|
243
|
+
expect(chron.state.count).is(2)
|
|
244
|
+
|
|
245
|
+
await chron.redo()
|
|
246
|
+
expect(chron.state.count).is(3)
|
|
247
|
+
|
|
248
|
+
await chron.undo()
|
|
249
|
+
expect(chron.state.count).is(2)
|
|
250
|
+
|
|
251
|
+
await chron.undo()
|
|
252
|
+
expect(chron.state.count).is(1)
|
|
253
|
+
|
|
254
|
+
await chron.undo()
|
|
255
|
+
expect(chron.state.count).is(0)
|
|
256
|
+
}),
|
|
257
|
+
|
|
258
|
+
"undo nothing does nothing": Science.test(async() => {
|
|
259
|
+
const {chron} = setup()
|
|
260
|
+
await chron.undo()
|
|
261
|
+
expect(chron.state.count).is(0)
|
|
262
|
+
}),
|
|
263
|
+
|
|
264
|
+
"redo nothing does nothing": Science.test(async() => {
|
|
265
|
+
const {chron} = setup()
|
|
266
|
+
await chron.redo()
|
|
267
|
+
expect(chron.state.count).is(0)
|
|
268
|
+
}),
|
|
269
|
+
|
|
270
|
+
"undo 2x": Science.test(async() => {
|
|
271
|
+
const {chron} = setup()
|
|
272
|
+
await chron.mutate(s => s.count++)
|
|
273
|
+
await chron.mutate(s => s.count++)
|
|
274
|
+
expect(chron.state.count).is(2)
|
|
275
|
+
await chron.undo(2)
|
|
276
|
+
expect(chron.state.count).is(0)
|
|
277
|
+
}),
|
|
278
|
+
|
|
279
|
+
"redo 2x": Science.test(async() => {
|
|
280
|
+
const {chron} = setup()
|
|
281
|
+
await chron.mutate(s => s.count++)
|
|
282
|
+
await chron.mutate(s => s.count++)
|
|
283
|
+
expect(chron.state.count).is(2)
|
|
284
|
+
await chron.undo(2)
|
|
285
|
+
expect(chron.state.count).is(0)
|
|
286
|
+
await chron.redo(2)
|
|
287
|
+
expect(chron.state.count).is(2)
|
|
288
|
+
}),
|
|
289
|
+
|
|
290
|
+
"substrata mutations are tracked": Science.test(async() => {
|
|
291
|
+
const strata = new Trunk({
|
|
292
|
+
chron: Trunk.chronicle({
|
|
293
|
+
group: {count: 0},
|
|
294
|
+
}),
|
|
295
|
+
})
|
|
296
|
+
const chron = strata.chronobranch(64, s => s.chron)
|
|
297
|
+
const group = chron.branch(s => s.group)
|
|
298
|
+
expect(group.state.count).is(0)
|
|
299
|
+
await group.mutate(g => g.count = 101)
|
|
300
|
+
expect(group.state.count).is(101)
|
|
301
|
+
await chron.undo()
|
|
302
|
+
expect(group.state.count).is(0)
|
|
303
|
+
}),
|
|
304
|
+
})
|
|
305
|
+
})(),
|
|
306
|
+
})
|
|
307
|
+
|
package/x/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./parts/substrata.js";
|
|
5
|
-
export * from "./parts/types.js";
|
|
1
|
+
export * from "./signals/index.js";
|
|
2
|
+
export * from "./tracker/index.js";
|
|
3
|
+
export * from "./tree/index.js";
|
package/x/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./parts/substrata.js";
|
|
5
|
-
export * from "./parts/types.js";
|
|
1
|
+
export * from "./signals/index.js";
|
|
2
|
+
export * from "./tracker/index.js";
|
|
3
|
+
export * from "./tree/index.js";
|
|
6
4
|
//# sourceMappingURL=index.js.map
|
package/x/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../s/index.ts"],"names":[],"mappings":"AACA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../s/index.ts"],"names":[],"mappings":"AACA,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/signals/index.ts"],"names":[],"mappings":"AACA,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SignalCore } from "./signal.js";
|
|
2
|
+
export type Computed<V> = {
|
|
3
|
+
(): V;
|
|
4
|
+
} & ComputedCore<V>;
|
|
5
|
+
export declare function computed<V>(fn: () => V): Computed<V>;
|
|
6
|
+
export declare class ComputedCore<V> extends SignalCore<V> {
|
|
7
|
+
_dirty: boolean;
|
|
8
|
+
_formula: () => V;
|
|
9
|
+
_effect: (() => void) | undefined;
|
|
10
|
+
constructor(formula: () => V);
|
|
11
|
+
get(): V;
|
|
12
|
+
set(): Promise<void>;
|
|
13
|
+
dispose(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { initEffect } from "./effect.js";
|
|
2
|
+
import { SignalCore } from "./signal.js";
|
|
3
|
+
export function computed(fn) {
|
|
4
|
+
const core = new ComputedCore(fn);
|
|
5
|
+
function f() {
|
|
6
|
+
return f.value;
|
|
7
|
+
}
|
|
8
|
+
Object.setPrototypeOf(f, ComputedCore.prototype);
|
|
9
|
+
Object.assign(f, core);
|
|
10
|
+
return f;
|
|
11
|
+
}
|
|
12
|
+
export class ComputedCore extends SignalCore {
|
|
13
|
+
_dirty = false;
|
|
14
|
+
_formula;
|
|
15
|
+
_effect;
|
|
16
|
+
constructor(formula) {
|
|
17
|
+
super(undefined);
|
|
18
|
+
this._formula = formula;
|
|
19
|
+
}
|
|
20
|
+
get() {
|
|
21
|
+
if (!this._effect) {
|
|
22
|
+
const { result, dispose } = initEffect(this._formula, () => this._dirty = true);
|
|
23
|
+
this._effect = dispose;
|
|
24
|
+
this.sneak = result;
|
|
25
|
+
}
|
|
26
|
+
if (this._dirty) {
|
|
27
|
+
this._dirty = false;
|
|
28
|
+
super.set(this._formula());
|
|
29
|
+
}
|
|
30
|
+
return super.get();
|
|
31
|
+
}
|
|
32
|
+
async set() {
|
|
33
|
+
throw new Error("computed is readonly");
|
|
34
|
+
}
|
|
35
|
+
dispose() {
|
|
36
|
+
if (this._effect)
|
|
37
|
+
this._effect();
|
|
38
|
+
super.dispose();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=computed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed.js","sourceRoot":"","sources":["../../../s/signals/parts/computed.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,UAAU,EAAC,MAAM,aAAa,CAAA;AACtC,OAAO,EAAC,UAAU,EAAC,MAAM,aAAa,CAAA;AAItC,MAAM,UAAU,QAAQ,CAAI,EAAW;IACtC,MAAM,IAAI,GAAG,IAAI,YAAY,CAAI,EAAE,CAAC,CAAA;IAEpC,SAAS,CAAC;QACT,OAAQ,CAAS,CAAC,KAAK,CAAA;IACxB,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAA;IAChD,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;IAEtB,OAAO,CAAgB,CAAA;AACxB,CAAC;AAED,MAAM,OAAO,YAAgB,SAAQ,UAAa;IACjD,MAAM,GAAG,KAAK,CAAA;IACd,QAAQ,CAAS;IACjB,OAAO,CAA0B;IAEjC,YAAY,OAAgB;QAC3B,KAAK,CAAC,SAAgB,CAAC,CAAA;QACvB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACxB,CAAC;IAED,GAAG;QACF,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,EAAC,MAAM,EAAE,OAAO,EAAC,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;YAC7E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;YACtB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAA;QACpB,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;YACnB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC3B,CAAC;QACD,OAAO,KAAK,CAAC,GAAG,EAAE,CAAA;IACnB,CAAC;IAED,KAAK,CAAC,GAAG;QACR,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAA;IACxC,CAAC;IAED,OAAO;QACN,IAAI,IAAI,CAAC,OAAO;YACf,IAAI,CAAC,OAAO,EAAE,CAAA;QACf,KAAK,CAAC,OAAO,EAAE,CAAA;IAChB,CAAC;CACD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { debounce } from "@e280/stz";
|
|
2
|
+
import { tracker } from "../../tracker/tracker.js";
|
|
3
|
+
export function effect(collector, responder = collector) {
|
|
4
|
+
return initEffect(collector, responder).dispose;
|
|
5
|
+
}
|
|
6
|
+
export function initEffect(collector, responder = collector) {
|
|
7
|
+
const { seen, result } = tracker.seen(collector);
|
|
8
|
+
const fn = debounce(0, responder);
|
|
9
|
+
const disposers = [];
|
|
10
|
+
const dispose = () => disposers.forEach(d => d());
|
|
11
|
+
for (const saw of seen) {
|
|
12
|
+
const dispose = tracker.changed(saw, fn);
|
|
13
|
+
disposers.push(dispose);
|
|
14
|
+
}
|
|
15
|
+
return { result, dispose };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=effect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../../s/signals/parts/effect.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAC,MAAM,WAAW,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,0BAA0B,CAAA;AAEhD,MAAM,UAAU,MAAM,CAAW,SAAkB,EAAE,YAAwB,SAAS;IACrF,OAAO,UAAU,CAAI,SAAS,EAAE,SAAS,CAAC,CAAC,OAAO,CAAA;AACnD,CAAC;AAED,MAAM,UAAU,UAAU,CAAW,SAAkB,EAAE,YAAwB,SAAS;IACzF,MAAM,EAAC,IAAI,EAAE,MAAM,EAAC,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IAC9C,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAA;IAEjC,MAAM,SAAS,GAAmB,EAAE,CAAA;IACpC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;IAEjD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;QACxC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACxB,CAAC;IAED,OAAO,EAAC,MAAM,EAAE,OAAO,EAAC,CAAA;AACzB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type Signal<V> = {
|
|
2
|
+
(): V;
|
|
3
|
+
(v: V): Promise<void>;
|
|
4
|
+
(v?: V): V | Promise<void>;
|
|
5
|
+
} & SignalCore<V>;
|
|
6
|
+
export declare function signal<V>(value: V): Signal<V>;
|
|
7
|
+
export declare class SignalCore<V> {
|
|
8
|
+
sneak: V;
|
|
9
|
+
on: import("@e280/stz").Sub<[V]>;
|
|
10
|
+
published: Promise<V>;
|
|
11
|
+
constructor(sneak: V);
|
|
12
|
+
get(): V;
|
|
13
|
+
set(v: V): Promise<void>;
|
|
14
|
+
get value(): V;
|
|
15
|
+
set value(v: V);
|
|
16
|
+
publish(v?: V): Promise<void>;
|
|
17
|
+
dispose(): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { sub } from "@e280/stz";
|
|
2
|
+
import { tracker } from "../../tracker/tracker.js";
|
|
3
|
+
export function signal(value) {
|
|
4
|
+
const core = new SignalCore(value);
|
|
5
|
+
function fn(v) {
|
|
6
|
+
return v !== undefined
|
|
7
|
+
? fn.set(v)
|
|
8
|
+
: fn.get();
|
|
9
|
+
}
|
|
10
|
+
Object.setPrototypeOf(fn, SignalCore.prototype);
|
|
11
|
+
Object.assign(fn, core);
|
|
12
|
+
return fn;
|
|
13
|
+
}
|
|
14
|
+
export class SignalCore {
|
|
15
|
+
sneak;
|
|
16
|
+
on = sub();
|
|
17
|
+
published;
|
|
18
|
+
constructor(sneak) {
|
|
19
|
+
this.sneak = sneak;
|
|
20
|
+
this.published = Promise.resolve(sneak);
|
|
21
|
+
}
|
|
22
|
+
get() {
|
|
23
|
+
tracker.see(this);
|
|
24
|
+
return this.sneak;
|
|
25
|
+
}
|
|
26
|
+
async set(v) {
|
|
27
|
+
if (v !== this.sneak)
|
|
28
|
+
await this.publish(v);
|
|
29
|
+
}
|
|
30
|
+
get value() {
|
|
31
|
+
return this.get();
|
|
32
|
+
}
|
|
33
|
+
set value(v) {
|
|
34
|
+
this.set(v);
|
|
35
|
+
}
|
|
36
|
+
async publish(v = this.get()) {
|
|
37
|
+
this.sneak = v;
|
|
38
|
+
await Promise.all([
|
|
39
|
+
tracker.change(this),
|
|
40
|
+
this.published = this.on.pub(v).then(() => v),
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
dispose() {
|
|
44
|
+
this.on.clear();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=signal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal.js","sourceRoot":"","sources":["../../../s/signals/parts/signal.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAC,MAAM,WAAW,CAAA;AAC7B,OAAO,EAAC,OAAO,EAAC,MAAM,0BAA0B,CAAA;AAQhD,MAAM,UAAU,MAAM,CAAI,KAAQ;IACjC,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAA;IAIlC,SAAS,EAAE,CAAC,CAAK;QAChB,OAAO,CAAC,KAAK,SAAS;YACrB,CAAC,CAAE,EAAU,CAAC,GAAG,CAAC,CAAC,CAAC;YACpB,CAAC,CAAE,EAAU,CAAC,GAAG,EAAE,CAAA;IACrB,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,EAAE,EAAE,UAAU,CAAC,SAAS,CAAC,CAAA;IAC/C,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IAEvB,OAAO,EAAe,CAAA;AACvB,CAAC;AAED,MAAM,OAAO,UAAU;IAIH;IAHnB,EAAE,GAAG,GAAG,EAAO,CAAA;IACf,SAAS,CAAY;IAErB,YAAmB,KAAQ;QAAR,UAAK,GAAL,KAAK,CAAG;QAC1B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC;IAED,GAAG;QACF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACjB,OAAO,IAAI,CAAC,KAAK,CAAA;IAClB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,CAAI;QACb,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK;YACnB,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IACvB,CAAC;IAED,IAAI,KAAK;QACR,OAAO,IAAI,CAAC,GAAG,EAAE,CAAA;IAClB,CAAC;IAED,IAAI,KAAK,CAAC,CAAI;QACb,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IACZ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE;QAC3B,IAAI,CAAC,KAAK,GAAG,CAAC,CAAA;QACd,MAAM,OAAO,CAAC,GAAG,CAAC;YACjB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;YACpB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;SAC7C,CAAC,CAAA;IACH,CAAC;IAED,OAAO;QACN,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAA;IAChB,CAAC;CACD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Science } from "@e280/science";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
"signal get/set value": Science.Test;
|
|
4
|
+
"signal fn syntax": Science.Test;
|
|
5
|
+
"signal syntax interop": Science.Test;
|
|
6
|
+
"signal on is not debounced": Science.Test;
|
|
7
|
+
"signal on only fires on change": Science.Test;
|
|
8
|
+
"effect tracks signal changes": Science.Test;
|
|
9
|
+
"effect is only called when signal actually changes": Science.Test;
|
|
10
|
+
"effects are debounced": Science.Test;
|
|
11
|
+
"effects can be disposed": Science.Test;
|
|
12
|
+
"signal set promise waits for effects": Science.Test;
|
|
13
|
+
"effect only runs on change": Science.Test;
|
|
14
|
+
"computed values": Science.Test;
|
|
15
|
+
"computed is lazy": Science.Test;
|
|
16
|
+
"computed fn syntax": Science.Test;
|
|
17
|
+
};
|
|
18
|
+
export default _default;
|