@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
package/s/tests.test.ts
CHANGED
|
@@ -1,307 +1,66 @@
|
|
|
1
1
|
|
|
2
|
-
import {expect, Science} from "@e280/science"
|
|
3
|
-
import {Strata} from "./parts/strata.js"
|
|
2
|
+
import {expect, Science, test} from "@e280/science"
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const strata = new Strata({count: 0})
|
|
9
|
-
expect(strata.state.count).is(0)
|
|
10
|
-
}),
|
|
11
|
-
|
|
12
|
-
"state is immutable": Science.test(async() => {
|
|
13
|
-
const strata = new Strata({count: 0})
|
|
14
|
-
expect(() => strata.state.count++).throws()
|
|
15
|
-
}),
|
|
16
|
-
|
|
17
|
-
"run a proper mutation": Science.test(async() => {
|
|
18
|
-
const strata = new Strata({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
|
-
}),
|
|
4
|
+
import tree from "./tree/tree.test.js"
|
|
5
|
+
import signals from "./signals/signals.test.js"
|
|
6
|
+
import tracker from "./tracker/tracker.test.js"
|
|
25
7
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let promise!: Promise<any>
|
|
30
|
-
await strata.mutate(() => {
|
|
31
|
-
promise = strata.mutate(() => {})
|
|
32
|
-
})
|
|
33
|
-
await promise
|
|
34
|
-
}).throwsAsync()
|
|
35
|
-
}),
|
|
8
|
+
import {Trunk} from "./tree/parts/trunk.js"
|
|
9
|
+
import {effect} from "./signals/parts/effect.js"
|
|
10
|
+
import {signal} from "./signals/parts/signal.js"
|
|
36
11
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}),
|
|
12
|
+
await Science.run({
|
|
13
|
+
tree,
|
|
14
|
+
signals,
|
|
15
|
+
tracker,
|
|
42
16
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
strata.watch.sub(() => {mutationCount++})
|
|
47
|
-
await strata.mutate(state => state.count++)
|
|
48
|
-
expect(mutationCount).is(1)
|
|
49
|
-
}),
|
|
17
|
+
interop: Science.suite({
|
|
18
|
+
"effect responds to trunk change": test(async() => {
|
|
19
|
+
const trunk = new Trunk({count: 1})
|
|
50
20
|
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
}),
|
|
21
|
+
let copy = 0
|
|
22
|
+
expect(copy).is(0)
|
|
60
23
|
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
}),
|
|
24
|
+
effect(() => copy = trunk.state.count)
|
|
25
|
+
expect(copy).is(1)
|
|
69
26
|
|
|
70
|
-
|
|
71
|
-
|
|
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)
|
|
27
|
+
await trunk.mutate(s => s.count++)
|
|
28
|
+
expect(copy).is(2)
|
|
83
29
|
}),
|
|
84
|
-
}),
|
|
85
30
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const substrata = strata.substrata(s => s.sub)
|
|
90
|
-
expect(substrata.state.rofls).is(0)
|
|
91
|
-
}),
|
|
31
|
+
"signal.set participates in flush": test(async() => {
|
|
32
|
+
let order: string[] = []
|
|
33
|
+
const count = signal(0)
|
|
92
34
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
35
|
+
effect(() => {
|
|
36
|
+
if (count.value)
|
|
37
|
+
order.push("effect")
|
|
96
38
|
})
|
|
97
|
-
const a = strata.substrata(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
39
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const b = a.substrata(s => s.b)
|
|
112
|
-
expect(strata.state.a.b.c).is(0)
|
|
113
|
-
expect(b.state.c).is(0)
|
|
114
|
-
}),
|
|
40
|
+
order.push("before")
|
|
41
|
+
await count.set(1)
|
|
42
|
+
order.push("after")
|
|
115
43
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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)
|
|
44
|
+
expect(order.length).is(3)
|
|
45
|
+
expect(order[0]).is("before")
|
|
46
|
+
expect(order[1]).is("effect")
|
|
47
|
+
expect(order[2]).is("after")
|
|
132
48
|
}),
|
|
133
49
|
|
|
134
|
-
"
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
expect(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"forbid submutation in mutation": Science.test(async() => {
|
|
145
|
-
const strata = new Strata({a: {b: 0}})
|
|
146
|
-
const a = strata.substrata(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
|
-
}),
|
|
50
|
+
"branch can include signal value": test.skip(async() => {
|
|
51
|
+
const bingus = signal(101)
|
|
52
|
+
const trunk = new Trunk({count: 1})
|
|
53
|
+
const branch = trunk.branch(s => ({
|
|
54
|
+
count: s.count,
|
|
55
|
+
bingus: bingus.value,
|
|
56
|
+
}))
|
|
57
|
+
expect(branch.state.count).is(1)
|
|
58
|
+
expect(branch.state.bingus).is(101)
|
|
155
59
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
await expect(async() => {
|
|
160
|
-
let promise!: Promise<any>
|
|
161
|
-
await a.mutate(() => {
|
|
162
|
-
promise = strata.mutate(() => {})
|
|
163
|
-
})
|
|
164
|
-
await promise
|
|
165
|
-
}).throwsAsync()
|
|
60
|
+
await bingus.set(102)
|
|
61
|
+
expect(branch.state.count).is(1)
|
|
62
|
+
expect(branch.state.bingus).is(102)
|
|
166
63
|
}),
|
|
167
64
|
}),
|
|
168
|
-
|
|
169
|
-
"chronstrata": (() => {
|
|
170
|
-
const setup = () => {
|
|
171
|
-
const strata = new Strata({
|
|
172
|
-
chron: Strata.chronicle({count: 0}),
|
|
173
|
-
})
|
|
174
|
-
const chron = strata.chronstrata(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 Strata({
|
|
292
|
-
chron: Strata.chronicle({
|
|
293
|
-
group: {count: 0},
|
|
294
|
-
}),
|
|
295
|
-
})
|
|
296
|
-
const chron = strata.chronstrata(64, s => s.chron)
|
|
297
|
-
const group = chron.substrata(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
65
|
})
|
|
307
66
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
import {Science, test, expect} from "@e280/science"
|
|
3
|
+
import {Tracker} from "./tracker.js"
|
|
4
|
+
|
|
5
|
+
export default Science.suite({
|
|
6
|
+
"change waits for downstream effects to settle": test(async() => {
|
|
7
|
+
const tracker = new Tracker()
|
|
8
|
+
let order: string[] = []
|
|
9
|
+
|
|
10
|
+
const item = {}
|
|
11
|
+
tracker.changed(item, async () => {
|
|
12
|
+
await Promise.resolve()
|
|
13
|
+
order.push("effect")
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
order.push("before")
|
|
17
|
+
await tracker.change(item)
|
|
18
|
+
order.push("after")
|
|
19
|
+
|
|
20
|
+
expect(order.length).is(3)
|
|
21
|
+
expect(order[0]).is("before")
|
|
22
|
+
expect(order[1]).is("effect")
|
|
23
|
+
expect(order[2]).is("after")
|
|
24
|
+
}),
|
|
25
|
+
|
|
26
|
+
"circularity forbidden": test(async() => {
|
|
27
|
+
const tracker = new Tracker()
|
|
28
|
+
const item = {}
|
|
29
|
+
|
|
30
|
+
// effect re-publishes the same change, creating a cycle
|
|
31
|
+
tracker.changed(item, async() => {
|
|
32
|
+
await tracker.change(item)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
expect(async() => {
|
|
36
|
+
await tracker.change(item)
|
|
37
|
+
}).throwsAsync()
|
|
38
|
+
}),
|
|
39
|
+
})
|
|
40
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
|
|
2
|
+
import {sub, Sub} from "@e280/stz"
|
|
3
|
+
|
|
4
|
+
export type TrackableItem = object | symbol
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* tracking system for state management
|
|
8
|
+
* - it tracks when items are seen or changed
|
|
9
|
+
*
|
|
10
|
+
* for state item integration (like you're integrating a new kind of state object)
|
|
11
|
+
* - items can call `tracker.see(this)` when they are accessed
|
|
12
|
+
* - items can call `tracker.change(this)` when they are reassigned
|
|
13
|
+
*
|
|
14
|
+
* for reactivity integration (like you're integrating a new view library that reacts to state changes)
|
|
15
|
+
* - run `tracker.seen(renderFn)`, collecting a set of seen items
|
|
16
|
+
* - loop over each seen item, attach a changed handler `tracker.changed(item, handlerFn)`
|
|
17
|
+
*/
|
|
18
|
+
export class Tracker<Item extends TrackableItem = any> {
|
|
19
|
+
#seeables: Set<Item>[] = []
|
|
20
|
+
#changeables = new WeakMap<Item, Sub>()
|
|
21
|
+
#changeStack: Set<Promise<void>>[] = []
|
|
22
|
+
#busy = new Set<Item>()
|
|
23
|
+
|
|
24
|
+
/** indicate item was accessed */
|
|
25
|
+
see(item: Item) {
|
|
26
|
+
this.#seeables.at(-1)?.add(item)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** collect which items were seen during fn */
|
|
30
|
+
seen<R>(fn: () => R) {
|
|
31
|
+
this.#seeables.push(new Set())
|
|
32
|
+
const result = fn()
|
|
33
|
+
const seen = this.#seeables.pop()!
|
|
34
|
+
return {seen, result}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** indicate item was changed */
|
|
38
|
+
async change(item: Item) {
|
|
39
|
+
if (this.#busy.has(item))
|
|
40
|
+
throw new Error("circularity forbidden")
|
|
41
|
+
const prom = this.#guaranteeChangeable(item).pub()
|
|
42
|
+
this.#changeStack.at(-1)?.add(prom)
|
|
43
|
+
return prom
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** respond to changes by calling fn */
|
|
47
|
+
changed(item: Item, fn: () => Promise<void>) {
|
|
48
|
+
return this.#guaranteeChangeable(item)(async() => {
|
|
49
|
+
const collected = new Set<Promise<void>>()
|
|
50
|
+
this.#changeStack.push(collected)
|
|
51
|
+
this.#busy.add(item)
|
|
52
|
+
collected.add(fn())
|
|
53
|
+
this.#busy.delete(item)
|
|
54
|
+
await Promise.all(collected)
|
|
55
|
+
this.#changeStack.pop()
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#guaranteeChangeable(item: Item) {
|
|
60
|
+
let on = this.#changeables.get(item)
|
|
61
|
+
if (!on) {
|
|
62
|
+
on = sub()
|
|
63
|
+
this.#changeables.set(item, on)
|
|
64
|
+
}
|
|
65
|
+
return on
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const key = Symbol.for("e280.tracker.v2")
|
|
70
|
+
|
|
71
|
+
/** standard global tracker for integrations */
|
|
72
|
+
export const tracker: Tracker = (globalThis as any)[key] ??= new Tracker()
|
|
73
|
+
|
package/s/tree/index.ts
ADDED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
|
|
2
|
-
import {debounce, deep, sub
|
|
2
|
+
import {debounce, deep, sub} from "@e280/stz"
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import {Chronobranch} from "./chronobranch.js"
|
|
5
|
+
import {tracker} from "../../tracker/tracker.js"
|
|
6
|
+
import {Chronicle, Mutator, Options, Selector, Tree, Branchstate} from "./types.js"
|
|
6
7
|
|
|
7
|
-
export class
|
|
8
|
+
export class Branch<S extends Branchstate, ParentState extends Branchstate = any> implements Tree<S> {
|
|
8
9
|
dispose: () => void
|
|
9
10
|
watch = sub<[state: S]>()
|
|
10
11
|
|
|
11
12
|
#immutable: S
|
|
12
13
|
#dispatchMutation = debounce(0, async(state: S) => {
|
|
13
14
|
await this.watch.pub(state)
|
|
14
|
-
tracker.change(this)
|
|
15
|
+
await tracker.change(this)
|
|
15
16
|
})
|
|
16
17
|
|
|
17
18
|
constructor(
|
|
18
|
-
private parent:
|
|
19
|
+
private parent: Tree<ParentState>,
|
|
19
20
|
private selector: Selector<S, ParentState>,
|
|
20
21
|
private options: Options,
|
|
21
22
|
) {
|
|
@@ -49,15 +50,15 @@ export class Substrata<S extends Substate, ParentState extends Substate = any> i
|
|
|
49
50
|
return this.#immutable
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
return new
|
|
53
|
+
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
54
|
+
return new Branch(this, selector, this.options)
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
chronobranch<Sub extends Branchstate>(
|
|
57
58
|
limit: number,
|
|
58
59
|
selector: Selector<Chronicle<Sub>, S>,
|
|
59
60
|
) {
|
|
60
|
-
return new
|
|
61
|
+
return new Chronobranch(limit, this, selector, this.options)
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
import {Chronicle, Mutator, Options, Selector,
|
|
2
|
+
import {Branch} from "./branch.js"
|
|
3
|
+
import {Chronicle, Mutator, Options, Selector, Tree, Branchstate} from "./types.js"
|
|
4
4
|
|
|
5
|
-
export class
|
|
6
|
-
#substrata:
|
|
5
|
+
export class Chronobranch<S extends Branchstate, ParentState extends Branchstate = any> implements Tree<S> {
|
|
6
|
+
#substrata: Branch<Chronicle<S>, ParentState>
|
|
7
7
|
|
|
8
8
|
constructor(
|
|
9
9
|
public limit: number,
|
|
10
|
-
public parent:
|
|
10
|
+
public parent: Tree<ParentState>,
|
|
11
11
|
public selector: Selector<Chronicle<S>, ParentState>,
|
|
12
12
|
public options: Options,
|
|
13
13
|
) {
|
|
14
|
-
this.#substrata = parent.
|
|
14
|
+
this.#substrata = parent.branch(selector)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
get state() {
|
|
@@ -77,8 +77,8 @@ export class Chronstrata<S extends Substate, ParentState extends Substate = any>
|
|
|
77
77
|
})
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
return new
|
|
80
|
+
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
81
|
+
return new Branch(this, selector, this.options)
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
|
|
2
|
-
import {debounce, deep, sub
|
|
2
|
+
import {debounce, deep, sub} from "@e280/stz"
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import {Branch} from "./branch.js"
|
|
5
|
+
import {trunkSetup} from "./utils/setup.js"
|
|
6
|
+
import {Chronobranch} from "./chronobranch.js"
|
|
7
|
+
import {tracker} from "../../tracker/tracker.js"
|
|
7
8
|
import {processOptions} from "./utils/process-options.js"
|
|
8
|
-
import {Chronicle, Mutator, Options, Selector,
|
|
9
|
+
import {Chronicle, Mutator, Options, Selector, Treestate, Tree, Branchstate} from "./types.js"
|
|
9
10
|
|
|
10
|
-
export class
|
|
11
|
-
static setup =
|
|
12
|
-
static chronicle = <S extends
|
|
11
|
+
export class Trunk<S extends Treestate> implements Tree<S> {
|
|
12
|
+
static setup = trunkSetup
|
|
13
|
+
static chronicle = <S extends Branchstate>(state: S): Chronicle<S> => ({
|
|
13
14
|
present: state,
|
|
14
15
|
past: [],
|
|
15
16
|
future: [],
|
|
@@ -52,22 +53,22 @@ export class Strata<S extends State> implements Stratum<S> {
|
|
|
52
53
|
await this.#dispatchMutation()
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
return new
|
|
56
|
+
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S> {
|
|
57
|
+
return new Branch(this, selector, this.options)
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
chronobranch<Sub extends Branchstate>(
|
|
60
61
|
limit: number,
|
|
61
62
|
selector: Selector<Chronicle<Sub>, S>,
|
|
62
63
|
) {
|
|
63
|
-
return new
|
|
64
|
+
return new Chronobranch(limit, this, selector, this.options)
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
#dispatchMutation = debounce(0, async() => {
|
|
67
68
|
this.#mutationLock++
|
|
68
69
|
try { await this.watch.pub(this.#immutable) }
|
|
69
70
|
finally { this.#mutationLock-- }
|
|
70
|
-
tracker.change(this)
|
|
71
|
+
await tracker.change(this)
|
|
71
72
|
})
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
2
|
+
import {Branch} from "./branch.js"
|
|
3
3
|
|
|
4
4
|
export type Options = {
|
|
5
5
|
clone: <X>(x: X) => X
|
|
@@ -8,29 +8,29 @@ export type Options = {
|
|
|
8
8
|
export type Selector<Sub, S> = (state: S) => Sub
|
|
9
9
|
export type Mutator<S> = (state: S) => void
|
|
10
10
|
|
|
11
|
-
export type
|
|
12
|
-
export type
|
|
11
|
+
export type Treestate = {}
|
|
12
|
+
export type Branchstate = {} | null | undefined
|
|
13
13
|
|
|
14
|
-
export type Versioned<S extends
|
|
14
|
+
export type Versioned<S extends Treestate> = {
|
|
15
15
|
state: S
|
|
16
16
|
version: number
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export type
|
|
19
|
+
export type Tree<S extends Branchstate> = {
|
|
20
20
|
readonly state: S
|
|
21
21
|
watch(fn: (s: S) => void): () => void
|
|
22
22
|
mutate(mutator: Mutator<S>): Promise<S>
|
|
23
|
-
|
|
23
|
+
branch<Sub extends Branchstate>(selector: Selector<Sub, S>): Branch<Sub, S>
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export type SetupOptions<S extends
|
|
26
|
+
export type SetupOptions<S extends Treestate> = {
|
|
27
27
|
version: number
|
|
28
28
|
initialState: S
|
|
29
29
|
saveDebounceTime?: number
|
|
30
30
|
persistence?: Persistence<Versioned<S>>
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export type Chronicle<S extends
|
|
33
|
+
export type Chronicle<S extends Branchstate> = {
|
|
34
34
|
// [abc] d [efg]
|
|
35
35
|
// \ \ \
|
|
36
36
|
// \ \ future
|