@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.
Files changed (86) hide show
  1. package/README.md +140 -39
  2. package/package.json +9 -2
  3. package/s/index.ts +3 -5
  4. package/s/signals/index.ts +5 -0
  5. package/s/signals/parts/computed.ts +53 -0
  6. package/s/signals/parts/effect.ts +23 -0
  7. package/s/signals/parts/signal.ts +66 -0
  8. package/s/signals/signals.test.ts +181 -0
  9. package/s/tests.test.ts +45 -286
  10. package/s/tracker/index.ts +3 -0
  11. package/s/tracker/tracker.test.ts +40 -0
  12. package/s/tracker/tracker.ts +73 -0
  13. package/s/tree/index.ts +7 -0
  14. package/s/{parts/substrata.ts → tree/parts/branch.ts} +11 -10
  15. package/s/{parts/chronstrata.ts → tree/parts/chronobranch.ts} +8 -8
  16. package/s/{parts/strata.ts → tree/parts/trunk.ts} +14 -13
  17. package/s/{parts → tree/parts}/types.ts +8 -8
  18. package/s/{parts → tree/parts}/utils/setup.ts +9 -9
  19. package/s/tree/tree.test.ts +307 -0
  20. package/x/index.d.ts +3 -5
  21. package/x/index.js +3 -5
  22. package/x/index.js.map +1 -1
  23. package/x/signals/index.d.ts +3 -0
  24. package/x/signals/index.js +4 -0
  25. package/x/signals/index.js.map +1 -0
  26. package/x/signals/parts/computed.d.ts +14 -0
  27. package/x/signals/parts/computed.js +41 -0
  28. package/x/signals/parts/computed.js.map +1 -0
  29. package/x/signals/parts/effect.d.ts +5 -0
  30. package/x/signals/parts/effect.js +17 -0
  31. package/x/signals/parts/effect.js.map +1 -0
  32. package/x/signals/parts/signal.d.ts +18 -0
  33. package/x/signals/parts/signal.js +47 -0
  34. package/x/signals/parts/signal.js.map +1 -0
  35. package/x/signals/signals.test.d.ts +18 -0
  36. package/x/signals/signals.test.js +144 -0
  37. package/x/signals/signals.test.js.map +1 -0
  38. package/x/tests.test.js +46 -265
  39. package/x/tests.test.js.map +1 -1
  40. package/x/tracker/index.d.ts +1 -0
  41. package/x/tracker/index.js +2 -0
  42. package/x/tracker/index.js.map +1 -0
  43. package/x/tracker/tracker.d.ts +29 -0
  44. package/x/tracker/tracker.js +62 -0
  45. package/x/tracker/tracker.js.map +1 -0
  46. package/x/tracker/tracker.test.d.ts +6 -0
  47. package/x/tracker/tracker.test.js +32 -0
  48. package/x/tracker/tracker.test.js.map +1 -0
  49. package/x/tree/index.d.ts +5 -0
  50. package/x/tree/index.js +6 -0
  51. package/x/tree/index.js.map +1 -0
  52. package/x/tree/parts/branch.d.ts +15 -0
  53. package/x/{parts/substrata.js → tree/parts/branch.js} +10 -9
  54. package/x/tree/parts/branch.js.map +1 -0
  55. package/x/{parts/chronstrata.d.ts → tree/parts/chronobranch.d.ts} +6 -6
  56. package/x/{parts/chronstrata.js → tree/parts/chronobranch.js} +6 -6
  57. package/x/tree/parts/chronobranch.js.map +1 -0
  58. package/x/tree/parts/persistence.js.map +1 -0
  59. package/x/tree/parts/trunk.d.ts +17 -0
  60. package/x/{parts/strata.js → tree/parts/trunk.js} +13 -12
  61. package/x/tree/parts/trunk.js.map +1 -0
  62. package/x/{parts → tree/parts}/types.d.ts +8 -8
  63. package/x/{parts → tree/parts}/types.js.map +1 -1
  64. package/x/tree/parts/utils/process-options.js.map +1 -0
  65. package/x/tree/parts/utils/setup.d.ts +8 -0
  66. package/x/{parts → tree/parts}/utils/setup.js +8 -8
  67. package/x/tree/parts/utils/setup.js.map +1 -0
  68. package/x/tree/tree.test.d.ts +37 -0
  69. package/x/tree/tree.test.js +271 -0
  70. package/x/tree/tree.test.js.map +1 -0
  71. package/x/parts/chronstrata.js.map +0 -1
  72. package/x/parts/persistence.js.map +0 -1
  73. package/x/parts/strata.d.ts +0 -17
  74. package/x/parts/strata.js.map +0 -1
  75. package/x/parts/substrata.d.ts +0 -15
  76. package/x/parts/substrata.js.map +0 -1
  77. package/x/parts/utils/process-options.js.map +0 -1
  78. package/x/parts/utils/setup.d.ts +0 -8
  79. package/x/parts/utils/setup.js.map +0 -1
  80. /package/s/{parts → tree/parts}/persistence.ts +0 -0
  81. /package/s/{parts → tree/parts}/utils/process-options.ts +0 -0
  82. /package/x/{parts → tree/parts}/persistence.d.ts +0 -0
  83. /package/x/{parts → tree/parts}/persistence.js +0 -0
  84. /package/x/{parts → tree/parts}/types.js +0 -0
  85. /package/x/{parts → tree/parts}/utils/process-options.d.ts +0 -0
  86. /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 {Strata} from "../strata.js"
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 strataSetup<S extends State>(options: SetupOptions<S>) {
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("strata"),
13
+ persistence = localPersistence("strataTree"),
14
14
  } = options
15
15
 
16
- const strata = new Strata<S>(initialState)
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 strata.overwrite(pickle.state)
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: strata.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
- strata.watch(save)
33
+ trunk.watch(save)
34
34
 
35
35
  // cross-tab sync
36
36
  const dispose = persistence.onChange(load)
37
37
 
38
- return {strata, load, save, dispose}
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 "./parts/chronstrata.js";
2
- export * from "./parts/persistence.js";
3
- export * from "./parts/strata.js";
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 "./parts/chronstrata.js";
2
- export * from "./parts/persistence.js";
3
- export * from "./parts/strata.js";
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,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA"}
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,3 @@
1
+ export * from "./parts/computed.js";
2
+ export * from "./parts/effect.js";
3
+ export * from "./parts/signal.js";
@@ -0,0 +1,4 @@
1
+ export * from "./parts/computed.js";
2
+ export * from "./parts/effect.js";
3
+ export * from "./parts/signal.js";
4
+ //# sourceMappingURL=index.js.map
@@ -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,5 @@
1
+ export declare function effect<C = void>(collector: () => C, responder?: () => void): () => void;
2
+ export declare function initEffect<C = void>(collector: () => C, responder?: () => void): {
3
+ result: C;
4
+ dispose: () => void;
5
+ };
@@ -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;