@zeix/cause-effect 0.17.3 → 0.18.1

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 (94) hide show
  1. package/.ai-context.md +169 -227
  2. package/.cursorrules +41 -35
  3. package/.github/copilot-instructions.md +176 -116
  4. package/ARCHITECTURE.md +276 -0
  5. package/CHANGELOG.md +29 -0
  6. package/CLAUDE.md +201 -143
  7. package/GUIDE.md +298 -0
  8. package/README.md +246 -193
  9. package/REQUIREMENTS.md +100 -0
  10. package/bench/reactivity.bench.ts +577 -0
  11. package/context7.json +4 -0
  12. package/examples/events-sensor.ts +187 -0
  13. package/examples/selector-sensor.ts +173 -0
  14. package/index.dev.js +1390 -1008
  15. package/index.js +1 -1
  16. package/index.ts +60 -74
  17. package/package.json +5 -2
  18. package/skills/changelog-keeper/SKILL.md +59 -0
  19. package/skills/changelog-keeper/agents/openai.yaml +4 -0
  20. package/src/errors.ts +118 -74
  21. package/src/graph.ts +612 -0
  22. package/src/nodes/collection.ts +512 -0
  23. package/src/nodes/effect.ts +149 -0
  24. package/src/nodes/list.ts +589 -0
  25. package/src/nodes/memo.ts +148 -0
  26. package/src/nodes/sensor.ts +149 -0
  27. package/src/nodes/state.ts +135 -0
  28. package/src/nodes/store.ts +378 -0
  29. package/src/nodes/task.ts +174 -0
  30. package/src/signal.ts +112 -66
  31. package/src/util.ts +26 -57
  32. package/test/batch.test.ts +96 -62
  33. package/test/benchmark.test.ts +473 -487
  34. package/test/collection.test.ts +456 -707
  35. package/test/effect.test.ts +293 -696
  36. package/test/list.test.ts +335 -592
  37. package/test/memo.test.ts +574 -0
  38. package/test/regression.test.ts +156 -0
  39. package/test/scope.test.ts +191 -0
  40. package/test/sensor.test.ts +454 -0
  41. package/test/signal.test.ts +220 -213
  42. package/test/state.test.ts +217 -265
  43. package/test/store.test.ts +346 -446
  44. package/test/task.test.ts +529 -0
  45. package/test/untrack.test.ts +167 -0
  46. package/types/index.d.ts +13 -15
  47. package/types/src/errors.d.ts +73 -17
  48. package/types/src/graph.d.ts +218 -0
  49. package/types/src/nodes/collection.d.ts +69 -0
  50. package/types/src/nodes/effect.d.ts +48 -0
  51. package/types/src/nodes/list.d.ts +66 -0
  52. package/types/src/nodes/memo.d.ts +63 -0
  53. package/types/src/nodes/sensor.d.ts +81 -0
  54. package/types/src/nodes/state.d.ts +78 -0
  55. package/types/src/nodes/store.d.ts +51 -0
  56. package/types/src/nodes/task.d.ts +79 -0
  57. package/types/src/signal.d.ts +43 -29
  58. package/types/src/util.d.ts +9 -16
  59. package/archive/benchmark.ts +0 -683
  60. package/archive/collection.ts +0 -253
  61. package/archive/composite.ts +0 -85
  62. package/archive/computed.ts +0 -195
  63. package/archive/list.ts +0 -483
  64. package/archive/memo.ts +0 -139
  65. package/archive/state.ts +0 -90
  66. package/archive/store.ts +0 -298
  67. package/archive/task.ts +0 -189
  68. package/src/classes/collection.ts +0 -245
  69. package/src/classes/computed.ts +0 -349
  70. package/src/classes/list.ts +0 -343
  71. package/src/classes/ref.ts +0 -70
  72. package/src/classes/state.ts +0 -102
  73. package/src/classes/store.ts +0 -262
  74. package/src/diff.ts +0 -138
  75. package/src/effect.ts +0 -93
  76. package/src/match.ts +0 -45
  77. package/src/resolve.ts +0 -49
  78. package/src/system.ts +0 -257
  79. package/test/computed.test.ts +0 -1108
  80. package/test/diff.test.ts +0 -955
  81. package/test/match.test.ts +0 -388
  82. package/test/ref.test.ts +0 -353
  83. package/test/resolve.test.ts +0 -154
  84. package/types/src/classes/collection.d.ts +0 -45
  85. package/types/src/classes/computed.d.ts +0 -94
  86. package/types/src/classes/list.d.ts +0 -43
  87. package/types/src/classes/ref.d.ts +0 -35
  88. package/types/src/classes/state.d.ts +0 -49
  89. package/types/src/classes/store.d.ts +0 -52
  90. package/types/src/diff.d.ts +0 -28
  91. package/types/src/effect.d.ts +0 -15
  92. package/types/src/match.d.ts +0 -21
  93. package/types/src/resolve.d.ts +0 -29
  94. package/types/src/system.d.ts +0 -78
package/src/signal.ts CHANGED
@@ -1,78 +1,79 @@
1
+ import { InvalidSignalValueError } from './errors'
1
2
  import {
2
- type Computed,
3
- isComputed,
4
- isMemoCallback,
5
- isTaskCallback,
6
- Memo,
7
- Task,
8
- } from './classes/computed'
9
- import { isList, List } from './classes/list'
10
- import { isState, State } from './classes/state'
11
- import { createStore, isStore, type Store } from './classes/store'
12
- import type { UnknownRecord } from './diff'
13
- import { isRecord, isUniformArray } from './util'
3
+ type ComputedOptions,
4
+ type MemoCallback,
5
+ type Signal,
6
+ type TaskCallback,
7
+ TYPE_COLLECTION,
8
+ TYPE_LIST,
9
+ TYPE_MEMO,
10
+ TYPE_SENSOR,
11
+ TYPE_STATE,
12
+ TYPE_STORE,
13
+ TYPE_TASK,
14
+ } from './graph'
15
+ import { createList, isList, type List, type UnknownRecord } from './nodes/list'
16
+ import { createMemo, isMemo, type Memo } from './nodes/memo'
17
+ import { createState, isState, type State } from './nodes/state'
18
+ import { createStore, isStore, type Store } from './nodes/store'
19
+ import { createTask, isTask, type Task } from './nodes/task'
20
+ import { isAsyncFunction, isFunction, isRecord, isUniformArray } from './util'
14
21
 
15
22
  /* === Types === */
16
23
 
17
- type Signal<T extends {}> = {
24
+ type MutableSignal<T extends {}> = {
18
25
  get(): T
26
+ set(value: T): void
27
+ update(callback: (value: T) => T): void
19
28
  }
20
29
 
21
- type UnknownSignal = Signal<unknown & {}>
22
- type MutableSignal<T extends {}> = T extends readonly (infer U extends {})[]
23
- ? List<U>
24
- : T extends UnknownRecord
25
- ? Store<T>
26
- : State<T>
27
- type ReadonlySignal<T extends {}> = Computed<T> // | Collection<T>
28
-
29
- type UnknownSignalRecord = Record<string, UnknownSignal>
30
-
31
- type SignalValues<S extends UnknownSignalRecord> = {
32
- [K in keyof S]: S[K] extends Signal<infer T> ? T : never
33
- }
34
-
35
- /* === Functions === */
30
+ /* === Factory Functions === */
36
31
 
37
32
  /**
38
- * Check whether a value is a Signal
33
+ * Create a derived signal from existing signals
39
34
  *
40
35
  * @since 0.9.0
41
- * @param {unknown} value - value to check
42
- * @returns {boolean} - true if value is a Signal, false otherwise
36
+ * @param callback - Computation callback function
37
+ * @param options - Optional configuration
43
38
  */
44
- const isSignal = /*#__PURE__*/ <T extends {}>(
45
- value: unknown,
46
- ): value is Signal<T> => isState(value) || isComputed(value) || isStore(value)
47
-
48
- /**
49
- * Check whether a value is a State, Store, or List
50
- *
51
- * @since 0.15.2
52
- * @param {unknown} value - Value to check
53
- * @returns {boolean} - True if value is a State, Store, or List, false otherwise
54
- */
55
- const isMutableSignal = /*#__PURE__*/ (
56
- value: unknown,
57
- ): value is MutableSignal<unknown & {}> =>
58
- isState(value) || isStore(value) || isList(value)
39
+ function createComputed<T extends {}>(
40
+ callback: TaskCallback<T>,
41
+ options?: ComputedOptions<T>,
42
+ ): Task<T>
43
+ function createComputed<T extends {}>(
44
+ callback: MemoCallback<T>,
45
+ options?: ComputedOptions<T>,
46
+ ): Memo<T>
47
+ function createComputed<T extends {}>(
48
+ callback: TaskCallback<T> | MemoCallback<T>,
49
+ options?: ComputedOptions<T>,
50
+ ): Memo<T> | Task<T> {
51
+ return isAsyncFunction(callback)
52
+ ? createTask(callback as TaskCallback<T>, options)
53
+ : createMemo(callback as MemoCallback<T>, options)
54
+ }
59
55
 
60
56
  /**
61
57
  * Convert a value to a Signal.
62
58
  *
63
59
  * @since 0.9.6
64
60
  */
61
+ function createSignal<T extends {}>(value: Signal<T>): Signal<T>
65
62
  function createSignal<T extends {}>(value: readonly T[]): List<T>
66
- function createSignal<T extends {}>(value: T[]): List<T>
67
63
  function createSignal<T extends UnknownRecord>(value: T): Store<T>
68
- function createSignal<T extends {}>(value: () => T): Computed<T>
64
+ function createSignal<T extends {}>(value: TaskCallback<T>): Task<T>
65
+ function createSignal<T extends {}>(value: MemoCallback<T>): Memo<T>
69
66
  function createSignal<T extends {}>(value: T): State<T>
70
67
  function createSignal(value: unknown): unknown {
71
- if (isMemoCallback(value)) return new Memo(value)
72
- if (isTaskCallback(value)) return new Task(value)
73
- if (isUniformArray<unknown & {}>(value)) return new List(value)
74
- if (isRecord(value)) return createStore(value as UnknownRecord)
75
- return new State(value as unknown & {})
68
+ if (isSignal(value)) return value
69
+ if (value == null) throw new InvalidSignalValueError('createSignal', value)
70
+ if (isAsyncFunction(value))
71
+ return createTask(value as TaskCallback<unknown & {}>)
72
+ if (isFunction(value))
73
+ return createMemo(value as MemoCallback<unknown & {}>)
74
+ if (isUniformArray<unknown & {}>(value)) return createList(value)
75
+ if (isRecord(value)) return createStore(value)
76
+ return createState(value as unknown & {})
76
77
  }
77
78
 
78
79
  /**
@@ -80,27 +81,72 @@ function createSignal(value: unknown): unknown {
80
81
  *
81
82
  * @since 0.17.0
82
83
  */
84
+ function createMutableSignal<T extends {}>(
85
+ value: MutableSignal<T>,
86
+ ): MutableSignal<T>
83
87
  function createMutableSignal<T extends {}>(value: readonly T[]): List<T>
84
- function createMutableSignal<T extends {}>(value: T[]): List<T>
85
88
  function createMutableSignal<T extends UnknownRecord>(value: T): Store<T>
86
89
  function createMutableSignal<T extends {}>(value: T): State<T>
87
90
  function createMutableSignal(value: unknown): unknown {
88
- if (isUniformArray<unknown & {}>(value)) return new List(value)
89
- if (isRecord(value)) return createStore(value as UnknownRecord)
90
- return new State(value as unknown & {})
91
+ if (isMutableSignal(value)) return value
92
+ if (value == null || isFunction(value) || isSignal(value))
93
+ throw new InvalidSignalValueError('createMutableSignal', value)
94
+ if (isUniformArray<unknown & {}>(value)) return createList(value)
95
+ if (isRecord(value)) return createStore(value)
96
+ return createState(value as unknown & {})
97
+ }
98
+
99
+ /* === Guards === */
100
+
101
+ /**
102
+ * Check if a value is a computed signal
103
+ *
104
+ * @since 0.9.0
105
+ * @param value - Value to check
106
+ * @returns True if value is a computed signal, false otherwise
107
+ */
108
+ function isComputed<T extends {}>(value: unknown): value is Memo<T> {
109
+ return isMemo(value) || isTask(value)
91
110
  }
92
111
 
93
- /* === Exports === */
112
+ /**
113
+ * Check whether a value is a Signal
114
+ *
115
+ * @since 0.9.0
116
+ * @param value - Value to check
117
+ * @returns True if value is a Signal, false otherwise
118
+ */
119
+ function isSignal<T extends {}>(value: unknown): value is Signal<T> {
120
+ const signalsTypes = [
121
+ TYPE_STATE,
122
+ TYPE_MEMO,
123
+ TYPE_TASK,
124
+ TYPE_SENSOR,
125
+ TYPE_LIST,
126
+ TYPE_COLLECTION,
127
+ TYPE_STORE,
128
+ ]
129
+ const typeStyle = Object.prototype.toString.call(value).slice(8, -1)
130
+ return signalsTypes.includes(typeStyle)
131
+ }
132
+
133
+ /**
134
+ * Check whether a value is a State, Store, or List
135
+ *
136
+ * @since 0.15.2
137
+ * @param value - Value to check
138
+ * @returns True if value is a State, Store, or List, false otherwise
139
+ */
140
+ function isMutableSignal(value: unknown): value is MutableSignal<unknown & {}> {
141
+ return isState(value) || isStore(value) || isList(value)
142
+ }
94
143
 
95
144
  export {
96
- createMutableSignal,
145
+ type MutableSignal,
146
+ createComputed,
97
147
  createSignal,
98
- isMutableSignal,
148
+ createMutableSignal,
149
+ isComputed,
99
150
  isSignal,
100
- type MutableSignal,
101
- type ReadonlySignal,
102
- type Signal,
103
- type SignalValues,
104
- type UnknownSignal,
105
- type UnknownSignalRecord,
151
+ isMutableSignal,
106
152
  }
package/src/util.ts CHANGED
@@ -1,85 +1,54 @@
1
1
  /* === Utility Functions === */
2
2
 
3
- const isString = /*#__PURE__*/ (value: unknown): value is string =>
4
- typeof value === 'string'
5
-
6
- const isNumber = /*#__PURE__*/ (value: unknown): value is number =>
7
- typeof value === 'number'
8
-
9
- const isSymbol = /*#__PURE__*/ (value: unknown): value is symbol =>
10
- typeof value === 'symbol'
11
-
12
- const isFunction = /*#__PURE__*/ <T>(
13
- fn: unknown,
14
- ): fn is (...args: unknown[]) => T => typeof fn === 'function'
3
+ function isFunction<T>(fn: unknown): fn is (...args: unknown[]) => T {
4
+ return typeof fn === 'function'
5
+ }
15
6
 
16
- const isAsyncFunction = /*#__PURE__*/ <T>(
7
+ function isAsyncFunction<T>(
17
8
  fn: unknown,
18
- ): fn is (...args: unknown[]) => Promise<T> =>
19
- isFunction(fn) && fn.constructor.name === 'AsyncFunction'
9
+ ): fn is (...args: unknown[]) => Promise<T> {
10
+ return isFunction(fn) && fn.constructor.name === 'AsyncFunction'
11
+ }
20
12
 
21
- const isSyncFunction = /*#__PURE__*/ <T extends unknown & { then?: undefined }>(
13
+ function isSyncFunction<T extends unknown & { then?: undefined }>(
22
14
  fn: unknown,
23
- ): fn is (...args: unknown[]) => T =>
24
- isFunction(fn) && fn.constructor.name !== 'AsyncFunction'
25
-
26
- const isNonNullObject = /*#__PURE__*/ (
27
- value: unknown,
28
- ): value is NonNullable<object> => value != null && typeof value === 'object'
29
-
30
- const isObjectOfType = /*#__PURE__*/ <T>(
31
- value: unknown,
32
- type: string,
33
- ): value is T => Object.prototype.toString.call(value) === `[object ${type}]`
15
+ ): fn is (...args: unknown[]) => T {
16
+ return isFunction(fn) && fn.constructor.name !== 'AsyncFunction'
17
+ }
34
18
 
35
- const isRecord = /*#__PURE__*/ <T extends Record<string, unknown>>(
36
- value: unknown,
37
- ): value is T => isObjectOfType(value, 'Object')
19
+ function isObjectOfType<T>(value: unknown, type: string): value is T {
20
+ return Object.prototype.toString.call(value) === `[object ${type}]`
21
+ }
38
22
 
39
- const isRecordOrArray = /*#__PURE__*/ <
40
- T extends Record<string | number, unknown> | ReadonlyArray<unknown>,
41
- >(
23
+ function isRecord<T extends Record<string, unknown>>(
42
24
  value: unknown,
43
- ): value is T => isRecord(value) || Array.isArray(value)
25
+ ): value is T {
26
+ return isObjectOfType(value, 'Object')
27
+ }
44
28
 
45
- const isUniformArray = <T>(
29
+ function isUniformArray<T>(
46
30
  value: unknown,
47
- guard = (item: T): item is T & {} => item != null,
48
- ): value is T[] => Array.isArray(value) && value.every(guard)
49
-
50
- const hasMethod = /*#__PURE__*/ <
51
- T extends object & Record<string, (...args: unknown[]) => unknown>,
52
- >(
53
- obj: T,
54
- methodName: string,
55
- ): obj is T & Record<string, (...args: unknown[]) => unknown> =>
56
- methodName in obj && isFunction(obj[methodName])
57
-
58
- const isAbortError = /*#__PURE__*/ (error: unknown): boolean =>
59
- error instanceof DOMException && error.name === 'AbortError'
31
+ guard: (item: T) => item is T & {} = (item): item is T & {} => item != null,
32
+ ): value is T[] {
33
+ return Array.isArray(value) && value.every(guard)
34
+ }
60
35
 
61
- const valueString = /*#__PURE__*/ (value: unknown): string =>
62
- isString(value)
36
+ function valueString(value: unknown): string {
37
+ return typeof value === 'string'
63
38
  ? `"${value}"`
64
39
  : !!value && typeof value === 'object'
65
40
  ? JSON.stringify(value)
66
41
  : String(value)
42
+ }
67
43
 
68
44
  /* === Exports === */
69
45
 
70
46
  export {
71
- isString,
72
- isNumber,
73
- isSymbol,
74
47
  isFunction,
75
48
  isAsyncFunction,
76
49
  isSyncFunction,
77
- isNonNullObject,
78
50
  isObjectOfType,
79
51
  isRecord,
80
- isRecordOrArray,
81
52
  isUniformArray,
82
- hasMethod,
83
- isAbortError,
84
53
  valueString,
85
54
  }
@@ -1,97 +1,131 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { batch, createEffect, Memo, match, resolve, State } from '../index.ts'
2
+ import {
3
+ batch,
4
+ createEffect,
5
+ createList,
6
+ createMemo,
7
+ createState,
8
+ createStore,
9
+ } from '../index.ts'
3
10
 
4
11
  /* === Tests === */
5
12
 
6
- describe('Batch', () => {
7
- test('should be triggered only once after repeated state change', () => {
8
- const cause = new State(0)
13
+ describe('batch', () => {
14
+ test('should trigger effect only once after repeated state changes', () => {
15
+ const source = createState(0)
9
16
  let result = 0
10
17
  let count = 0
11
18
  createEffect((): undefined => {
12
- result = cause.get()
19
+ result = source.get()
13
20
  count++
14
21
  })
22
+ expect(count).toBe(1)
15
23
  batch(() => {
16
- for (let i = 1; i <= 10; i++) cause.set(i)
24
+ for (let i = 1; i <= 10; i++) source.set(i)
17
25
  })
18
26
  expect(result).toBe(10)
19
- expect(count).toBe(2) // + 1 for effect initialization
27
+ expect(count).toBe(2)
20
28
  })
21
29
 
22
- test('should be triggered only once when multiple signals are set', () => {
23
- const a = new State(3)
24
- const b = new State(4)
25
- const c = new State(5)
26
- const sum = new Memo(() => a.get() + b.get() + c.get())
30
+ test('should trigger effect only once when multiple states change', () => {
31
+ const a = createState(3)
32
+ const b = createState(4)
33
+ const c = createState(5)
34
+ const sum = createMemo(() => a.get() + b.get() + c.get())
27
35
  let result = 0
28
36
  let count = 0
29
- createEffect(() => {
30
- const resolved = resolve({ sum })
31
- match(resolved, {
32
- ok: ({ sum: res }) => {
33
- result = res
34
- count++
35
- },
36
- err: () => {},
37
- })
37
+ createEffect((): undefined => {
38
+ result = sum.get()
39
+ count++
38
40
  })
41
+ expect(result).toBe(12)
42
+ expect(count).toBe(1)
39
43
  batch(() => {
40
44
  a.set(6)
41
45
  b.set(8)
42
46
  c.set(10)
43
47
  })
44
48
  expect(result).toBe(24)
45
- expect(count).toBe(2) // + 1 for effect initialization
49
+ expect(count).toBe(2)
46
50
  })
47
51
 
48
- test('should prove example from README works', () => {
49
- // State: define an array of Signal<number>
50
- const signals = [new State(2), new State(3), new State(5)]
51
-
52
- // Computed: derive a calculation ...
53
- const sum = new Memo(() => {
54
- const v = signals.reduce((total, v) => total + v.get(), 0)
55
- if (!Number.isFinite(v)) throw new Error('Invalid value')
56
- return v
52
+ test('should batch store property updates', () => {
53
+ const user = createStore({ name: 'Alice', age: 30 })
54
+ let result = ''
55
+ let count = 0
56
+ createEffect((): undefined => {
57
+ result = `${user.name.get()} (${user.age.get()})`
58
+ count++
59
+ })
60
+ expect(result).toBe('Alice (30)')
61
+ expect(count).toBe(1)
62
+ batch(() => {
63
+ user.name.set('Bob')
64
+ user.age.set(25)
57
65
  })
66
+ expect(result).toBe('Bob (25)')
67
+ expect(count).toBe(2)
68
+ })
58
69
 
70
+ test('should batch list mutations', () => {
71
+ const list = createList([1, 2, 3])
59
72
  let result = 0
60
- let okCount = 0
61
- let errCount = 0
62
-
63
- // Effect: switch cases for the result
64
- createEffect(() => {
65
- const resolved = resolve({ sum })
66
- match(resolved, {
67
- ok: ({ sum: v }) => {
68
- result = v
69
- okCount++
70
- // console.log('Sum:', v)
71
- },
72
- err: () => {
73
- errCount++
74
- // console.error('Error:', error)
75
- },
76
- })
73
+ let count = 0
74
+ createEffect((): undefined => {
75
+ result = list.get().reduce((sum, v) => sum + v, 0)
76
+ count++
77
77
  })
78
-
79
- expect(okCount).toBe(1)
80
- expect(result).toBe(10)
81
-
82
- // Batch: apply changes to all signals in a single transaction
78
+ expect(result).toBe(6)
79
+ expect(count).toBe(1)
83
80
  batch(() => {
84
- signals.forEach(signal => signal.update(v => v * 2))
81
+ list.add(4)
82
+ list.add(5)
85
83
  })
84
+ expect(result).toBe(15)
85
+ expect(count).toBe(2)
86
+ })
86
87
 
87
- expect(okCount).toBe(2)
88
- expect(result).toBe(20)
89
-
90
- // Provoke an error
91
- signals[0].set(NaN)
88
+ test('should batch mixed signal type updates', () => {
89
+ const count = createState(1)
90
+ const items = createList([10, 20])
91
+ const config = createStore({ multiplier: 2 })
92
+ let result = 0
93
+ let runs = 0
94
+ createEffect((): undefined => {
95
+ const sum = items.get().reduce((s, v) => s + v, 0)
96
+ result = (count.get() + sum) * config.multiplier.get()
97
+ runs++
98
+ })
99
+ expect(result).toBe(62) // (1 + 30) * 2
100
+ expect(runs).toBe(1)
101
+ batch(() => {
102
+ count.set(5)
103
+ items.add(30)
104
+ config.multiplier.set(3)
105
+ })
106
+ expect(result).toBe(195) // (5 + 60) * 3
107
+ expect(runs).toBe(2)
108
+ })
92
109
 
93
- expect(errCount).toBe(1)
94
- expect(okCount).toBe(2) // should not have changed due to error
95
- expect(result).toBe(20) // should not have changed due to error
110
+ test('should support nested batches', () => {
111
+ const a = createState(1)
112
+ const b = createState(2)
113
+ let result = 0
114
+ let count = 0
115
+ createEffect((): undefined => {
116
+ result = a.get() + b.get()
117
+ count++
118
+ })
119
+ expect(count).toBe(1)
120
+ batch(() => {
121
+ a.set(10)
122
+ batch(() => {
123
+ b.set(20)
124
+ })
125
+ // inner batch should not flush yet
126
+ expect(count).toBe(1)
127
+ })
128
+ expect(result).toBe(30)
129
+ expect(count).toBe(2)
96
130
  })
97
131
  })