signalium 0.3.8 → 1.0.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 (287) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/CHANGELOG.md +21 -0
  3. package/build/react.js +19 -0
  4. package/build/transform.js +19 -0
  5. package/dist/cjs/config.d.ts +8 -3
  6. package/dist/cjs/config.d.ts.map +1 -1
  7. package/dist/cjs/config.js +14 -8
  8. package/dist/cjs/config.js.map +1 -1
  9. package/dist/cjs/debug.d.ts +2 -2
  10. package/dist/cjs/debug.d.ts.map +1 -1
  11. package/dist/cjs/debug.js +3 -3
  12. package/dist/cjs/debug.js.map +1 -1
  13. package/dist/cjs/hooks.d.ts +14 -42
  14. package/dist/cjs/hooks.d.ts.map +1 -1
  15. package/dist/cjs/hooks.js +19 -240
  16. package/dist/cjs/hooks.js.map +1 -1
  17. package/dist/cjs/index.d.ts +5 -3
  18. package/dist/cjs/index.d.ts.map +1 -1
  19. package/dist/cjs/index.js +18 -18
  20. package/dist/cjs/index.js.map +1 -1
  21. package/dist/cjs/internals/async.d.ts +52 -0
  22. package/dist/cjs/internals/async.d.ts.map +1 -0
  23. package/dist/cjs/internals/async.js +394 -0
  24. package/dist/cjs/internals/async.js.map +1 -0
  25. package/dist/cjs/internals/connect.d.ts +4 -0
  26. package/dist/cjs/internals/connect.d.ts.map +1 -0
  27. package/dist/cjs/internals/connect.js +37 -0
  28. package/dist/cjs/internals/connect.js.map +1 -0
  29. package/dist/cjs/internals/consumer.d.ts +6 -0
  30. package/dist/cjs/internals/consumer.d.ts.map +1 -0
  31. package/dist/cjs/internals/consumer.js +13 -0
  32. package/dist/cjs/internals/consumer.js.map +1 -0
  33. package/dist/cjs/internals/contexts.d.ts +33 -0
  34. package/dist/cjs/internals/contexts.d.ts.map +1 -0
  35. package/dist/cjs/internals/contexts.js +103 -0
  36. package/dist/cjs/internals/contexts.js.map +1 -0
  37. package/dist/cjs/internals/derived.d.ts +66 -0
  38. package/dist/cjs/internals/derived.d.ts.map +1 -0
  39. package/dist/cjs/internals/derived.js +128 -0
  40. package/dist/cjs/internals/derived.js.map +1 -0
  41. package/dist/cjs/internals/dirty.d.ts +5 -0
  42. package/dist/cjs/internals/dirty.d.ts.map +1 -0
  43. package/dist/cjs/internals/dirty.js +79 -0
  44. package/dist/cjs/internals/dirty.js.map +1 -0
  45. package/dist/cjs/internals/edge.d.ts +32 -0
  46. package/dist/cjs/internals/edge.d.ts.map +1 -0
  47. package/dist/cjs/internals/edge.js +59 -0
  48. package/dist/cjs/internals/edge.js.map +1 -0
  49. package/dist/cjs/internals/get.d.ts +10 -0
  50. package/dist/cjs/internals/get.d.ts.map +1 -0
  51. package/dist/cjs/internals/get.js +255 -0
  52. package/dist/cjs/internals/get.js.map +1 -0
  53. package/dist/cjs/internals/scheduling.d.ts +12 -0
  54. package/dist/cjs/internals/scheduling.d.ts.map +1 -0
  55. package/dist/cjs/internals/scheduling.js +117 -0
  56. package/dist/cjs/internals/scheduling.js.map +1 -0
  57. package/dist/cjs/internals/state.d.ts +18 -0
  58. package/dist/cjs/internals/state.d.ts.map +1 -0
  59. package/dist/cjs/internals/state.js +88 -0
  60. package/dist/cjs/internals/state.js.map +1 -0
  61. package/dist/cjs/internals/utils/debug-name.d.ts +2 -0
  62. package/dist/cjs/internals/utils/debug-name.d.ts.map +1 -0
  63. package/dist/cjs/internals/utils/debug-name.js +14 -0
  64. package/dist/cjs/internals/utils/debug-name.js.map +1 -0
  65. package/dist/cjs/internals/utils/equals.d.ts +3 -0
  66. package/dist/cjs/internals/utils/equals.d.ts.map +1 -0
  67. package/dist/cjs/internals/utils/equals.js +13 -0
  68. package/dist/cjs/internals/utils/equals.js.map +1 -0
  69. package/dist/cjs/internals/utils/hash.d.ts +7 -0
  70. package/dist/cjs/internals/utils/hash.d.ts.map +1 -0
  71. package/dist/cjs/internals/utils/hash.js +181 -0
  72. package/dist/cjs/internals/utils/hash.js.map +1 -0
  73. package/dist/cjs/internals/utils/stringify.d.ts +3 -0
  74. package/dist/cjs/internals/utils/stringify.d.ts.map +1 -0
  75. package/dist/cjs/{utils.js → internals/utils/stringify.js} +5 -27
  76. package/dist/cjs/internals/utils/stringify.js.map +1 -0
  77. package/dist/cjs/internals/utils/type-utils.d.ts +6 -0
  78. package/dist/cjs/internals/utils/type-utils.d.ts.map +1 -0
  79. package/dist/cjs/internals/utils/type-utils.js +22 -0
  80. package/dist/cjs/internals/utils/type-utils.js.map +1 -0
  81. package/dist/cjs/react/context.d.ts +1 -1
  82. package/dist/cjs/react/context.d.ts.map +1 -1
  83. package/dist/cjs/react/provider.d.ts +4 -3
  84. package/dist/cjs/react/provider.d.ts.map +1 -1
  85. package/dist/cjs/react/provider.js +7 -3
  86. package/dist/cjs/react/provider.js.map +1 -1
  87. package/dist/cjs/react/setup.d.ts.map +1 -1
  88. package/dist/cjs/react/setup.js +2 -1
  89. package/dist/cjs/react/setup.js.map +1 -1
  90. package/dist/cjs/react/signal-value.d.ts +5 -1
  91. package/dist/cjs/react/signal-value.d.ts.map +1 -1
  92. package/dist/cjs/react/signal-value.js +35 -45
  93. package/dist/cjs/react/signal-value.js.map +1 -1
  94. package/dist/cjs/trace.d.ts +32 -28
  95. package/dist/cjs/trace.d.ts.map +1 -1
  96. package/dist/cjs/trace.js +14 -16
  97. package/dist/cjs/trace.js.map +1 -1
  98. package/dist/cjs/transform.d.ts +6 -0
  99. package/dist/cjs/transform.d.ts.map +1 -0
  100. package/dist/cjs/transform.js +92 -0
  101. package/dist/cjs/transform.js.map +1 -0
  102. package/dist/cjs/types.d.ts +32 -40
  103. package/dist/cjs/types.d.ts.map +1 -1
  104. package/dist/esm/config.d.ts +8 -3
  105. package/dist/esm/config.d.ts.map +1 -1
  106. package/dist/esm/config.js +12 -7
  107. package/dist/esm/config.js.map +1 -1
  108. package/dist/esm/debug.d.ts +2 -2
  109. package/dist/esm/debug.d.ts.map +1 -1
  110. package/dist/esm/debug.js +2 -2
  111. package/dist/esm/debug.js.map +1 -1
  112. package/dist/esm/hooks.d.ts +14 -42
  113. package/dist/esm/hooks.d.ts.map +1 -1
  114. package/dist/esm/hooks.js +17 -226
  115. package/dist/esm/hooks.js.map +1 -1
  116. package/dist/esm/index.d.ts +5 -3
  117. package/dist/esm/index.d.ts.map +1 -1
  118. package/dist/esm/index.js +5 -3
  119. package/dist/esm/index.js.map +1 -1
  120. package/dist/esm/internals/async.d.ts +52 -0
  121. package/dist/esm/internals/async.d.ts.map +1 -0
  122. package/dist/esm/internals/async.js +387 -0
  123. package/dist/esm/internals/async.js.map +1 -0
  124. package/dist/esm/internals/connect.d.ts +4 -0
  125. package/dist/esm/internals/connect.d.ts.map +1 -0
  126. package/dist/esm/internals/connect.js +33 -0
  127. package/dist/esm/internals/connect.js.map +1 -0
  128. package/dist/esm/internals/consumer.d.ts +6 -0
  129. package/dist/esm/internals/consumer.d.ts.map +1 -0
  130. package/dist/esm/internals/consumer.js +9 -0
  131. package/dist/esm/internals/consumer.js.map +1 -0
  132. package/dist/esm/internals/contexts.d.ts +33 -0
  133. package/dist/esm/internals/contexts.d.ts.map +1 -0
  134. package/dist/esm/internals/contexts.js +92 -0
  135. package/dist/esm/internals/contexts.js.map +1 -0
  136. package/dist/esm/internals/derived.d.ts +66 -0
  137. package/dist/esm/internals/derived.d.ts.map +1 -0
  138. package/dist/esm/internals/derived.js +118 -0
  139. package/dist/esm/internals/derived.js.map +1 -0
  140. package/dist/esm/internals/dirty.d.ts +5 -0
  141. package/dist/esm/internals/dirty.d.ts.map +1 -0
  142. package/dist/esm/internals/dirty.js +75 -0
  143. package/dist/esm/internals/dirty.js.map +1 -0
  144. package/dist/esm/internals/edge.d.ts +32 -0
  145. package/dist/esm/internals/edge.d.ts.map +1 -0
  146. package/dist/esm/internals/edge.js +54 -0
  147. package/dist/esm/internals/edge.js.map +1 -0
  148. package/dist/esm/internals/get.d.ts +10 -0
  149. package/dist/esm/internals/get.d.ts.map +1 -0
  150. package/dist/esm/internals/get.js +247 -0
  151. package/dist/esm/internals/get.js.map +1 -0
  152. package/dist/esm/internals/scheduling.d.ts +12 -0
  153. package/dist/esm/internals/scheduling.d.ts.map +1 -0
  154. package/dist/esm/internals/scheduling.js +106 -0
  155. package/dist/esm/internals/scheduling.js.map +1 -0
  156. package/dist/esm/internals/state.d.ts +18 -0
  157. package/dist/esm/internals/state.d.ts.map +1 -0
  158. package/dist/esm/internals/state.js +82 -0
  159. package/dist/esm/internals/state.js.map +1 -0
  160. package/dist/esm/internals/utils/debug-name.d.ts +2 -0
  161. package/dist/esm/internals/utils/debug-name.d.ts.map +1 -0
  162. package/dist/esm/internals/utils/debug-name.js +11 -0
  163. package/dist/esm/internals/utils/debug-name.js.map +1 -0
  164. package/dist/esm/internals/utils/equals.d.ts +3 -0
  165. package/dist/esm/internals/utils/equals.d.ts.map +1 -0
  166. package/dist/esm/internals/utils/equals.js +9 -0
  167. package/dist/esm/internals/utils/equals.js.map +1 -0
  168. package/dist/esm/internals/utils/hash.d.ts +7 -0
  169. package/dist/esm/internals/utils/hash.d.ts.map +1 -0
  170. package/dist/esm/internals/utils/hash.js +174 -0
  171. package/dist/esm/internals/utils/hash.js.map +1 -0
  172. package/dist/esm/internals/utils/stringify.d.ts +3 -0
  173. package/dist/esm/internals/utils/stringify.d.ts.map +1 -0
  174. package/dist/esm/{utils.js → internals/utils/stringify.js} +4 -25
  175. package/dist/esm/internals/utils/stringify.js.map +1 -0
  176. package/dist/esm/internals/utils/type-utils.d.ts +6 -0
  177. package/dist/esm/internals/utils/type-utils.d.ts.map +1 -0
  178. package/dist/esm/internals/utils/type-utils.js +15 -0
  179. package/dist/esm/internals/utils/type-utils.js.map +1 -0
  180. package/dist/esm/react/context.d.ts +1 -1
  181. package/dist/esm/react/context.d.ts.map +1 -1
  182. package/dist/esm/react/provider.d.ts +4 -3
  183. package/dist/esm/react/provider.d.ts.map +1 -1
  184. package/dist/esm/react/provider.js +6 -2
  185. package/dist/esm/react/provider.js.map +1 -1
  186. package/dist/esm/react/setup.d.ts.map +1 -1
  187. package/dist/esm/react/setup.js +3 -2
  188. package/dist/esm/react/setup.js.map +1 -1
  189. package/dist/esm/react/signal-value.d.ts +5 -1
  190. package/dist/esm/react/signal-value.d.ts.map +1 -1
  191. package/dist/esm/react/signal-value.js +34 -45
  192. package/dist/esm/react/signal-value.js.map +1 -1
  193. package/dist/esm/trace.d.ts +32 -28
  194. package/dist/esm/trace.d.ts.map +1 -1
  195. package/dist/esm/trace.js +13 -15
  196. package/dist/esm/trace.js.map +1 -1
  197. package/dist/esm/transform.d.ts +6 -0
  198. package/dist/esm/transform.d.ts.map +1 -0
  199. package/dist/esm/transform.js +89 -0
  200. package/dist/esm/transform.js.map +1 -0
  201. package/dist/esm/types.d.ts +32 -40
  202. package/dist/esm/types.d.ts.map +1 -1
  203. package/package.json +23 -4
  204. package/src/__tests__/__snapshots__/context.test.ts.snap +2101 -0
  205. package/src/__tests__/__snapshots__/nesting.test.ts.snap +16201 -0
  206. package/src/__tests__/__snapshots__/params-and-state.test.ts.snap +1879 -0
  207. package/src/__tests__/async-task.test.ts +327 -0
  208. package/src/__tests__/context.test.ts +517 -0
  209. package/src/__tests__/nesting.test.ts +298 -0
  210. package/src/__tests__/params-and-state.test.ts +230 -0
  211. package/src/__tests__/reactive-async.test.ts +548 -0
  212. package/src/__tests__/reactive-sync.test.ts +130 -0
  213. package/src/__tests__/subscription.test.ts +510 -0
  214. package/src/__tests__/utils/async.ts +1 -1
  215. package/src/__tests__/utils/instrumented-hooks.ts +229 -124
  216. package/src/__tests__/utils/permute.ts +25 -14
  217. package/src/config.ts +19 -9
  218. package/src/debug.ts +2 -2
  219. package/src/hooks.ts +46 -380
  220. package/src/index.ts +7 -24
  221. package/src/internals/async.ts +558 -0
  222. package/src/internals/connect.ts +41 -0
  223. package/src/internals/consumer.ts +13 -0
  224. package/src/internals/contexts.ts +133 -0
  225. package/src/internals/derived.ts +208 -0
  226. package/src/internals/dirty.ts +91 -0
  227. package/src/internals/edge.ts +109 -0
  228. package/src/internals/get.ts +298 -0
  229. package/src/internals/scheduling.ts +140 -0
  230. package/src/internals/state.ts +111 -0
  231. package/src/internals/utils/debug-name.ts +14 -0
  232. package/src/internals/utils/equals.ts +12 -0
  233. package/src/internals/utils/hash.ts +221 -0
  234. package/src/{utils.ts → internals/utils/stringify.ts} +3 -29
  235. package/src/internals/utils/type-utils.ts +19 -0
  236. package/src/react/__tests__/async.test.tsx +704 -0
  237. package/src/react/__tests__/basic.test.tsx +95 -0
  238. package/src/react/__tests__/contexts.test.tsx +99 -0
  239. package/src/react/__tests__/subscriptions.test.tsx +49 -0
  240. package/src/react/__tests__/utils.tsx +40 -0
  241. package/src/react/context.ts +1 -1
  242. package/src/react/provider.tsx +12 -4
  243. package/src/react/setup.ts +3 -2
  244. package/src/react/signal-value.ts +47 -67
  245. package/src/trace.ts +43 -38
  246. package/src/transform.ts +113 -0
  247. package/src/types.ts +56 -46
  248. package/transform.js +19 -0
  249. package/vitest.workspace.ts +38 -2
  250. package/dist/cjs/scheduling.d.ts +0 -11
  251. package/dist/cjs/scheduling.d.ts.map +0 -1
  252. package/dist/cjs/scheduling.js +0 -108
  253. package/dist/cjs/scheduling.js.map +0 -1
  254. package/dist/cjs/signals.d.ts +0 -73
  255. package/dist/cjs/signals.d.ts.map +0 -1
  256. package/dist/cjs/signals.js +0 -632
  257. package/dist/cjs/signals.js.map +0 -1
  258. package/dist/cjs/utils.d.ts +0 -4
  259. package/dist/cjs/utils.d.ts.map +0 -1
  260. package/dist/cjs/utils.js.map +0 -1
  261. package/dist/esm/scheduling.d.ts +0 -11
  262. package/dist/esm/scheduling.d.ts.map +0 -1
  263. package/dist/esm/scheduling.js +0 -97
  264. package/dist/esm/scheduling.js.map +0 -1
  265. package/dist/esm/signals.d.ts +0 -73
  266. package/dist/esm/signals.d.ts.map +0 -1
  267. package/dist/esm/signals.js +0 -614
  268. package/dist/esm/signals.js.map +0 -1
  269. package/dist/esm/utils.d.ts +0 -4
  270. package/dist/esm/utils.d.ts.map +0 -1
  271. package/dist/esm/utils.js.map +0 -1
  272. package/src/__tests__/hooks/async-computed.test.ts +0 -190
  273. package/src/__tests__/hooks/async-task.test.ts +0 -334
  274. package/src/__tests__/hooks/computed.test.ts +0 -126
  275. package/src/__tests__/hooks/context.test.ts +0 -527
  276. package/src/__tests__/hooks/nesting.test.ts +0 -303
  277. package/src/__tests__/hooks/params-and-state.test.ts +0 -168
  278. package/src/__tests__/hooks/subscription.test.ts +0 -97
  279. package/src/__tests__/signals/async.test.ts +0 -416
  280. package/src/__tests__/signals/basic.test.ts +0 -399
  281. package/src/__tests__/signals/subscription.test.ts +0 -632
  282. package/src/__tests__/signals/watcher.test.ts +0 -253
  283. package/src/__tests__/utils/builders.ts +0 -22
  284. package/src/__tests__/utils/instrumented-signals.ts +0 -291
  285. package/src/react/__tests__/react.test.tsx +0 -227
  286. package/src/scheduling.ts +0 -130
  287. package/src/signals.ts +0 -824
@@ -0,0 +1,95 @@
1
+ import { beforeEach, describe, expect, test } from 'vitest';
2
+ import { render } from 'vitest-browser-react';
3
+ import { state, reactive, createContext, useContext } from 'signalium';
4
+ import { ContextProvider, setupReact, useStateSignal } from '../index.js';
5
+ import React, { useState } from 'react';
6
+ import { userEvent } from '@vitest/browser/context';
7
+ import { sleep } from '../../__tests__/utils/async.js';
8
+
9
+ setupReact();
10
+
11
+ describe('React > basic', () => {
12
+ test('basic state usage works', async () => {
13
+ const value = state('Hello');
14
+
15
+ function Component(): React.ReactNode {
16
+ return <div>{value.get()}</div>;
17
+ }
18
+
19
+ const { getByText } = render(<Component />);
20
+
21
+ await expect.element(getByText('Hello')).toBeInTheDocument();
22
+
23
+ value.set('World');
24
+
25
+ await expect.element(getByText('World')).toBeInTheDocument();
26
+ });
27
+
28
+ test('useStateSignal works', async () => {
29
+ function Component(): React.ReactNode {
30
+ const value = useStateSignal('Hello');
31
+
32
+ return (
33
+ <div>
34
+ {value.get()}
35
+ <button onClick={() => value.set('World')}>Toggle</button>
36
+ </div>
37
+ );
38
+ }
39
+
40
+ const { getByText } = render(<Component />);
41
+
42
+ await expect.element(getByText('Hello')).toBeInTheDocument();
43
+
44
+ await userEvent.click(getByText('Toggle'));
45
+
46
+ await expect.element(getByText('World')).toBeInTheDocument();
47
+ });
48
+
49
+ test('basic computed usage works', async () => {
50
+ const value = state('Hello');
51
+
52
+ const derived = reactive(() => `${value.get()}, World`);
53
+
54
+ function Component(): React.ReactNode {
55
+ return <div>{derived()}</div>;
56
+ }
57
+
58
+ const { getByText } = render(<Component />);
59
+
60
+ await expect.element(getByText('Hello, World')).toBeInTheDocument();
61
+
62
+ value.set('Hey');
63
+
64
+ await expect.element(getByText('Hey, World')).toBeInTheDocument();
65
+ });
66
+
67
+ test('computed updates when params change', async () => {
68
+ const value = state('Hello');
69
+
70
+ const derived = reactive((universe: boolean) => `${value.get()}, ${universe ? 'Universe' : 'World'}`);
71
+
72
+ function Component(): React.ReactNode {
73
+ const [universe, setUniverse] = useState(true);
74
+
75
+ return (
76
+ <div>
77
+ {derived(universe)}
78
+ <button onClick={() => setUniverse(!universe)}>Toggle Universe</button>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ const { getByText } = render(<Component />);
84
+
85
+ await expect.element(getByText('Hello, Universe')).toBeInTheDocument();
86
+
87
+ value.set('Hey');
88
+
89
+ await expect.element(getByText('Hey, Universe')).toBeInTheDocument();
90
+
91
+ await userEvent.click(getByText('Toggle Universe'));
92
+
93
+ await expect.element(getByText('Hey, World')).toBeInTheDocument();
94
+ });
95
+ });
@@ -0,0 +1,99 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { render } from 'vitest-browser-react';
3
+ import { state, reactive, createContext, useContext } from 'signalium';
4
+ import { ContextProvider, setupReact } from '../index.js';
5
+ import React, { useState } from 'react';
6
+
7
+ setupReact();
8
+
9
+ describe('React > contexts', () => {
10
+ test('useContext works inside computed with default value', async () => {
11
+ const value = state('Hello');
12
+ const context = createContext(value);
13
+
14
+ const derived = reactive(() => `${useContext(context).get()}, World`);
15
+
16
+ function Component(): React.ReactNode {
17
+ return <div>{derived()}</div>;
18
+ }
19
+
20
+ const { getByText } = render(<Component />);
21
+
22
+ await expect.element(getByText('Hello, World')).toBeInTheDocument();
23
+
24
+ value.set('Hey');
25
+
26
+ await expect.element(getByText('Hey, World')).toBeInTheDocument();
27
+ });
28
+
29
+ test('useContext works at root level with default value', async () => {
30
+ const value = state('Hello');
31
+ const context = createContext(value);
32
+
33
+ function Component(): React.ReactNode {
34
+ const v = useContext(context);
35
+
36
+ return <div>{v.get()}, World</div>;
37
+ }
38
+
39
+ const { getByText } = render(
40
+ <ContextProvider contexts={[]}>
41
+ <Component />
42
+ </ContextProvider>,
43
+ );
44
+
45
+ await expect.element(getByText('Hello, World')).toBeInTheDocument();
46
+
47
+ value.set('Hey');
48
+
49
+ await expect.element(getByText('Hey, World')).toBeInTheDocument();
50
+ });
51
+
52
+ test('useContext works inside computed value passed via context provider', async () => {
53
+ const value = state('Hello');
54
+ const override = state('Hey');
55
+ const context = createContext(value);
56
+
57
+ const derived = reactive(() => `${useContext(context).get()}, World`);
58
+
59
+ function Component(): React.ReactNode {
60
+ return <div>{derived()}</div>;
61
+ }
62
+
63
+ const { getByText } = render(
64
+ <ContextProvider contexts={[[context, override]]}>
65
+ <Component />
66
+ </ContextProvider>,
67
+ );
68
+
69
+ await expect.element(getByText('Hey, World')).toBeInTheDocument();
70
+
71
+ override.set('Hi');
72
+
73
+ await expect.element(getByText('Hi, World')).toBeInTheDocument();
74
+ });
75
+
76
+ test('useContext works at root level with default value', async () => {
77
+ const value = state('Hello');
78
+ const override = state('Hey');
79
+ const context = createContext(value);
80
+
81
+ function Component(): React.ReactNode {
82
+ const v = useContext(context);
83
+
84
+ return <div>{v.get()}, World</div>;
85
+ }
86
+
87
+ const { getByText } = render(
88
+ <ContextProvider contexts={[[context, override]]}>
89
+ <Component />
90
+ </ContextProvider>,
91
+ );
92
+
93
+ await expect.element(getByText('Hey, World')).toBeInTheDocument();
94
+
95
+ override.set('Hi');
96
+
97
+ await expect.element(getByText('Hi, World')).toBeInTheDocument();
98
+ });
99
+ });
@@ -0,0 +1,49 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { render } from 'vitest-browser-react';
3
+ import { state, reactive, subscription } from 'signalium';
4
+ import { setupReact } from '../index.js';
5
+ import React from 'react';
6
+ import { sleep } from '../../__tests__/utils/async.js';
7
+
8
+ setupReact();
9
+
10
+ describe('React > subscriptions', () => {
11
+ test('subscriptions can be set by values accessed outside of normal run loop ', async () => {
12
+ const value = state('Hello');
13
+
14
+ const derived = reactive(() => {
15
+ return subscription<string>(({ set }) => {
16
+ const run = async () => {
17
+ await sleep(100);
18
+
19
+ try {
20
+ return `${value.get()}, World`;
21
+ } catch (e) {
22
+ console.error(e);
23
+ return 'Error';
24
+ }
25
+ };
26
+
27
+ set(run());
28
+ });
29
+ });
30
+
31
+ function GrandChild({ text }: { text: string }): React.ReactNode {
32
+ return <span>{text}</span>;
33
+ }
34
+
35
+ function Child({ asyncValue }: { asyncValue: { isPending: boolean; value: string | undefined } }): React.ReactNode {
36
+ return <div>{asyncValue.isPending ? 'Loading...' : <GrandChild text={asyncValue.value!} />}</div>;
37
+ }
38
+
39
+ function Parent(): React.ReactNode {
40
+ const d = derived();
41
+ return <Child asyncValue={d} />;
42
+ }
43
+
44
+ const { getByText } = render(<Parent />);
45
+
46
+ await expect.element(getByText('Loading...')).toBeInTheDocument();
47
+ await expect.element(getByText('Hello, World')).toBeInTheDocument();
48
+ });
49
+ });
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+
3
+ export interface RenderCounter<Props extends Record<string, unknown>> {
4
+ (props: Props): React.ReactNode;
5
+ testId: number;
6
+ renderCount: number;
7
+ }
8
+
9
+ let CURRENT_ID = 0;
10
+
11
+ export type ComponentType<P = any> = (props: P) => React.ReactNode;
12
+ export type HOC<InProps = any, OutProps = InProps> = (Component: ComponentType<InProps>) => ComponentType<OutProps>;
13
+
14
+ /**
15
+ * The wrapper passed to createRenderCounter is a HOC that will wrap the component
16
+ * with additional functionality. The reason we don't pass a component directly is
17
+ * because introducing additional components would mess with the real render counts.
18
+ */
19
+ const EmptyWrapper: HOC = Component => props => Component(props);
20
+
21
+ export function createRenderCounter<Props extends Record<string, unknown>>(
22
+ Component: (props: Props) => React.ReactNode,
23
+ wrapper: HOC<Props> = EmptyWrapper,
24
+ ): RenderCounter<Props> {
25
+ const id = CURRENT_ID++;
26
+
27
+ const RenderCounterComponent = wrapper((props: Props) => {
28
+ RenderCounterComponent.renderCount++;
29
+
30
+ // Call the component manually so it's not a separate React component
31
+ const children = Component(props);
32
+
33
+ return <div data-testid={id}>{children}</div>;
34
+ }) as RenderCounter<Props>;
35
+
36
+ RenderCounterComponent.testId = id;
37
+ RenderCounterComponent.renderCount = 0;
38
+
39
+ return RenderCounterComponent;
40
+ }
@@ -1,5 +1,5 @@
1
1
  import { createContext, useContext } from 'react';
2
- import { SignalScope } from '../hooks.js';
2
+ import { SignalScope } from '../internals/contexts.js';
3
3
 
4
4
  export const ScopeContext = createContext<SignalScope | undefined>(undefined);
5
5
 
@@ -1,18 +1,26 @@
1
1
  import { useContext } from 'react';
2
- import { SignalScope, SignalStoreMap } from '../hooks.js';
3
2
  import { ScopeContext } from './context.js';
3
+ import { ContextImpl, ContextPair, SignalScope } from '../internals/contexts.js';
4
4
 
5
- export function ContextProvider({
5
+ export function ContextProvider<C extends unknown[]>({
6
6
  children,
7
7
  contexts,
8
8
  inherit = true,
9
+ root = false,
9
10
  }: {
10
11
  children: React.ReactNode;
11
- contexts: SignalStoreMap;
12
+ contexts: [...ContextPair<C>];
12
13
  inherit?: boolean;
14
+ root?: boolean;
13
15
  }) {
16
+ // if (root) {
17
+ // useEffect(() => )
18
+
19
+ // return <ScopeContext.Provider value={scope}>{children}</ScopeContext.Provider>;
20
+ // }
21
+
14
22
  const parentScope = useContext(ScopeContext);
15
- const scope = new SignalScope(contexts, inherit ? parentScope : undefined);
23
+ const scope = new SignalScope(contexts as [ContextImpl<unknown>, unknown][], inherit ? parentScope : undefined);
16
24
 
17
25
  return <ScopeContext.Provider value={scope}>{children}</ScopeContext.Provider>;
18
26
  }
@@ -1,10 +1,11 @@
1
1
  import { setConfig } from '../config.js';
2
2
  import { useScope } from './context.js';
3
- import { useSignalValue } from './signal-value.js';
3
+ import { useDerivedSignal, useStateSignal } from './signal-value.js';
4
4
 
5
5
  export function setupReact() {
6
6
  setConfig({
7
- useSignalValue,
7
+ useDerivedSignal,
8
+ useStateSignal,
8
9
  getFrameworkScope: useScope,
9
10
  });
10
11
  }
@@ -1,7 +1,10 @@
1
1
  /* eslint-disable react-hooks/rules-of-hooks */
2
- import React, { useCallback, useContext, useRef, useState, useSyncExternalStore } from 'react';
3
- import { ScopeContext } from './context.js';
4
- import { watcher } from '../hooks.js';
2
+ import React, { useCallback, useSyncExternalStore } from 'react';
3
+ import type { DerivedSignal } from '../internals/derived.js';
4
+ import type { StateSignal } from '../internals/state.js';
5
+ import type { ReactiveValue } from '../types.js';
6
+ import { isReactivePromise } from '../internals/utils/type-utils.js';
7
+ import { isReactiveSubscription } from '../internals/async.js';
5
8
 
6
9
  // This is a private React internal that we need to access to check if we are rendering.
7
10
  // There is no other consistent way to check if we are rendering in both development
@@ -9,85 +12,62 @@ import { watcher } from '../hooks.js';
9
12
  // should be checked on every major React version upgrade.
10
13
  const REACT_INTERNALS =
11
14
  (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ||
12
- (React as any).__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
15
+ (React as any).__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ||
16
+ (React as any).__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
13
17
 
14
18
  const ReactCurrentDispatcher = REACT_INTERNALS.ReactCurrentDispatcher || REACT_INTERNALS;
19
+ const ReactCurrentOwner = REACT_INTERNALS.ReactCurrentOwner || REACT_INTERNALS;
15
20
 
16
21
  const getReactCurrentDispatcher = () => {
17
- return ReactCurrentDispatcher?.current || ReactCurrentDispatcher?.A || null;
22
+ return ReactCurrentDispatcher.current || REACT_INTERNALS.H;
23
+ };
24
+
25
+ const getReactCurrentOwner = () => {
26
+ return ReactCurrentOwner.current || REACT_INTERNALS.A;
18
27
  };
19
28
 
20
29
  function isRendering() {
21
- return getReactCurrentDispatcher() !== null;
30
+ return !!getReactCurrentDispatcher() && !!getReactCurrentOwner();
22
31
  }
23
32
 
24
- export function useSignalValue<T>(key: string, fn: () => T): T {
33
+ export function useStateSignal<T>(signal: StateSignal<T>): T {
25
34
  if (!isRendering()) {
26
- return fn();
35
+ return signal.peek();
27
36
  }
28
37
 
29
- const [, setVersion] = useState(0);
30
- const scope = useContext(ScopeContext);
31
- const ref = useRef<{
32
- value: T | undefined;
33
- sub: (() => () => void) | undefined;
34
- unsub: (() => void) | undefined;
35
- key: string | undefined;
36
- }>({
37
- value: undefined,
38
- sub: undefined,
39
- unsub: undefined,
40
- key: undefined,
41
- });
42
-
43
- const currentKey = ref.current.key;
44
-
45
- if (key !== currentKey) {
46
- ref.current.unsub?.();
47
-
48
- const w = watcher(fn, { scope });
49
-
50
- let initialized = false;
51
-
52
- ref.current.sub = () => {
53
- if (ref.current.unsub) {
54
- return ref.current.unsub;
55
- }
56
-
57
- const unsub = w.addListener(
58
- value => {
59
- ref.current.value = value;
60
-
61
- // Trigger an update to the component
62
- if (initialized) {
63
- setVersion(v => v + 1);
64
- }
65
-
66
- initialized = true;
67
- },
68
- {
69
- immediate: true,
70
- },
71
- );
72
-
73
- ref.current.unsub = () => {
74
- ref.current.unsub = undefined;
75
- unsub();
76
- };
77
-
78
- return ref.current.unsub!;
79
- };
80
-
81
- ref.current.sub!();
38
+ return useSyncExternalStore(
39
+ useCallback(onStoreChange => signal.addListener(onStoreChange), [signal]),
40
+ () => signal.peek(),
41
+ () => signal.peek(),
42
+ );
43
+ }
82
44
 
83
- ref.current.key = key;
45
+ export function useDerivedSignal<T>(signal: DerivedSignal<T, unknown[]>): ReactiveValue<T> {
46
+ if (!isRendering()) {
47
+ return signal.get();
84
48
  }
85
49
 
86
- useSyncExternalStore(
87
- ref.current.sub!,
88
- () => ref.current.value!,
89
- () => ref.current.value!,
50
+ const value = useSyncExternalStore(
51
+ signal.addListenerLazy(),
52
+ () => signal.get(),
53
+ () => signal.get(),
90
54
  );
91
55
 
92
- return ref.current.value!;
56
+ // Reactive promises can update their value independently of the signal, since
57
+ // we reuse the same promise object for each result. We need to entangle the
58
+ // version of the promise here so that we can trigger a re-render when the
59
+ // promise value updates.
60
+ //
61
+ // If hooks could be called in dynamic order this would not be necessary, we
62
+ // could entangle the promise when it is used. But, because that is not the
63
+ // case, we need to eagerly entangle.
64
+ if (typeof value === 'object' && value !== null && isReactivePromise(value)) {
65
+ if (isReactiveSubscription(value)) {
66
+ useDerivedSignal(value['_signal'] as DerivedSignal<any, unknown[]>);
67
+ }
68
+
69
+ useStateSignal(value['_version']);
70
+ }
71
+
72
+ return value as ReactiveValue<T>;
93
73
  }