signalium 0.3.8 → 1.0.0

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 +15 -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 +50 -0
  22. package/dist/cjs/internals/async.d.ts.map +1 -0
  23. package/dist/cjs/internals/async.js +390 -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 +50 -0
  121. package/dist/esm/internals/async.d.ts.map +1 -0
  122. package/dist/esm/internals/async.js +383 -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 +556 -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,298 @@
1
+ import { scheduleListeners, scheduleTracer, scheduleUnwatch, setResolved } from './scheduling.js';
2
+ import { SignalType, TRACER as TRACER, TracerEventType } from '../trace.js';
3
+ import { DerivedSignal, SignalFlags, SignalState } from './derived.js';
4
+ import { createEdge, Edge, EdgeType } from './edge.js';
5
+ import { watchSignal } from './connect.js';
6
+ import { ReactivePromise } from './async.js';
7
+ import { ReactiveValue } from '../types.js';
8
+ import { isGeneratorResult, isPromise, isReactivePromise } from './utils/type-utils.js';
9
+
10
+ export let CURRENT_CONSUMER: DerivedSignal<any, any> | undefined;
11
+
12
+ export function getSignal<T, Args extends unknown[]>(signal: DerivedSignal<T, Args>): ReactiveValue<T> {
13
+ if (CURRENT_CONSUMER !== undefined) {
14
+ const { ref, computedCount, deps } = CURRENT_CONSUMER;
15
+ const prevEdge = deps.get(signal);
16
+
17
+ const prevConsumedAt = prevEdge?.consumedAt;
18
+
19
+ if (prevConsumedAt !== computedCount) {
20
+ if (prevEdge === undefined) {
21
+ TRACER?.emit({
22
+ type: TracerEventType.Connected,
23
+ id: CURRENT_CONSUMER.tracerMeta!.id,
24
+ childId: signal.tracerMeta!.id,
25
+ name: signal.tracerMeta!.desc,
26
+ params: signal.tracerMeta!.params,
27
+ nodeType: SignalType.Reactive,
28
+ });
29
+
30
+ if (CURRENT_CONSUMER.watchCount > 0) {
31
+ watchSignal(signal);
32
+ }
33
+ }
34
+
35
+ const updatedAt = checkSignal(signal);
36
+ const newEdge = createEdge(prevEdge, EdgeType.Signal, signal, updatedAt, computedCount);
37
+
38
+ signal.subs.set(ref, newEdge);
39
+ deps.set(signal, newEdge);
40
+ }
41
+ } else {
42
+ checkSignal(signal);
43
+ }
44
+
45
+ return signal.value as ReactiveValue<T>;
46
+ }
47
+
48
+ export function checkSignal(signal: DerivedSignal<any, any>): number {
49
+ let { ref, _state: state } = signal;
50
+
51
+ if (state < SignalState.Dirty) {
52
+ return signal.updatedCount;
53
+ }
54
+
55
+ if (state === SignalState.MaybeDirty) {
56
+ let edge: Edge | undefined = signal.dirtyHead;
57
+
58
+ while (edge !== undefined) {
59
+ if (edge.type === EdgeType.Promise) {
60
+ const dep = edge.dep;
61
+
62
+ // If the dependency is pending, then we need to propagate the pending state to the
63
+ // parent signal, and we halt the computation here.
64
+ if (dep.isPending) {
65
+ const value = signal.value;
66
+
67
+ if (value instanceof ReactivePromise) {
68
+ // Propagate the pending state to the parent signal
69
+ value._setPending();
70
+ }
71
+
72
+ // Add the signal to the awaitSubs map to be notified when the promise is resolved
73
+ dep._awaitSubs.set(ref, edge);
74
+
75
+ state = SignalState.Pending;
76
+ signal.dirtyHead = edge;
77
+
78
+ // Early return to prevent the signal from being computed and to preserve the dirty state
79
+ return signal.updatedCount;
80
+ }
81
+
82
+ edge = edge.nextDirty;
83
+ continue;
84
+ }
85
+
86
+ const dep = edge.dep;
87
+ const updatedAt = checkSignal(dep);
88
+
89
+ dep.subs.set(ref, edge);
90
+
91
+ if (edge.updatedAt !== updatedAt) {
92
+ signal.dirtyHead = edge.nextDirty;
93
+ state = SignalState.Dirty;
94
+ break;
95
+ }
96
+
97
+ edge = edge.nextDirty;
98
+ }
99
+ }
100
+
101
+ if (state === SignalState.Dirty) {
102
+ runSignal(signal);
103
+ }
104
+
105
+ signal._state = SignalState.Clean;
106
+ signal.dirtyHead = undefined;
107
+
108
+ if (TRACER !== undefined && signal.tracerMeta?.tracer) {
109
+ scheduleTracer(signal.tracerMeta.tracer);
110
+ }
111
+
112
+ return signal.updatedCount;
113
+ }
114
+
115
+ export function runSignal(signal: DerivedSignal<any, any[]>) {
116
+ TRACER?.emit({
117
+ type: TracerEventType.StartUpdate,
118
+ id: signal.tracerMeta!.id,
119
+ });
120
+
121
+ const prevConsumer = CURRENT_CONSUMER;
122
+
123
+ const updatedCount = signal.updatedCount;
124
+ const computedCount = ++signal.computedCount;
125
+
126
+ try {
127
+ CURRENT_CONSUMER = signal;
128
+
129
+ const initialized = updatedCount !== 0;
130
+ const prevValue = signal.value;
131
+ let nextValue = signal.compute(...signal.args);
132
+ let valueIsPromise = false;
133
+
134
+ if (nextValue !== null && typeof nextValue === 'object') {
135
+ if (isGeneratorResult(nextValue)) {
136
+ nextValue = generatorResultToPromise(nextValue, signal);
137
+ valueIsPromise = true;
138
+ } else if (isPromise(nextValue)) {
139
+ valueIsPromise = true;
140
+ }
141
+ }
142
+
143
+ if (valueIsPromise) {
144
+ if (TRACER !== undefined) {
145
+ TRACER.emit({
146
+ type: TracerEventType.StartLoading,
147
+ id: signal.tracerMeta!.id,
148
+ });
149
+
150
+ nextValue.finally(() => {
151
+ TRACER!.emit({
152
+ type: TracerEventType.EndLoading,
153
+ id: signal.tracerMeta!.id,
154
+ value: signal.value,
155
+ });
156
+ });
157
+ }
158
+
159
+ TRACER?.emit({
160
+ type: TracerEventType.StartLoading,
161
+ id: signal.tracerMeta!.id,
162
+ });
163
+
164
+ if (prevValue !== null && typeof prevValue === 'object' && isReactivePromise(prevValue)) {
165
+ // Update the ReactivePromise with the new promise. Since the value
166
+ // returned from the function is the same ReactivePromise instance,
167
+ // we don't need to increment the updatedCount, because the returned
168
+ // value is the same. _setPromise will update the nested values on the
169
+ // ReactivePromise instance, and consumers of those values will be notified
170
+ // of the change through that.
171
+ prevValue._setPromise(nextValue);
172
+ } else {
173
+ // If the signal has not been computed yet, we then the initValue was assigned
174
+ // in the constructor. Otherwise, we don't know what the initial value was, so
175
+ // we don't pass it to the ReactivePromise constructor.
176
+ const initValue = !initialized ? prevValue : undefined;
177
+ signal.value = ReactivePromise.createPromise(nextValue, signal, initValue);
178
+ signal.updatedCount = updatedCount + 1;
179
+ }
180
+ } else if (!initialized || !signal.equals(prevValue!, nextValue)) {
181
+ signal.value = nextValue;
182
+ signal.updatedCount = updatedCount + 1;
183
+ }
184
+ } finally {
185
+ CURRENT_CONSUMER = prevConsumer;
186
+
187
+ TRACER?.emit({
188
+ type: TracerEventType.EndUpdate,
189
+ id: signal.tracerMeta!.id,
190
+ value: signal.value,
191
+ });
192
+
193
+ const { ref, deps } = signal;
194
+
195
+ for (const [dep, edge] of deps) {
196
+ if (edge.consumedAt !== computedCount) {
197
+ scheduleUnwatch(dep);
198
+ dep.subs.delete(ref);
199
+ deps.delete(dep);
200
+
201
+ TRACER?.emit({
202
+ type: TracerEventType.Disconnected,
203
+ id: signal.tracerMeta!.id,
204
+ childId: dep.tracerMeta!.id,
205
+ });
206
+ }
207
+ }
208
+ }
209
+ }
210
+
211
+ export function checkAndRunListeners(signal: DerivedSignal<any, any>, willWatch = false) {
212
+ const listeners = signal.listeners;
213
+
214
+ if (willWatch && (listeners === null || listeners.current.size === 0)) {
215
+ signal.watchCount++;
216
+ signal['flags'] |= SignalFlags.isListener;
217
+ }
218
+
219
+ let updatedCount = checkSignal(signal);
220
+
221
+ if (listeners !== null && listeners.updatedAt !== updatedCount) {
222
+ listeners.updatedAt = updatedCount;
223
+
224
+ scheduleListeners(signal);
225
+ }
226
+
227
+ return updatedCount;
228
+ }
229
+
230
+ export function callback<T, Args extends unknown[]>(fn: (...args: Args) => T): (...args: Args) => T {
231
+ const savedConsumer = CURRENT_CONSUMER;
232
+
233
+ return (...args) => {
234
+ const prevConsumer = CURRENT_CONSUMER;
235
+ CURRENT_CONSUMER = savedConsumer;
236
+
237
+ try {
238
+ const result = fn(...args);
239
+
240
+ if (result !== null && typeof result === 'object' && isGeneratorResult(result)) {
241
+ return generatorResultToPromise(result, savedConsumer) as T;
242
+ }
243
+
244
+ return result;
245
+ } finally {
246
+ CURRENT_CONSUMER = prevConsumer;
247
+ }
248
+ };
249
+ }
250
+
251
+ export function generatorResultToPromise<T, Args extends unknown[]>(
252
+ generator: Generator<any, T>,
253
+ savedConsumer: DerivedSignal<any, any> | undefined,
254
+ ): Promise<T> {
255
+ function adopt(value: any) {
256
+ return typeof value === 'object' && value !== null && (isPromise(value) || isReactivePromise(value))
257
+ ? value
258
+ : Promise.resolve(value);
259
+ }
260
+
261
+ return new Promise((resolve, reject) => {
262
+ function step(result: any) {
263
+ if (result.done) {
264
+ resolve(result.value);
265
+ } else {
266
+ adopt(result.value).then(fulfilled, rejected);
267
+ }
268
+ }
269
+
270
+ function fulfilled(value: any) {
271
+ const prevConsumer = CURRENT_CONSUMER;
272
+
273
+ try {
274
+ CURRENT_CONSUMER = savedConsumer;
275
+ step(generator.next(value));
276
+ } catch (e) {
277
+ reject(e);
278
+ } finally {
279
+ CURRENT_CONSUMER = prevConsumer;
280
+ }
281
+ }
282
+
283
+ function rejected(value: any) {
284
+ const prevConsumer = CURRENT_CONSUMER;
285
+
286
+ try {
287
+ CURRENT_CONSUMER = savedConsumer;
288
+ step(generator['throw'](value));
289
+ } catch (e) {
290
+ reject(e);
291
+ } finally {
292
+ CURRENT_CONSUMER = prevConsumer;
293
+ }
294
+ }
295
+
296
+ step(generator.next());
297
+ });
298
+ }
@@ -0,0 +1,140 @@
1
+ import { scheduleFlush as _scheduleFlush, runBatch } from '../config.js';
2
+ import { DerivedSignal } from './derived.js';
3
+ import { checkAndRunListeners, checkSignal } from './get.js';
4
+ import { runListeners as runDerivedListeners } from './derived.js';
5
+ import { runListeners as runStateListeners } from './state.js';
6
+ import { Tracer } from '../trace.js';
7
+ import { unwatchSignal } from './connect.js';
8
+ import { StateSignal } from './state.js';
9
+
10
+ let PROMISE_WAS_RESOLVED = false;
11
+
12
+ let PENDING_PULLS: DerivedSignal<any, any>[] = [];
13
+ let PENDING_ASYNC_PULLS: DerivedSignal<any, any>[] = [];
14
+ let PENDING_UNWATCH = new Map<DerivedSignal<any, any>, number>();
15
+ let PENDING_LISTENERS: (DerivedSignal<any, any> | StateSignal<any>)[] = [];
16
+ let PENDING_TRACERS: Tracer[] = [];
17
+
18
+ const microtask = () => Promise.resolve();
19
+
20
+ let currentFlush: { promise: Promise<void>; resolve: () => void } | null = null;
21
+
22
+ const scheduleFlush = (fn: () => void) => {
23
+ if (currentFlush) return;
24
+
25
+ let resolve: () => void;
26
+ const promise = new Promise<void>(r => (resolve = r));
27
+
28
+ currentFlush = { promise, resolve: resolve! };
29
+
30
+ _scheduleFlush(flushWatchers);
31
+ };
32
+
33
+ export const setResolved = () => {
34
+ PROMISE_WAS_RESOLVED = true;
35
+ };
36
+
37
+ export const schedulePull = (signal: DerivedSignal<any, any>) => {
38
+ PENDING_PULLS.push(signal);
39
+ scheduleFlush(flushWatchers);
40
+ };
41
+
42
+ export const scheduleAsyncPull = (signal: DerivedSignal<any, any>) => {
43
+ PENDING_ASYNC_PULLS.push(signal);
44
+ scheduleFlush(flushWatchers);
45
+ };
46
+
47
+ export const scheduleUnwatch = (unwatch: DerivedSignal<any, any>) => {
48
+ const current = PENDING_UNWATCH.get(unwatch) ?? 0;
49
+
50
+ PENDING_UNWATCH.set(unwatch, current + 1);
51
+
52
+ scheduleFlush(flushWatchers);
53
+ };
54
+
55
+ export const scheduleListeners = (signal: DerivedSignal<any, any> | StateSignal<any>) => {
56
+ PENDING_LISTENERS.push(signal);
57
+ scheduleFlush(flushWatchers);
58
+ };
59
+
60
+ export const scheduleTracer = (tracer: Tracer) => {
61
+ PENDING_TRACERS.push(tracer);
62
+ scheduleFlush(flushWatchers);
63
+ };
64
+
65
+ const flushWatchers = async () => {
66
+ const flush = currentFlush!;
67
+
68
+ // Flush all auto-pulled signals recursively, clearing
69
+ // the microtask queue until they are all settled
70
+ while (PROMISE_WAS_RESOLVED || PENDING_ASYNC_PULLS.length > 0 || PENDING_PULLS.length > 0) {
71
+ PROMISE_WAS_RESOLVED = false;
72
+ const asyncPulls = PENDING_ASYNC_PULLS;
73
+
74
+ PENDING_ASYNC_PULLS = [];
75
+
76
+ for (const pull of asyncPulls) {
77
+ checkSignal(pull);
78
+ }
79
+
80
+ const pulls = PENDING_PULLS;
81
+
82
+ PENDING_PULLS = [];
83
+
84
+ for (const pull of pulls) {
85
+ checkAndRunListeners(pull);
86
+ }
87
+
88
+ // This is used to tell the scheduler to wait if any async values have been resolved
89
+ // since the last tick. If they have, we wait an extra microtask to ensure that the
90
+ // async values have recursivey flushed before moving on to pulling watchers.
91
+
92
+ await microtask();
93
+ }
94
+
95
+ // Clear the flush so that if any more watchers are scheduled,
96
+ // they will be flushed in the next tick
97
+ currentFlush = null;
98
+
99
+ runBatch(() => {
100
+ for (const [signal, count] of PENDING_UNWATCH) {
101
+ unwatchSignal(signal, count);
102
+ }
103
+
104
+ for (const signal of PENDING_LISTENERS) {
105
+ if (signal instanceof DerivedSignal) {
106
+ runDerivedListeners(signal as any);
107
+ } else {
108
+ runStateListeners(signal as any);
109
+ }
110
+ }
111
+
112
+ for (const tracer of PENDING_TRACERS) {
113
+ tracer.flush();
114
+ }
115
+
116
+ PENDING_UNWATCH.clear();
117
+ PENDING_LISTENERS = [];
118
+ PENDING_TRACERS = [];
119
+ });
120
+
121
+ // resolve the flush promise
122
+ flush.resolve();
123
+ };
124
+
125
+ export const settled = async () => {
126
+ while (currentFlush) {
127
+ await currentFlush.promise;
128
+ }
129
+ };
130
+
131
+ export const batch = (fn: () => void) => {
132
+ let resolve: () => void;
133
+ const promise = new Promise<void>(r => (resolve = r));
134
+
135
+ currentFlush = { promise, resolve: resolve! };
136
+
137
+ fn();
138
+ flushWatchers();
139
+ // flushDisconnects();
140
+ };
@@ -0,0 +1,111 @@
1
+ import { TRACER as TRACER, TracerEventType } from '../trace.js';
2
+ import { ReactiveValue, SignalEquals, SignalListener, SignalOptions, WriteableSignal } from '../types.js';
3
+ import { DerivedSignal, SignalState } from './derived.js';
4
+ import { dirtySignal } from './dirty.js';
5
+ import { CURRENT_CONSUMER } from './get.js';
6
+ import { useStateSignal } from '../config.js';
7
+ import { scheduleListeners } from './scheduling.js';
8
+
9
+ let STATE_ID = 0;
10
+
11
+ export class StateSignal<T> implements WriteableSignal<T> {
12
+ private _value: T;
13
+ private _equals: SignalEquals<T>;
14
+ private _subs = new Map<WeakRef<DerivedSignal<unknown, unknown[]>>, number>();
15
+ _desc: string;
16
+ _id: number;
17
+
18
+ private _listeners: Set<SignalListener> | null = null;
19
+
20
+ constructor(value: T, equals: SignalEquals<T> = (a, b) => a === b, desc: string = 'state') {
21
+ this._value = value;
22
+ this._equals = equals;
23
+ this._id = STATE_ID++;
24
+ this._desc = `${desc}${this._id}`;
25
+ }
26
+
27
+ get(): T {
28
+ if (CURRENT_CONSUMER !== undefined) {
29
+ TRACER?.emit({
30
+ type: TracerEventType.ConsumeState,
31
+ id: CURRENT_CONSUMER.tracerMeta!.id,
32
+ childId: this._id,
33
+ value: this._value,
34
+ setValue: (value: unknown) => {
35
+ this.set(value as T);
36
+ },
37
+ });
38
+ this._subs.set(CURRENT_CONSUMER.ref, CURRENT_CONSUMER.computedCount);
39
+ return this._value!;
40
+ }
41
+
42
+ // eslint-disable-next-line react-hooks/rules-of-hooks
43
+ return useStateSignal(this);
44
+ }
45
+
46
+ update(fn: (value: T) => T) {
47
+ this.set(fn(this._value));
48
+ }
49
+
50
+ peek(): T {
51
+ return this._value;
52
+ }
53
+
54
+ set(value: T) {
55
+ if (this._equals(value, this._value)) {
56
+ return;
57
+ }
58
+
59
+ this._value = value;
60
+ const { _subs: subs, _listeners: listeners } = this;
61
+
62
+ for (const [subRef, consumedAt] of subs.entries()) {
63
+ const sub = subRef.deref();
64
+
65
+ if (sub === undefined || consumedAt !== sub.computedCount) {
66
+ continue;
67
+ }
68
+
69
+ dirtySignal(sub);
70
+ }
71
+
72
+ this._subs = new Map();
73
+
74
+ scheduleListeners(this);
75
+ }
76
+
77
+ addListener(listener: SignalListener): () => void {
78
+ let listeners = this._listeners;
79
+
80
+ if (listeners === null) {
81
+ this._listeners = listeners = new Set();
82
+ }
83
+
84
+ listeners.add(listener);
85
+
86
+ return () => listeners.delete(listener);
87
+ }
88
+ }
89
+
90
+ export function runListeners(signal: StateSignal<any>) {
91
+ const listeners = signal['_listeners'];
92
+
93
+ if (listeners === null) {
94
+ return;
95
+ }
96
+
97
+ for (const listener of listeners) {
98
+ listener();
99
+ }
100
+ }
101
+
102
+ const FALSE_EQUALS: SignalEquals<unknown> = () => false;
103
+
104
+ export function createStateSignal<T>(
105
+ initialValue: T,
106
+ opts?: Omit<SignalOptions<T, unknown[]>, 'paramKey'>,
107
+ ): StateSignal<T> {
108
+ const equals = opts?.equals === false ? FALSE_EQUALS : (opts?.equals ?? ((a, b) => a === b));
109
+
110
+ return new StateSignal(initialValue, equals, opts?.desc);
111
+ }
@@ -0,0 +1,14 @@
1
+ let UNKNOWN_SIGNAL_ID = 0;
2
+ const UNKNOWN_SIGNAL_NAMES = new Map<object, string>();
3
+
4
+ export function getUnknownSignalFnName(fn: object) {
5
+ let name = UNKNOWN_SIGNAL_NAMES.get(fn);
6
+
7
+ if (name === undefined) {
8
+ name = `unknownSignal${UNKNOWN_SIGNAL_ID++}`;
9
+
10
+ UNKNOWN_SIGNAL_NAMES.set(fn, name);
11
+ }
12
+
13
+ return name;
14
+ }
@@ -0,0 +1,12 @@
1
+ import { SignalEquals } from '../../types.js';
2
+
3
+ const DEFAULT_EQUALS: SignalEquals<unknown> = (a, b) => a === b;
4
+ const FALSE_EQUALS: SignalEquals<unknown> = () => false;
5
+
6
+ export const equalsFrom = <T>(equals: SignalEquals<T> | false | undefined): SignalEquals<T> => {
7
+ if (equals === false) {
8
+ return FALSE_EQUALS;
9
+ }
10
+
11
+ return equals ?? DEFAULT_EQUALS;
12
+ };