@livestore/livestore 0.0.12 → 0.0.14

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 (226) hide show
  1. package/README.md +25 -28
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/QueryCache.d.ts +20 -0
  4. package/dist/QueryCache.d.ts.map +1 -0
  5. package/dist/QueryCache.js +71 -0
  6. package/dist/QueryCache.js.map +1 -0
  7. package/dist/__tests__/react/fixture.d.ts +26 -0
  8. package/dist/__tests__/react/fixture.d.ts.map +1 -0
  9. package/dist/__tests__/react/fixture.js +60 -0
  10. package/dist/__tests__/react/fixture.js.map +1 -0
  11. package/dist/__tests__/react/useComponentState.test.d.ts +2 -0
  12. package/dist/__tests__/react/useComponentState.test.d.ts.map +1 -0
  13. package/dist/__tests__/react/useComponentState.test.js +68 -0
  14. package/dist/__tests__/react/useComponentState.test.js.map +1 -0
  15. package/dist/__tests__/react/useLQuery.test.d.ts +2 -0
  16. package/dist/__tests__/react/useLQuery.test.d.ts.map +1 -0
  17. package/dist/__tests__/react/useLQuery.test.js +38 -0
  18. package/dist/__tests__/react/useLQuery.test.js.map +1 -0
  19. package/dist/__tests__/react/useLiveStoreComponent.test.d.ts +2 -0
  20. package/dist/__tests__/react/useLiveStoreComponent.test.d.ts.map +1 -0
  21. package/dist/__tests__/react/useLiveStoreComponent.test.js +73 -0
  22. package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -0
  23. package/dist/__tests__/react/useQuery.test.d.ts +2 -0
  24. package/dist/__tests__/react/useQuery.test.d.ts.map +1 -0
  25. package/dist/__tests__/react/useQuery.test.js +33 -0
  26. package/dist/__tests__/react/useQuery.test.js.map +1 -0
  27. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +2 -0
  28. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +1 -0
  29. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +38 -0
  30. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +1 -0
  31. package/dist/__tests__/reactive.test.d.ts +2 -0
  32. package/dist/__tests__/reactive.test.d.ts.map +1 -0
  33. package/dist/__tests__/reactive.test.js +271 -0
  34. package/dist/__tests__/reactive.test.js.map +1 -0
  35. package/dist/__tests__/reactiveQueries/sql.test.d.ts +2 -0
  36. package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +1 -0
  37. package/dist/__tests__/reactiveQueries/sql.test.js +337 -0
  38. package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -0
  39. package/dist/bounded-collections.d.ts +34 -0
  40. package/dist/bounded-collections.d.ts.map +1 -0
  41. package/dist/bounded-collections.js +103 -0
  42. package/dist/bounded-collections.js.map +1 -0
  43. package/dist/componentKey.d.ts +20 -0
  44. package/dist/componentKey.d.ts.map +1 -0
  45. package/dist/componentKey.js +3 -0
  46. package/dist/componentKey.js.map +1 -0
  47. package/dist/effect/LiveStore.d.ts +36 -0
  48. package/dist/effect/LiveStore.d.ts.map +1 -0
  49. package/dist/effect/LiveStore.js +41 -0
  50. package/dist/effect/LiveStore.js.map +1 -0
  51. package/dist/effect/index.d.ts +2 -0
  52. package/dist/effect/index.d.ts.map +1 -0
  53. package/dist/effect/index.js +2 -0
  54. package/dist/effect/index.js.map +1 -0
  55. package/dist/events.d.ts +7 -0
  56. package/dist/events.d.ts.map +1 -0
  57. package/dist/events.js +2 -0
  58. package/dist/events.js.map +1 -0
  59. package/dist/inMemoryDatabase.d.ts +56 -0
  60. package/dist/inMemoryDatabase.d.ts.map +1 -0
  61. package/dist/inMemoryDatabase.js +223 -0
  62. package/dist/inMemoryDatabase.js.map +1 -0
  63. package/dist/index.d.ts +22 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +13 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/migrations.d.ts +16 -0
  68. package/dist/migrations.d.ts.map +1 -0
  69. package/dist/migrations.js +67 -0
  70. package/dist/migrations.js.map +1 -0
  71. package/dist/otel.d.ts +4 -0
  72. package/dist/otel.d.ts.map +1 -0
  73. package/dist/otel.js +6 -0
  74. package/dist/otel.js.map +1 -0
  75. package/dist/react/LiveStoreContext.d.ts +11 -0
  76. package/dist/react/LiveStoreContext.d.ts.map +1 -0
  77. package/dist/react/LiveStoreContext.js +10 -0
  78. package/dist/react/LiveStoreContext.js.map +1 -0
  79. package/dist/react/LiveStoreProvider.d.ts +20 -0
  80. package/dist/react/LiveStoreProvider.d.ts.map +1 -0
  81. package/dist/react/LiveStoreProvider.js +52 -0
  82. package/dist/react/LiveStoreProvider.js.map +1 -0
  83. package/dist/react/index.d.ts +8 -0
  84. package/dist/react/index.d.ts.map +1 -0
  85. package/dist/react/index.js +6 -0
  86. package/dist/react/index.js.map +1 -0
  87. package/dist/react/useComponentState.d.ts +50 -0
  88. package/dist/react/useComponentState.d.ts.map +1 -0
  89. package/dist/react/useComponentState.js +248 -0
  90. package/dist/react/useComponentState.js.map +1 -0
  91. package/dist/react/useGlobalQuery.d.ts +3 -0
  92. package/dist/react/useGlobalQuery.d.ts.map +1 -0
  93. package/dist/react/useGlobalQuery.js +26 -0
  94. package/dist/react/useGlobalQuery.js.map +1 -0
  95. package/dist/react/useGraphQL.d.ts +13 -0
  96. package/dist/react/useGraphQL.d.ts.map +1 -0
  97. package/dist/react/useGraphQL.js +87 -0
  98. package/dist/react/useGraphQL.js.map +1 -0
  99. package/dist/react/useLiveStoreComponent.d.ts +75 -0
  100. package/dist/react/useLiveStoreComponent.d.ts.map +1 -0
  101. package/dist/react/useLiveStoreComponent.js +361 -0
  102. package/dist/react/useLiveStoreComponent.js.map +1 -0
  103. package/dist/react/useQuery.d.ts +3 -0
  104. package/dist/react/useQuery.d.ts.map +1 -0
  105. package/dist/react/useQuery.js +42 -0
  106. package/dist/react/useQuery.js.map +1 -0
  107. package/dist/react/useTemporaryQuery.d.ts +8 -0
  108. package/dist/react/useTemporaryQuery.d.ts.map +1 -0
  109. package/dist/react/useTemporaryQuery.js +17 -0
  110. package/dist/react/useTemporaryQuery.js.map +1 -0
  111. package/dist/react/utils/extractNamesFromStackTrace.d.ts +3 -0
  112. package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +1 -0
  113. package/dist/react/utils/extractNamesFromStackTrace.js +40 -0
  114. package/dist/react/utils/extractNamesFromStackTrace.js.map +1 -0
  115. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +7 -0
  116. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +1 -0
  117. package/dist/react/utils/extractStackInfoFromStackTrace.js +40 -0
  118. package/dist/react/utils/extractStackInfoFromStackTrace.js.map +1 -0
  119. package/dist/react/utils/useStateRefWithReactiveInput.d.ts +13 -0
  120. package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -0
  121. package/dist/react/utils/useStateRefWithReactiveInput.js +38 -0
  122. package/dist/react/utils/useStateRefWithReactiveInput.js.map +1 -0
  123. package/dist/reactive.d.ts +134 -0
  124. package/dist/reactive.d.ts.map +1 -0
  125. package/dist/reactive.js +409 -0
  126. package/dist/reactive.js.map +1 -0
  127. package/dist/reactiveQueries/base-class.d.ts +32 -0
  128. package/dist/reactiveQueries/base-class.d.ts.map +1 -0
  129. package/dist/reactiveQueries/base-class.js +30 -0
  130. package/dist/reactiveQueries/base-class.js.map +1 -0
  131. package/dist/reactiveQueries/graph.d.ts +10 -0
  132. package/dist/reactiveQueries/graph.d.ts.map +1 -0
  133. package/dist/reactiveQueries/graph.js +6 -0
  134. package/dist/reactiveQueries/graph.js.map +1 -0
  135. package/dist/reactiveQueries/graphql.d.ts +42 -0
  136. package/dist/reactiveQueries/graphql.d.ts.map +1 -0
  137. package/dist/reactiveQueries/graphql.js +99 -0
  138. package/dist/reactiveQueries/graphql.js.map +1 -0
  139. package/dist/reactiveQueries/js.d.ts +23 -0
  140. package/dist/reactiveQueries/js.d.ts.map +1 -0
  141. package/dist/reactiveQueries/js.js +36 -0
  142. package/dist/reactiveQueries/js.js.map +1 -0
  143. package/dist/reactiveQueries/sql.d.ts +35 -0
  144. package/dist/reactiveQueries/sql.d.ts.map +1 -0
  145. package/dist/reactiveQueries/sql.js +97 -0
  146. package/dist/reactiveQueries/sql.js.map +1 -0
  147. package/dist/schema.d.ts +81 -0
  148. package/dist/schema.d.ts.map +1 -0
  149. package/dist/schema.js +46 -0
  150. package/dist/schema.js.map +1 -0
  151. package/dist/storage/in-memory/index.d.ts +15 -0
  152. package/dist/storage/in-memory/index.d.ts.map +1 -0
  153. package/dist/storage/in-memory/index.js +14 -0
  154. package/dist/storage/in-memory/index.js.map +1 -0
  155. package/dist/storage/index.d.ts +14 -0
  156. package/dist/storage/index.d.ts.map +1 -0
  157. package/dist/storage/index.js +9 -0
  158. package/dist/storage/index.js.map +1 -0
  159. package/dist/storage/tauri/index.d.ts +19 -0
  160. package/dist/storage/tauri/index.d.ts.map +1 -0
  161. package/dist/storage/tauri/index.js +38 -0
  162. package/dist/storage/tauri/index.js.map +1 -0
  163. package/dist/storage/utils/idb.d.ts +10 -0
  164. package/dist/storage/utils/idb.d.ts.map +1 -0
  165. package/dist/storage/utils/idb.js +58 -0
  166. package/dist/storage/utils/idb.js.map +1 -0
  167. package/dist/storage/web-worker/index.d.ts +27 -0
  168. package/dist/storage/web-worker/index.d.ts.map +1 -0
  169. package/dist/storage/web-worker/index.js +74 -0
  170. package/dist/storage/web-worker/index.js.map +1 -0
  171. package/dist/storage/web-worker/worker.d.ts +13 -0
  172. package/dist/storage/web-worker/worker.d.ts.map +1 -0
  173. package/dist/storage/web-worker/worker.js +110 -0
  174. package/dist/storage/web-worker/worker.js.map +1 -0
  175. package/dist/store.d.ts +159 -0
  176. package/dist/store.d.ts.map +1 -0
  177. package/dist/store.js +626 -0
  178. package/dist/store.js.map +1 -0
  179. package/dist/util.d.ts +28 -0
  180. package/dist/util.d.ts.map +1 -0
  181. package/dist/util.js +55 -0
  182. package/dist/util.js.map +1 -0
  183. package/package.json +47 -19
  184. package/src/QueryCache.ts +1 -1
  185. package/src/__tests__/react/fixture.tsx +35 -39
  186. package/src/__tests__/react/{useLiveStoreComponent.test.tsx → useComponentState.test.tsx} +9 -20
  187. package/src/__tests__/react/useQuery.test.tsx +48 -0
  188. package/src/__tests__/react/utils/extractStackInfoFromStackTrace.test.ts +40 -0
  189. package/src/__tests__/reactive.test.ts +194 -142
  190. package/src/__tests__/reactiveQueries/sql.test.ts +372 -0
  191. package/src/effect/LiveStore.ts +22 -31
  192. package/src/events.ts +1 -1
  193. package/src/inMemoryDatabase.ts +117 -142
  194. package/src/index.ts +18 -22
  195. package/src/migrations.ts +119 -0
  196. package/src/otel.ts +0 -11
  197. package/src/react/LiveStoreProvider.tsx +24 -23
  198. package/src/react/index.ts +12 -7
  199. package/src/react/useComponentState.ts +409 -0
  200. package/src/react/useQuery.ts +58 -0
  201. package/src/react/useTemporaryQuery.ts +21 -0
  202. package/src/react/utils/extractStackInfoFromStackTrace.ts +47 -0
  203. package/src/reactive.ts +386 -267
  204. package/src/reactiveQueries/base-class.ts +61 -39
  205. package/src/reactiveQueries/graph.ts +15 -0
  206. package/src/reactiveQueries/graphql.ts +147 -31
  207. package/src/reactiveQueries/js.ts +54 -21
  208. package/src/reactiveQueries/sql.ts +128 -37
  209. package/src/schema.ts +69 -145
  210. package/src/storage/in-memory/index.ts +21 -0
  211. package/src/storage/index.ts +27 -0
  212. package/src/{backends/tauri.ts → storage/tauri/index.ts} +14 -28
  213. package/src/storage/web-worker/index.ts +116 -0
  214. package/src/{backends/web-worker.ts → storage/web-worker/worker.ts} +17 -52
  215. package/src/store.ts +466 -457
  216. package/src/util.ts +13 -3
  217. package/tsconfig.json +1 -3
  218. package/src/backends/base.ts +0 -67
  219. package/src/backends/index.ts +0 -98
  220. package/src/backends/noop.ts +0 -32
  221. package/src/backends/web-in-memory.ts +0 -65
  222. package/src/backends/web.ts +0 -97
  223. package/src/react/useGlobalQuery.ts +0 -40
  224. package/src/react/useGraphQL.ts +0 -112
  225. package/src/react/useLiveStoreComponent.ts +0 -483
  226. /package/src/{backends → storage}/utils/idb.ts +0 -0
@@ -1,27 +1,23 @@
1
- import { makeNoopTracer } from '@livestore/utils'
2
- import * as otel from '@opentelemetry/api'
3
1
  import { describe, expect, it } from 'vitest'
4
2
 
5
3
  import { ReactiveGraph } from '../reactive.js'
6
4
 
7
- const mockOtelCtx = otel.context.active()
8
-
9
5
  describe('a trivial graph', () => {
10
6
  const makeGraph = () => {
11
- const graph = new ReactiveGraph({ otelTracer: makeNoopTracer() })
12
- const a = graph.makeRef(1)
13
- const b = graph.makeRef(2)
7
+ const graph = new ReactiveGraph({})
8
+ graph.context = {}
9
+ const a = graph.makeRef(1, { label: 'a' })
10
+ const b = graph.makeRef(2, { label: 'b' })
14
11
  const numberOfRunsForC = { runs: 0 }
15
12
  const c = graph.makeThunk(
16
13
  (get) => {
17
14
  numberOfRunsForC.runs++
18
15
  return get(a) + get(b)
19
16
  },
20
- undefined,
21
- mockOtelCtx,
17
+ { label: 'c' },
22
18
  )
23
- const d = graph.makeRef(3)
24
- const e = graph.makeThunk((get) => get(c) + get(d), undefined, mockOtelCtx)
19
+ const d = graph.makeRef(3, { label: 'd' })
20
+ const e = graph.makeThunk((get) => get(c) + get(d), { label: 'e' })
25
21
 
26
22
  // a(1) b(2)
27
23
  // \ /
@@ -39,158 +35,216 @@ describe('a trivial graph', () => {
39
35
 
40
36
  it('has the right initial values', () => {
41
37
  const { c, e } = makeGraph()
42
- expect(c.result).toBe(3)
43
- expect(e.result).toBe(6)
38
+ expect(c.computeResult()).toBe(3)
39
+ expect(e.computeResult()).toBe(6)
44
40
  })
45
41
 
46
42
  it('propagates change through the graph', () => {
47
43
  const { graph, a, c, e } = makeGraph()
48
- graph.setRef(a, 5, undefined, mockOtelCtx)
49
- expect(c.result).toBe(7)
50
- expect(e.result).toBe(10)
44
+ graph.setRef(a, 5)
45
+ expect(c.computeResult()).toBe(7)
46
+ expect(e.computeResult()).toBe(10)
47
+ })
48
+
49
+ it('does not rerun downstream computations eagerly when an upstream dep changes', () => {
50
+ const { graph, a, c, numberOfRunsForC } = makeGraph()
51
+ expect(numberOfRunsForC.runs).toBe(0)
52
+ graph.setRef(a, 5)
53
+ expect(numberOfRunsForC.runs).toBe(0)
54
+ c.computeResult()
55
+ expect(numberOfRunsForC.runs).toBe(1)
56
+ })
57
+
58
+ it('does not rerun c when d is edited and e is rerun', () => {
59
+ const { graph, c, d, e, numberOfRunsForC } = makeGraph()
60
+ expect(numberOfRunsForC.runs).toBe(0)
61
+ expect(e.computeResult()).toBe(3 + 3)
62
+ expect(numberOfRunsForC.runs).toBe(1)
63
+ graph.setRef(d, 4)
64
+ expect(e.computeResult()).toBe(4 + 3)
65
+ expect(numberOfRunsForC.runs).toBe(1)
51
66
  })
52
67
 
53
68
  it('cuts off reactive propagation when a thunk evaluates to same result as before', () => {
54
69
  const { graph, a, c, d } = makeGraph()
55
70
 
56
71
  let numberOfRuns = 0
57
- const f = graph.makeThunk(
58
- (get) => {
59
- numberOfRuns++
60
- return get(c) + get(d)
61
- },
62
- undefined,
63
- mockOtelCtx,
64
- )
65
- expect(numberOfRuns).toBe(1) // initializing f should run it once
72
+ const f = graph.makeThunk((get) => {
73
+ numberOfRuns++
74
+ return get(c) + get(d)
75
+ })
76
+ expect(numberOfRuns).toBe(0) // defining f shouldn't run it yet
77
+ f.computeResult()
78
+ expect(numberOfRuns).toBe(1) // refreshing should run it once
66
79
 
67
80
  // f doesn't run because a is set to same value as before
68
- graph.setRef(a, 1, undefined, mockOtelCtx)
69
- expect(f.result).toBe(6)
70
- expect(numberOfRuns).toBe(1)
81
+ graph.setRef(a, 1)
82
+ expect(f.computeResult()).toBe(6)
83
+ // expect(numberOfRuns).toBe(1) // TODO comp caching
71
84
 
72
85
  // f runs because a is set to a different value
73
- graph.setRef(a, 5, undefined, mockOtelCtx)
74
- expect(f.result).toBe(10)
75
- expect(numberOfRuns).toBe(2)
86
+ graph.setRef(a, 5)
87
+ expect(f.computeResult()).toBe(10)
88
+ // expect(numberOfRuns).toBe(2) // TODO comp caching
76
89
 
77
90
  // f runs again when d is set to a different value
78
- graph.setRef(d, 4, undefined, mockOtelCtx)
79
- expect(f.result).toBe(11)
80
- expect(numberOfRuns).toBe(3)
91
+ graph.setRef(d, 4)
92
+ expect(f.computeResult()).toBe(11)
93
+ // expect(numberOfRuns).toBe(3) // TODO comp caching
81
94
 
82
95
  // f only runs one time if we set two refs together
83
- graph.setRefs(
84
- [
85
- [a, 6],
86
- [d, 5],
87
- ],
88
- undefined,
89
- mockOtelCtx,
90
- )
91
- expect(f.result).toBe(13)
92
- expect(numberOfRuns).toBe(4)
96
+ graph.setRefs([
97
+ [a, 6],
98
+ [d, 5],
99
+ ])
100
+ expect(f.computeResult()).toBe(13)
101
+ // expect(numberOfRuns).toBe(4) // TODO comp caching
93
102
  })
94
103
 
95
104
  it('only runs a thunk once when two upstream refs are updated together', () => {
96
105
  const { graph, a, b, c, numberOfRunsForC } = makeGraph()
106
+ graph.setRefs([
107
+ [a, 5],
108
+ [b, 6],
109
+ ])
110
+ expect(numberOfRunsForC.runs).toBe(0)
111
+ expect(c.computeResult()).toBe(11)
97
112
  expect(numberOfRunsForC.runs).toBe(1)
98
- graph.setRefs(
99
- [
100
- [a, 5],
101
- [b, 6],
102
- ],
103
- undefined,
104
- mockOtelCtx,
105
- )
106
- expect(numberOfRunsForC.runs).toBe(2)
107
- expect(c.result).toBe(11)
108
113
  })
109
114
 
110
- it('skips refresh when that option is passed when setting a single ref', () => {
111
- const { graph, a, c, numberOfRunsForC } = makeGraph()
112
- expect(numberOfRunsForC.runs).toBe(1)
115
+ describe('effects', () => {
116
+ // TODO TBD whether we want to keep this as intended behavior
117
+ it(`doesn't run on initial definition`, () => {
118
+ const { graph, c, numberOfRunsForC } = makeGraph()
119
+ expect(numberOfRunsForC.runs).toBe(0)
120
+ c.computeResult()
121
+ expect(numberOfRunsForC.runs).toBe(1)
122
+
123
+ let numberOfEffectRuns = 0
124
+ const effect = graph.makeEffect((get) => {
125
+ // establish a dependency on thunk c and mutate an outside value
126
+ expect(get(c)).toBe(3)
127
+ numberOfEffectRuns++
128
+ })
129
+ expect(numberOfEffectRuns).toBe(0)
130
+ expect(numberOfRunsForC.runs).toBe(1)
131
+
132
+ effect.doEffect()
133
+ expect(numberOfEffectRuns).toBe(1)
134
+ })
113
135
 
114
- graph.setRef(a, 5, { skipRefresh: true }, mockOtelCtx)
136
+ it('only reruns an effect if the thunk value changed', () => {
137
+ const { graph, a, c } = makeGraph()
138
+ let numberOfEffectRuns = 0
139
+ let aHasChanged = true
140
+ expect(numberOfEffectRuns).toBe(0)
141
+ const effect = graph.makeEffect((get) => {
142
+ // establish a dependency on thunk c and mutate an outside value
143
+ expect(get(c)).toBe(aHasChanged ? 3 : 4)
144
+ numberOfEffectRuns++
145
+ })
146
+
147
+ expect(numberOfEffectRuns).toBe(0)
148
+ effect.doEffect()
149
+ expect(numberOfEffectRuns).toBe(1)
115
150
 
116
- // C hasn't changed
117
- expect(numberOfRunsForC.runs).toBe(1)
118
- expect(c.result).toBe(3)
151
+ // if we set a to the same value, the effect should not run again
152
+ graph.setRef(a, 1)
153
+ // expect(numberOfCallsToC).toBe(1) // TODO comp caching
154
+
155
+ aHasChanged = false
119
156
 
120
- // Now we trigger a refresh and everything runs
121
- graph.refresh(undefined, mockOtelCtx)
122
- expect(numberOfRunsForC.runs).toBe(2)
123
- expect(c.result).toBe(7)
157
+ graph.setRef(a, 2)
158
+ // expect(numberOfCallsToC).toBe(2) // TODO comp caching
159
+ })
124
160
  })
161
+ })
125
162
 
126
- it('skips refresh when that option is passed when setting multiple refs together', () => {
127
- const { graph, a, b, c, numberOfRunsForC } = makeGraph()
128
- expect(numberOfRunsForC.runs).toBe(1)
163
+ describe('a dynamic graph', () => {
164
+ const makeGraph = () => {
165
+ const graph = new ReactiveGraph({})
166
+ graph.context = {}
129
167
 
130
- graph.setRefs(
131
- [
132
- [a, 5],
133
- [b, 6],
134
- ],
135
- { skipRefresh: true },
136
- mockOtelCtx,
168
+ const a = graph.makeRef(1, { label: 'a' })
169
+ const b = graph.makeRef(2, { label: 'b' })
170
+ const c = graph.makeRef<'a' | 'b'>('a', { label: 'c' })
171
+ const numberOfRunsForD = { runs: 0 }
172
+ const d = graph.makeThunk(
173
+ (get) => {
174
+ numberOfRunsForD.runs++
175
+ return get(c) === 'a' ? get(a) : get(b)
176
+ },
177
+ { label: 'd' },
137
178
  )
179
+ const e = graph.makeRef(2, { label: 'e' })
180
+ const f = graph.makeThunk((get) => get(d) * get(e), { label: 'f' })
138
181
 
139
- // C hasn't changed
140
- expect(numberOfRunsForC.runs).toBe(1)
141
- expect(c.result).toBe(3)
182
+ // a(1) b(2) c('a')
183
+ // \ / /
184
+ // \ / /
185
+ // d = a or b depending on c
186
+ // \
187
+ // \
188
+ // e(2) \
189
+ // \ \
190
+ // \ \
191
+ // f = d * e
142
192
 
143
- // Now we trigger a refresh and everything runs
144
- graph.refresh(undefined, mockOtelCtx)
145
- expect(numberOfRunsForC.runs).toBe(2)
146
- expect(c.result).toBe(11)
147
- })
193
+ return { graph, a, b, c, d, e, f, numberOfRunsForD }
194
+ }
148
195
 
149
- describe('effects', () => {
150
- it('only reruns an effect if the thunk value changed', () => {
151
- const { graph, a, c } = makeGraph()
152
- let numberOfCallsToC = 0
153
- graph.makeEffect(
154
- (get) => {
155
- // establish a dependency on thunk c and mutate an outside value
156
- get(c)
157
- numberOfCallsToC++
158
- },
159
- undefined,
160
- mockOtelCtx,
161
- )
162
- expect(numberOfCallsToC).toBe(1)
196
+ it('has the right initial values', () => {
197
+ const { d, f } = makeGraph()
198
+ expect(d.computeResult()).toBe(1)
199
+ expect(f.computeResult()).toBe(2)
200
+ })
163
201
 
164
- // if we set a to the same value, the effect should not run again
165
- graph.setRef(a, 1, undefined, mockOtelCtx)
166
- expect(numberOfCallsToC).toBe(1)
202
+ it('dynamically adjusts d when a, b or c changes', () => {
203
+ const { graph, c, d, e, f, numberOfRunsForD } = makeGraph()
204
+ expect(numberOfRunsForD.runs).toBe(0)
205
+ expect(d.computeResult()).toBe(1)
206
+ expect(f.computeResult()).toBe(2)
207
+ expect(numberOfRunsForD.runs).toBe(1)
208
+ graph.setRef(c, 'b')
209
+ expect(d.computeResult()).toBe(2)
210
+ expect(f.computeResult()).toBe(4)
211
+ expect(numberOfRunsForD.runs).toBe(2)
212
+ graph.setRef(e, 3)
213
+ expect(f.computeResult()).toBe(6)
214
+ expect(numberOfRunsForD.runs).toBe(2)
215
+ })
167
216
 
168
- graph.setRef(a, 2, undefined, mockOtelCtx)
169
- expect(numberOfCallsToC).toBe(2)
170
- })
217
+ it('runs d only when a changes, not b', () => {
218
+ const { graph, a, b, d, numberOfRunsForD } = makeGraph()
219
+ numberOfRunsForD.runs = 0
220
+ d.computeResult()
221
+ expect(numberOfRunsForD.runs).toBe(1)
222
+ graph.setRef(a, 3)
223
+ expect(d.computeResult()).toBe(3)
224
+ expect(numberOfRunsForD.runs).toBe(2)
225
+ graph.setRef(b, 4)
226
+ expect(d.computeResult()).toBe(3)
227
+ expect(numberOfRunsForD.runs).toBe(2)
171
228
  })
172
229
  })
173
230
 
174
231
  describe('a diamond shaped graph', () => {
175
232
  const makeGraph = () => {
176
- const graph = new ReactiveGraph({ otelTracer: makeNoopTracer() })
233
+ const graph = new ReactiveGraph({})
234
+ graph.context = {}
177
235
  const a = graph.makeRef(1)
178
- const b = graph.makeThunk((get) => get(a) + 1, undefined, mockOtelCtx)
179
- const c = graph.makeThunk((get) => get(a) + 1, undefined, mockOtelCtx)
236
+ const b = graph.makeThunk((get) => get(a) + 1)
237
+ const c = graph.makeThunk((get) => get(a) + 1)
180
238
 
181
239
  // track the number of times d has run in an object so we can mutate it
182
240
  const dRuns = { runs: 0 }
183
241
 
184
242
  // normally thunks aren't supposed to side effect;
185
243
  // we do it here to track the number of times d has run
186
- const d = graph.makeThunk(
187
- (get) => {
188
- dRuns.runs++
189
- return get(b) + get(c)
190
- },
191
- undefined,
192
- mockOtelCtx,
193
- )
244
+ const d = graph.makeThunk((get) => {
245
+ dRuns.runs++
246
+ return get(b) + get(c)
247
+ })
194
248
 
195
249
  // a(1)
196
250
  // / \
@@ -203,48 +257,46 @@ describe('a diamond shaped graph', () => {
203
257
 
204
258
  it('has the right initial values', () => {
205
259
  const { b, c, d } = makeGraph()
206
- expect(b.result).toBe(2)
207
- expect(c.result).toBe(2)
208
- expect(d.result).toBe(4)
260
+ expect(b.computeResult()).toBe(2)
261
+ expect(c.computeResult()).toBe(2)
262
+ expect(d.computeResult()).toBe(4)
209
263
  })
210
264
 
211
265
  it('propagates change through the graph', () => {
212
266
  const { graph, a, b, c, d } = makeGraph()
213
- graph.setRef(a, 5, undefined, mockOtelCtx)
214
- expect(b.result).toBe(6)
215
- expect(c.result).toBe(6)
216
- expect(d.result).toBe(12)
267
+ graph.setRef(a, 5)
268
+ expect(b.computeResult()).toBe(6)
269
+ expect(c.computeResult()).toBe(6)
270
+ expect(d.computeResult()).toBe(12)
217
271
  })
218
272
 
219
273
  // if we're being efficient, we should update b and c before updating d,
220
274
  // so d only needs to update one time
221
275
  it('only runs d once when a changes', () => {
222
- const { graph, a, dRuns } = makeGraph()
276
+ const { graph, a, d, dRuns } = makeGraph()
277
+ expect(dRuns.runs).toBe(0)
278
+ d.computeResult()
223
279
  expect(dRuns.runs).toBe(1)
224
- graph.setRef(a, 5, undefined, mockOtelCtx)
280
+ graph.setRef(a, 5)
281
+ d.computeResult()
282
+ d.computeResult() // even extra calls to computeResult should not run d again
225
283
  expect(dRuns.runs).toBe(2)
226
284
  })
227
285
  })
228
286
 
229
- // TODO handle `undefined` in the graph
230
- describe.todo('a trivial graph with undefined', () => {
287
+ describe('a trivial graph with undefined', () => {
231
288
  const makeGraph = () => {
232
- const graph = new ReactiveGraph({ otelTracer: makeNoopTracer() })
233
- const a = graph.makeRef(undefined)
234
- const b = graph.makeRef(2)
235
- const numberOfRunsForC = { runs: 0 }
236
- const c = graph.makeThunk(
237
- (get) => {
238
- numberOfRunsForC.runs++
239
- return (get(a) ?? 0) + get(b)
240
- },
241
- undefined,
242
- mockOtelCtx,
243
- )
289
+ const graph = new ReactiveGraph({})
290
+ graph.context = {}
291
+ const a = graph.makeRef(1)
292
+ const b = graph.makeRef(undefined)
293
+ const c = graph.makeThunk((get) => {
294
+ return get(a) + (get(b) ?? 0)
295
+ })
244
296
  const d = graph.makeRef(3)
245
- const e = graph.makeThunk((get) => get(c) + get(d), undefined, mockOtelCtx)
297
+ const e = graph.makeThunk((get) => get(c) + get(d))
246
298
 
247
- // a(1) b(2)
299
+ // a(1) b(undefined)
248
300
  // \ /
249
301
  // \ /
250
302
  // c = a + b
@@ -255,12 +307,12 @@ describe.todo('a trivial graph with undefined', () => {
255
307
  // \ \
256
308
  // e = c + d
257
309
 
258
- return { graph, a, b, c, d, e, numberOfRunsForC }
310
+ return { graph, a, b, c, d, e }
259
311
  }
260
312
 
261
313
  it('has the right initial values', () => {
262
314
  const { c, e } = makeGraph()
263
- expect(c.result).toBe(3)
264
- expect(e.result).toBe(6)
315
+ expect(c.computeResult()).toBe(1)
316
+ expect(e.computeResult()).toBe(4)
265
317
  })
266
318
  })