@e280/strata 0.0.0-7 → 0.0.0-9

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 (67) hide show
  1. package/README.md +27 -24
  2. package/package.json +9 -10
  3. package/s/signals/index.ts +2 -1
  4. package/s/signals/parts/derive.ts +29 -0
  5. package/s/signals/parts/effect.ts +3 -3
  6. package/s/signals/parts/lazy.ts +27 -0
  7. package/s/signals/parts/signal.ts +21 -43
  8. package/s/signals/parts/types.ts +11 -0
  9. package/s/signals/parts/units.ts +150 -0
  10. package/s/signals/signals.test.ts +103 -8
  11. package/s/tests.test.ts +1 -1
  12. package/s/tree/index.ts +1 -1
  13. package/s/tree/parts/branch.ts +17 -40
  14. package/s/tree/parts/chronobranch.ts +13 -13
  15. package/s/tree/parts/trunk.ts +27 -29
  16. package/s/tree/parts/types.ts +18 -6
  17. package/s/tree/parts/utils/setup.ts +4 -4
  18. package/s/tree/tree.test.ts +77 -68
  19. package/x/signals/index.d.ts +2 -1
  20. package/x/signals/index.js +2 -1
  21. package/x/signals/index.js.map +1 -1
  22. package/x/signals/parts/derive.d.ts +12 -0
  23. package/x/signals/parts/derive.js +12 -0
  24. package/x/signals/parts/derive.js.map +1 -0
  25. package/x/signals/parts/effect.d.ts +2 -2
  26. package/x/signals/parts/effect.js +2 -2
  27. package/x/signals/parts/effect.js.map +1 -1
  28. package/x/signals/parts/lazy.d.ts +10 -0
  29. package/x/signals/parts/lazy.js +12 -0
  30. package/x/signals/parts/lazy.js.map +1 -0
  31. package/x/signals/parts/signal.d.ts +11 -8
  32. package/x/signals/parts/signal.js +8 -37
  33. package/x/signals/parts/signal.js.map +1 -1
  34. package/x/signals/parts/types.d.ts +7 -0
  35. package/x/signals/parts/types.js +2 -0
  36. package/x/signals/parts/types.js.map +1 -0
  37. package/x/signals/parts/units.d.ts +43 -0
  38. package/x/signals/parts/units.js +131 -0
  39. package/x/signals/parts/units.js.map +1 -0
  40. package/x/signals/signals.test.d.ts +8 -3
  41. package/x/signals/signals.test.js +87 -8
  42. package/x/signals/signals.test.js.map +1 -1
  43. package/x/tests.test.js +1 -1
  44. package/x/tests.test.js.map +1 -1
  45. package/x/tree/index.d.ts +1 -1
  46. package/x/tree/index.js +1 -1
  47. package/x/tree/index.js.map +1 -1
  48. package/x/tree/parts/branch.d.ts +4 -7
  49. package/x/tree/parts/branch.js +11 -30
  50. package/x/tree/parts/branch.js.map +1 -1
  51. package/x/tree/parts/chronobranch.d.ts +4 -4
  52. package/x/tree/parts/chronobranch.js +12 -12
  53. package/x/tree/parts/chronobranch.js.map +1 -1
  54. package/x/tree/parts/trunk.d.ts +5 -5
  55. package/x/tree/parts/trunk.js +18 -29
  56. package/x/tree/parts/trunk.js.map +1 -1
  57. package/x/tree/parts/types.d.ts +12 -6
  58. package/x/tree/parts/utils/setup.d.ts +2 -2
  59. package/x/tree/parts/utils/setup.js +1 -1
  60. package/x/tree/parts/utils/setup.js.map +1 -1
  61. package/x/tree/tree.test.d.ts +7 -7
  62. package/x/tree/tree.test.js +76 -68
  63. package/x/tree/tree.test.js.map +1 -1
  64. package/s/signals/parts/computed.ts +0 -53
  65. package/x/signals/parts/computed.d.ts +0 -14
  66. package/x/signals/parts/computed.js +0 -41
  67. package/x/signals/parts/computed.js.map +0 -1
package/README.md CHANGED
@@ -8,6 +8,7 @@
8
8
  ### get in loser, we're managing state
9
9
  📦 `npm install @e280/strata`
10
10
  🧙‍♂️ probably my tenth state management library, lol
11
+ 💁 it's all about rerendering ui when data changes
11
12
 
12
13
  🚦 **signals** — ephemeral view-level state
13
14
  🌳 **tree** — persistent app-level state
@@ -43,9 +44,6 @@ import {signal, effect, computed} from "@e280/strata"
43
44
  ```ts
44
45
  await count(2)
45
46
  ```
46
- - **signals are for auto rerendering your ui.**
47
- components/views will auto rerender when relevant signals change
48
- — well only if your ui lib is cool and integrates `tracker`.
49
47
 
50
48
  ### pick your poison
51
49
  - **signals hipster fn syntax**
@@ -53,6 +51,7 @@ import {signal, effect, computed} from "@e280/strata"
53
51
  count() // get
54
52
  await count(2) // set
55
53
  ```
54
+ > to achieve this hipster syntax i had to make the implementation so damn cursed, lol 💀
56
55
  - **signals get/set syntax**
57
56
  ```ts
58
57
  count.get() // get
@@ -80,33 +79,36 @@ import {signal, effect, computed} from "@e280/strata"
80
79
  // 2
81
80
  // when count is changed, the effect fn is run
82
81
  ```
83
- - **computed signals are super lazy**
84
- they only run if and when you get the value
82
+
83
+ ### `signal.derive` and `signal.lazy` are computed signals
84
+ - **signal.derive**
85
+ is for combining signals
85
86
  ```ts
86
- const tenly = computed(() => {
87
- console.log("recomputed!")
88
- return count() * 10
89
- })
87
+ const a = signal(1)
88
+ const b = signal(10)
89
+ const product = signal.derive(() => a() * b())
90
90
 
91
- console.log(tenly())
92
- // "recomputed!"
93
- // 20
91
+ product() // 10
94
92
 
95
- await count(3)
93
+ // change a dependency,
94
+ // and the derived signal is automatically updated
95
+ await a.set(2)
96
96
 
97
- console.log(tenly.value)
98
- // "recomputed!"
99
- // 30
97
+ product() // 20
100
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!*
101
104
 
102
105
  <br/>
103
106
 
104
107
  ## 🌳 tree — *persistent app-level state*
105
108
  - single-source-of-truth state tree
106
109
  - immutable except for `mutate(fn)` calls
107
- - undo/redo history, cross-tab sync, localStorage persistence
110
+ - localStorage persistence, cross-tab sync, undo/redo history
108
111
  - no spooky-dookie proxy magic — just god's honest javascript
109
- - separate but compatible with signals
110
112
 
111
113
  #### `Trunk` is your app's state tree root
112
114
  - better stick to json-friendly serializable data
@@ -150,28 +152,29 @@ import {signal, effect, computed} from "@e280/strata"
150
152
  ```
151
153
  - you can branch a branch
152
154
 
153
- #### watch for mutations
155
+ #### `on` to watch for mutations
154
156
  - on the trunk, we can listen deeply for mutations within the whole tree
155
157
  ```ts
156
- trunk.watch(s => console.log(s.count))
158
+ trunk.on(s => console.log(s.count))
157
159
  ```
158
160
  - whereas branch listeners don't care about changes outside their scope
159
161
  ```ts
160
- snacks.watch(s => console.log(s.peanuts))
162
+ snacks.on(s => console.log(s.peanuts))
161
163
  ```
162
- - watch returns a fn to stop listening
164
+ - on returns a fn to stop listening
163
165
  ```ts
164
- const stop = trunk.watch(s => console.log(s.count))
166
+ const stop = trunk.on(s => console.log(s.count))
165
167
  stop() // stop listening
166
168
  ```
167
169
 
168
170
  ### only discerning high-class aristocrats are permitted beyond this point
169
171
 
170
172
  #### `Trunk.setup` for localStorage persistence etc
173
+ - it automatically handles persistence to localStorage and cross-tab synchronization
171
174
  - simple setup
172
175
  ```ts
173
176
  const {trunk} = await Trunk.setup({
174
- version: 1, // 👈 bump whenever your change state schema!
177
+ version: 1, // 👈 bump whenever you change state schema!
175
178
  initialState: {count: 0},
176
179
  })
177
180
  ```
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@e280/strata",
3
- "version": "0.0.0-7",
3
+ "version": "0.0.0-9",
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"
@@ -14,8 +14,7 @@
14
14
  ".": "./x/index.js",
15
15
  "./signals": "./x/signals/index.js",
16
16
  "./tracker": "./x/tracker/index.js",
17
- "./tree": "./x/tree/index.js",
18
- "./*": "./*"
17
+ "./tree": "./x/tree/index.js"
19
18
  },
20
19
  "scripts": {
21
20
  "build": "run-s _clean _links _tsc",
@@ -29,11 +28,14 @@
29
28
  "_tsc": "tsc",
30
29
  "_tscw": "tsc -w"
31
30
  },
31
+ "dependencies": {
32
+ "@e280/stz": "^0.0.0-34"
33
+ },
32
34
  "devDependencies": {
33
- "@e280/science": "^0.0.5",
34
- "@types/node": "^24.0.7",
35
+ "@e280/science": "^0.0.6",
36
+ "@types/node": "^24.2.0",
35
37
  "npm-run-all": "^4.1.5",
36
- "typescript": "^5.8.3"
38
+ "typescript": "^5.9.2"
37
39
  },
38
40
  "keywords": [
39
41
  "state",
@@ -46,8 +48,5 @@
46
48
  },
47
49
  "bugs": {
48
50
  "url": "https://github.com/e280/strata/issues"
49
- },
50
- "dependencies": {
51
- "@e280/stz": "^0.0.0-27"
52
51
  }
53
52
  }
@@ -1,5 +1,6 @@
1
1
 
2
- export * from "./parts/computed.js"
2
+ export * from "./parts/lazy.js"
3
3
  export * from "./parts/effect.js"
4
4
  export * from "./parts/signal.js"
5
+ export * from "./parts/types.js"
5
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
+
@@ -2,11 +2,11 @@
2
2
  import {debounce} from "@e280/stz"
3
3
  import {tracker} from "../../tracker/tracker.js"
4
4
 
5
- export function effect<C = void>(collector: () => C, responder: () => void = collector) {
6
- return initEffect<C>(collector, responder).dispose
5
+ export function effect(collector: () => void, responder: () => void = collector) {
6
+ return collectorEffect(collector, responder).dispose
7
7
  }
8
8
 
9
- export function initEffect<C = void>(collector: () => C, responder: () => void = collector) {
9
+ export function collectorEffect<C = void>(collector: () => C, responder: () => void = collector) {
10
10
  const {seen, result} = tracker.seen(collector)
11
11
  const fn = debounce(0, responder)
12
12
 
@@ -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
+
@@ -1,16 +1,28 @@
1
1
 
2
- import {sub} from "@e280/stz"
3
- import {tracker} from "../../tracker/tracker.js"
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"
4
8
 
5
9
  export type Signal<V> = {
6
10
  (): V
7
11
  (v: V): Promise<void>
8
12
  (v?: V): V | Promise<void>
9
- } & SignalCore<V>
10
13
 
11
- export function signal<V>(value: V) {
12
- const core = new SignalCore(value)
14
+ kind: "signal"
13
15
 
16
+ sneak: V
17
+ value: V
18
+ on: Sub<[V]>
19
+ get(): V
20
+ set(v: V): Promise<void>
21
+ publish(v?: V): Promise<void>
22
+ dispose(): void
23
+ } & SignalCore<V>
24
+
25
+ export function signal<V>(value: V, options: Partial<SignalOptions> = {}) {
14
26
  function fn(): V
15
27
  function fn(v: V): Promise<void>
16
28
  function fn(v?: V): V | Promise<void> {
@@ -19,48 +31,14 @@ export function signal<V>(value: V) {
19
31
  : (fn as any).get()
20
32
  }
21
33
 
34
+ const o = processSignalOptions(options)
35
+ const core = new SignalCore(value, o)
22
36
  Object.setPrototypeOf(fn, SignalCore.prototype)
23
37
  Object.assign(fn, core)
24
38
 
25
39
  return fn as Signal<V>
26
40
  }
27
41
 
28
- export class SignalCore<V> {
29
- on = sub<[V]>()
30
- published: Promise<V>
31
-
32
- constructor(public sneak: V) {
33
- this.published = Promise.resolve(sneak)
34
- }
35
-
36
- get() {
37
- tracker.see(this)
38
- return this.sneak
39
- }
40
-
41
- async set(v: V) {
42
- if (v !== this.sneak)
43
- await this.publish(v)
44
- }
45
-
46
- get value() {
47
- return this.get()
48
- }
49
-
50
- set value(v: V) {
51
- this.set(v)
52
- }
53
-
54
- async publish(v = this.get()) {
55
- this.sneak = v
56
- await Promise.all([
57
- tracker.change(this),
58
- this.published = this.on.pub(v).then(() => v),
59
- ])
60
- }
61
-
62
- dispose() {
63
- this.on.clear()
64
- }
65
- }
42
+ signal.lazy = lazy
43
+ signal.derive = derive
66
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,150 @@
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
+ }
49
+
50
+ get value() {
51
+ return this.get()
52
+ }
53
+
54
+ set value(v: V) {
55
+ this.set(v)
56
+ }
57
+
58
+ async publish(v = this.get()) {
59
+ if (this._lock)
60
+ throw new Error("forbid circularity")
61
+
62
+ let promise = Promise.resolve()
63
+
64
+ try {
65
+ this._lock = true
66
+ this.sneak = v
67
+ promise = Promise.all([
68
+ tracker.change(this),
69
+ this.on.pub(v),
70
+ ]) as any
71
+ }
72
+ finally {
73
+ this._lock = false
74
+ }
75
+
76
+ return promise
77
+ }
78
+ }
79
+
80
+ export class LazyCore<V> extends ReadableSignal<V> {
81
+ kind: "lazy" = "lazy"
82
+
83
+ _dirty = false
84
+ _effect: (() => void) | undefined
85
+
86
+ constructor(public _formula: () => V, public _options: SignalOptions) {
87
+ super(undefined as any)
88
+ }
89
+
90
+ get() {
91
+ if (!this._effect) {
92
+ const {result, dispose} = collectorEffect(this._formula, () => this._dirty = true)
93
+ this._effect = dispose
94
+ this.sneak = result
95
+ }
96
+ if (this._dirty) {
97
+ this._dirty = false
98
+
99
+ const v = this._formula()
100
+ const isChanged = !this._options.compare(this.sneak, v)
101
+ if (isChanged) {
102
+ this.sneak = v
103
+ tracker.change(this)
104
+ }
105
+ }
106
+ return super.get()
107
+ }
108
+
109
+ get value() {
110
+ return this.get()
111
+ }
112
+
113
+ dispose() {
114
+ if (this._effect)
115
+ this._effect()
116
+ }
117
+ }
118
+
119
+ export class DerivedCore<V> extends ReactiveSignal<V> {
120
+ static make<V>(that: DerivedCore<V>, formula: () => V, options: SignalOptions) {
121
+ const {result, dispose} = collectorEffect(formula, async() => {
122
+ const value = formula()
123
+ const isChanged = !options.compare(that.sneak, value)
124
+ if (isChanged) {
125
+ that.sneak = value
126
+ await Promise.all([
127
+ tracker.change(that),
128
+ that.on.pub(value),
129
+ ])
130
+ }
131
+ })
132
+ return new this(result, dispose)
133
+ }
134
+
135
+ kind: "derived" = "derived"
136
+
137
+ constructor(initialValue: V, public _effect: () => void) {
138
+ super(initialValue)
139
+ }
140
+
141
+ get value() {
142
+ return this.get()
143
+ }
144
+
145
+ dispose() {
146
+ super.dispose()
147
+ this._effect()
148
+ }
149
+ }
150
+
@@ -1,9 +1,9 @@
1
1
 
2
- import {Science, test, expect} from "@e280/science"
2
+ import {Science, test, expect, spy} from "@e280/science"
3
3
 
4
+ import {lazy} from "./parts/lazy.js"
4
5
  import {effect} from "./parts/effect.js"
5
6
  import {signal} from "./parts/signal.js"
6
- import {computed} from "./parts/computed.js"
7
7
 
8
8
  export default Science.suite({
9
9
  "signal get/set value": test(async() => {
@@ -53,6 +53,19 @@ export default Science.suite({
53
53
  expect(runs).is(1)
54
54
  }),
55
55
 
56
+ "signal on circularity forbidden": test(async() => {
57
+ const count = signal(1)
58
+ let runs = 0
59
+ count.on(async() => {
60
+ await count.set(99)
61
+ runs++
62
+ })
63
+ expect(async() => {
64
+ await count.set(2)
65
+ }).throwsAsync()
66
+ expect(runs).is(0)
67
+ }),
68
+
56
69
  "effect tracks signal changes": test(async() => {
57
70
  const count = signal(1)
58
71
  let doubled = 0
@@ -135,10 +148,10 @@ export default Science.suite({
135
148
  expect(runs).is(2)
136
149
  }),
137
150
 
138
- "computed values": test(async() => {
151
+ "lazy values": test(async() => {
139
152
  const a = signal(2)
140
153
  const b = signal(3)
141
- const sum = computed(() => a.value + b.value)
154
+ const sum = lazy(() => a.value + b.value)
142
155
  expect(sum.value).is(5)
143
156
 
144
157
  await a.set(5)
@@ -148,11 +161,93 @@ export default Science.suite({
148
161
  expect(sum.value).is(12)
149
162
  }),
150
163
 
151
- "computed is lazy": test(async() => {
164
+ "effect reacts to derived changes": test(async() => {
165
+ const a = signal(1)
166
+ const b = signal(10)
167
+ const product = signal.derive(() => a.value * b.value)
168
+
169
+ let mutations = 0
170
+ effect(() => {
171
+ void product.get()
172
+ mutations++
173
+ })
174
+ expect(product.value).is(10)
175
+ expect(mutations).is(1)
176
+
177
+ await a.set(2)
178
+ expect(product.value).is(20)
179
+ expect(mutations).is(2)
180
+
181
+ await a.set(3)
182
+ expect(product.value).is(30)
183
+ expect(mutations).is(3)
184
+ }),
185
+
186
+ "effect doesn't overreact to derived": test(async() => {
187
+ const a = signal(1)
188
+ const b = signal(10)
189
+ const product = signal.derive(() => a.value * b.value)
190
+
191
+ const derivedSpy = spy(() => {})
192
+ product.on(derivedSpy)
193
+
194
+ let mutations = 0
195
+ effect(() => {
196
+ a.get()
197
+ product.get()
198
+ mutations++
199
+ })
200
+ expect(product.value).is(10)
201
+ expect(mutations).is(1)
202
+ expect(derivedSpy.spy.calls.length).is(0)
203
+
204
+ await a.set(2)
205
+ expect(product.value).is(20)
206
+ expect(mutations).is(2)
207
+ expect(derivedSpy.spy.calls.length).is(1)
208
+ }),
209
+
210
+ "derived.on": test(async() => {
211
+ const a = signal(1)
212
+ const b = signal(10)
213
+ const product = signal.derive(() => a.value * b.value)
214
+ expect(product.value).is(10)
215
+
216
+ const mole = spy((_v: number) => {})
217
+ product.on(mole)
218
+ expect(mole.spy.calls.length).is(0)
219
+
220
+ await a.set(2)
221
+ expect(product.value).is(20)
222
+ expect(mole.spy.calls.length).is(1)
223
+ expect(mole.spy.calls[0].args[0]).is(20)
224
+ }),
225
+
226
+ "derived.on not called if result doesn't change": test(async() => {
227
+ const a = signal(1)
228
+ const b = signal(10)
229
+ const product = signal.derive(() => a.value * b.value)
230
+ expect(product.value).is(10)
231
+
232
+ const mole = spy((_v: number) => {})
233
+ product.on(mole)
234
+ expect(mole.spy.calls.length).is(0)
235
+
236
+ await a.set(2)
237
+ expect(product.value).is(20)
238
+ expect(mole.spy.calls.length).is(1)
239
+ expect(mole.spy.calls[0].args[0]).is(20)
240
+
241
+ await a.set(2)
242
+ expect(product.value).is(20)
243
+ expect(mole.spy.calls.length).is(1)
244
+ }),
245
+
246
+ "lazy is lazy": test(async() => {
152
247
  const a = signal(1)
153
248
  let runs = 0
154
249
 
155
- const comp = computed(() => {
250
+ const comp = lazy(() => {
156
251
  runs++
157
252
  return a.value * 10
158
253
  })
@@ -167,10 +262,10 @@ export default Science.suite({
167
262
  expect(runs).is(2)
168
263
  }),
169
264
 
170
- "computed fn syntax": test(async() => {
265
+ "lazy fn syntax": test(async() => {
171
266
  const a = signal(2)
172
267
  const b = signal(3)
173
- const sum = computed(() => a.value + b.value)
268
+ const sum = lazy(() => a.value + b.value)
174
269
  expect(sum.value).is(5)
175
270
 
176
271
  await a.set(5)
package/s/tests.test.ts CHANGED
@@ -47,7 +47,7 @@ await Science.run({
47
47
  expect(order[2]).is("after")
48
48
  }),
49
49
 
50
- "branch can include signal value": test.skip(async() => {
50
+ "branch can include signal value": test(async() => {
51
51
  const bingus = signal(101)
52
52
  const trunk = new Trunk({count: 1})
53
53
  const branch = trunk.branch(s => ({
package/s/tree/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
+ export * from "./parts/branch.js"
2
3
  export * from "./parts/chronobranch.js"
3
4
  export * from "./parts/persistence.js"
4
5
  export * from "./parts/trunk.js"
5
- export * from "./parts/branch.js"
6
6
  export * from "./parts/types.js"
7
7