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,556 @@
1
+ import {
2
+ BaseReactivePromise,
3
+ ReactiveSubscription,
4
+ ReactiveTask,
5
+ ReactivePromise as IReactivePromise,
6
+ SignalEquals,
7
+ SignalOptionsWithInit,
8
+ SignalSubscribe,
9
+ SignalSubscription,
10
+ } from '../types.js';
11
+ import { createDerivedSignal, DerivedSignal, SignalState } from './derived.js';
12
+ import { CURRENT_CONSUMER, generatorResultToPromise, getSignal } from './get.js';
13
+ import { dirtySignal, dirtySignalConsumers } from './dirty.js';
14
+ import { scheduleAsyncPull, schedulePull, setResolved } from './scheduling.js';
15
+ import { createEdge, Edge, EdgeType, findAndRemoveDirty, PromiseEdge } from './edge.js';
16
+ import { getCurrentScope, ROOT_SCOPE, SignalScope, withScope } from './contexts.js';
17
+ import { createStateSignal } from './state.js';
18
+ import { useStateSignal } from '../config.js';
19
+ import { isGeneratorResult, isPromise } from './utils/type-utils.js';
20
+ import { equalsFrom } from './utils/equals.js';
21
+
22
+ const enum AsyncFlags {
23
+ // ======= Notifiers ========
24
+
25
+ Pending = 1,
26
+ Rejected = 1 << 1,
27
+ Resolved = 1 << 2,
28
+ Ready = 1 << 3,
29
+
30
+ Value = 1 << 4,
31
+ Error = 1 << 5,
32
+
33
+ // ======= Properties ========
34
+
35
+ isRunnable = 1 << 6,
36
+ isSubscription = 1 << 7,
37
+
38
+ // ======= Helpers ========
39
+
40
+ Settled = Resolved | Rejected,
41
+ }
42
+
43
+ interface PendingResolve<T> {
44
+ ref: WeakRef<DerivedSignal<unknown, unknown[]>> | undefined;
45
+ edge: PromiseEdge | undefined;
46
+ resolve: ((value: T) => void) | undefined | null;
47
+ reject: ((error: unknown) => void) | undefined | null;
48
+ }
49
+
50
+ type TaskFn<T, Args extends unknown[]> = (...args: Args) => Promise<T>;
51
+
52
+ export class ReactivePromise<T, Args extends unknown[] = unknown[]> implements BaseReactivePromise<T> {
53
+ private _value: T | undefined = undefined;
54
+
55
+ private _error: unknown | undefined = undefined;
56
+ private _flags = 0;
57
+
58
+ private _signal: DerivedSignal<any, any> | TaskFn<T, Args> | undefined = undefined;
59
+ private _equals!: SignalEquals<T>;
60
+ private _promise: Promise<T> | undefined;
61
+
62
+ private _pending: PendingResolve<T>[] = [];
63
+
64
+ private _stateSubs = new Map<WeakRef<DerivedSignal<unknown, unknown[]>>, number>();
65
+ _awaitSubs = new Map<WeakRef<DerivedSignal<unknown, unknown[]>>, PromiseEdge>();
66
+
67
+ // Version is not really needed in a pure signal world, but when integrating
68
+ // with non-signal code, it's sometimes needed to entangle changes to the promise.
69
+ // For example, in React we need to entangle each promise immediately after it
70
+ // was used because we can't dynamically call hooks.
71
+ private _version = createStateSignal(0);
72
+
73
+ static createPromise<T>(promise: Promise<T>, signal?: DerivedSignal<T, unknown[]>, initValue?: T | undefined) {
74
+ const p = new ReactivePromise();
75
+
76
+ p._signal = signal;
77
+ p._equals = signal?.equals ?? ((a, b) => a === b);
78
+
79
+ p._initFlags(AsyncFlags.Pending, initValue);
80
+
81
+ if (promise) {
82
+ p._setPromise(promise);
83
+ }
84
+
85
+ return p;
86
+ }
87
+
88
+ static createSubscription<T>(
89
+ subscribe: SignalSubscribe<T>,
90
+ scope: SignalScope,
91
+ opts?: Partial<SignalOptionsWithInit<T, unknown[]>>,
92
+ ) {
93
+ const p = new ReactivePromise<T>();
94
+ const initValue = opts?.initValue;
95
+
96
+ let active = false;
97
+ let currentSub: SignalSubscription | (() => void) | undefined | void;
98
+
99
+ const unsubscribe = () => {
100
+ if (typeof currentSub === 'function') {
101
+ currentSub();
102
+ } else if (currentSub !== undefined) {
103
+ currentSub.unsubscribe?.();
104
+ }
105
+
106
+ const signal = p._signal as DerivedSignal<any, any>;
107
+
108
+ // Reset the signal state, preparing it for next activation
109
+ signal.subs = new Map();
110
+ signal._state = SignalState.Dirty;
111
+ signal.watchCount = 0;
112
+ active = false;
113
+ currentSub = undefined;
114
+ };
115
+
116
+ const state = {
117
+ get: () => p._value as T,
118
+
119
+ set: (value: T | Promise<T>) => {
120
+ if (value !== null && typeof value === 'object' && isPromise(value)) {
121
+ p._setPromise(value);
122
+ } else {
123
+ p._setValue(value as T);
124
+ }
125
+ },
126
+
127
+ setError: (error: unknown) => {
128
+ p._setError(error);
129
+ },
130
+ };
131
+
132
+ p._signal = createDerivedSignal(
133
+ () => {
134
+ if (active === false) {
135
+ currentSub = subscribe(state);
136
+ active = true;
137
+ } else if (typeof currentSub === 'function' || currentSub === undefined) {
138
+ currentSub?.();
139
+ currentSub = subscribe(state);
140
+ } else {
141
+ currentSub.update?.();
142
+ }
143
+
144
+ return unsubscribe;
145
+ },
146
+ [],
147
+ undefined,
148
+ scope,
149
+ opts as Omit<SignalOptionsWithInit<T, unknown[]>, 'equals' | 'initValue' | 'paramKey'>,
150
+ true,
151
+ );
152
+
153
+ p._equals = equalsFrom(opts?.equals);
154
+ p._initFlags(AsyncFlags.isSubscription | AsyncFlags.Pending, initValue as T);
155
+
156
+ return p;
157
+ }
158
+
159
+ static createTask<T, Args extends unknown[]>(
160
+ task: (...args: Args) => Promise<T>,
161
+ scope: SignalScope,
162
+ opts?: Partial<SignalOptionsWithInit<T, Args>>,
163
+ ): ReactiveTask<T, Args> {
164
+ const p = new ReactivePromise<T, Args>();
165
+ const initValue = opts?.initValue;
166
+
167
+ p._signal = (...args) => {
168
+ return withScope(scope, () => {
169
+ const result = task(...args);
170
+
171
+ return typeof result === 'object' && result !== null && isGeneratorResult(result)
172
+ ? generatorResultToPromise(result, undefined)
173
+ : result;
174
+ });
175
+ };
176
+
177
+ p._equals = equalsFrom(opts?.equals);
178
+ p._initFlags(AsyncFlags.isRunnable, initValue as T);
179
+
180
+ return p as ReactiveTask<T, Args>;
181
+ }
182
+
183
+ private _initFlags(baseFlags: number, initValue?: T) {
184
+ let flags = baseFlags;
185
+
186
+ if (initValue !== undefined) {
187
+ flags |= AsyncFlags.Ready;
188
+ }
189
+
190
+ this._flags = flags;
191
+ this._value = initValue as T;
192
+ }
193
+
194
+ private _consumeFlags(flags: number) {
195
+ if (CURRENT_CONSUMER === undefined) return;
196
+
197
+ if ((this._flags & AsyncFlags.isSubscription) !== 0) {
198
+ this._connect();
199
+ }
200
+
201
+ const ref = CURRENT_CONSUMER.ref;
202
+
203
+ const subs = this._stateSubs;
204
+
205
+ const subbedFlags = subs.get(ref) ?? 0;
206
+ subs.set(ref, subbedFlags | flags);
207
+ }
208
+
209
+ private _connect() {
210
+ const signal = this._signal as DerivedSignal<any, any>;
211
+
212
+ if (CURRENT_CONSUMER?.watchCount === 0) {
213
+ const { ref, computedCount, deps } = CURRENT_CONSUMER!;
214
+ const prevEdge = deps.get(signal);
215
+
216
+ if (prevEdge?.consumedAt !== computedCount) {
217
+ const newEdge = createEdge(prevEdge, EdgeType.Signal, signal, signal.updatedCount, computedCount);
218
+
219
+ signal.subs.set(ref, newEdge);
220
+ deps.set(signal, newEdge);
221
+ }
222
+ } else {
223
+ getSignal(signal);
224
+ }
225
+ }
226
+
227
+ private _setFlags(setTrue: number, setFalse = 0, notify = 0) {
228
+ const prevFlags = this._flags;
229
+
230
+ const nextFlags = (prevFlags & ~setFalse) | setTrue;
231
+ const allChanged = (prevFlags ^ nextFlags) | notify;
232
+
233
+ this._flags = nextFlags;
234
+
235
+ if (allChanged === 0) {
236
+ return;
237
+ }
238
+
239
+ const subs = this._stateSubs;
240
+
241
+ for (const [signalRef, subbedFlags] of subs) {
242
+ if ((subbedFlags & allChanged) !== 0) {
243
+ const signal = signalRef.deref();
244
+
245
+ if (signal) {
246
+ dirtySignal(signal);
247
+ }
248
+
249
+ subs.delete(signalRef);
250
+ }
251
+ }
252
+
253
+ this._version.update(v => v + 1);
254
+ }
255
+
256
+ _setPending() {
257
+ this._setFlags(AsyncFlags.Pending);
258
+ }
259
+
260
+ async _setPromise(promise: Promise<T>) {
261
+ // Store the current promise so we can check if it's the same promise in the
262
+ // then handlers. If it's not the same promise, it means that the promise has
263
+ // been recomputed and replaced, so we should not update state.
264
+ this._promise = promise;
265
+
266
+ const flags = this._flags;
267
+ let awaitSubs = this._awaitSubs;
268
+
269
+ // If we were not already pending, we need to propagate the dirty state to any
270
+ // consumers that were added since the promise was resolved last.
271
+ if ((flags & AsyncFlags.Pending) === 0) {
272
+ this._setPending();
273
+ dirtySignalConsumers(awaitSubs);
274
+ this._awaitSubs = awaitSubs = new Map();
275
+ }
276
+
277
+ try {
278
+ const nextValue = await promise;
279
+
280
+ if (promise !== this._promise) {
281
+ return;
282
+ }
283
+
284
+ this._setValue(nextValue, awaitSubs);
285
+ } catch (nextError) {
286
+ if (promise !== this._promise) {
287
+ return;
288
+ }
289
+
290
+ this._setError(nextError, awaitSubs);
291
+ }
292
+ }
293
+
294
+ private _setValue(nextValue: T, awaitSubs = this._awaitSubs) {
295
+ let flags = this._flags;
296
+ let value = this._value;
297
+
298
+ let notifyFlags = 0;
299
+
300
+ if ((flags & AsyncFlags.Ready) === 0 || this._equals(value!, nextValue) === false) {
301
+ this._value = value = nextValue;
302
+ notifyFlags = AsyncFlags.Value;
303
+ }
304
+
305
+ if (flags & AsyncFlags.Rejected) {
306
+ notifyFlags = AsyncFlags.Error;
307
+ this._error = undefined;
308
+ }
309
+
310
+ this._scheduleSubs(awaitSubs, notifyFlags !== 0);
311
+
312
+ this._setFlags(AsyncFlags.Resolved | AsyncFlags.Ready, AsyncFlags.Pending | AsyncFlags.Rejected, notifyFlags);
313
+
314
+ for (const { ref, edge, resolve } of this._pending) {
315
+ resolve?.(value!);
316
+
317
+ if (ref !== undefined) {
318
+ awaitSubs.set(ref, edge!);
319
+ }
320
+ }
321
+
322
+ this._pending = [];
323
+ }
324
+
325
+ private _setError(nextError: unknown, awaitSubs = this._awaitSubs) {
326
+ let error = this._error;
327
+
328
+ let notifyFlags = 0;
329
+
330
+ if (error !== nextError) {
331
+ this._error = error = nextError;
332
+ notifyFlags = AsyncFlags.Error;
333
+ }
334
+
335
+ this._scheduleSubs(awaitSubs, notifyFlags !== 0);
336
+
337
+ this._setFlags(AsyncFlags.Rejected, AsyncFlags.Pending | AsyncFlags.Resolved, notifyFlags);
338
+
339
+ for (const { ref, edge, reject } of this._pending) {
340
+ reject?.(error);
341
+
342
+ if (ref !== undefined) {
343
+ awaitSubs.set(ref, edge!);
344
+ }
345
+ }
346
+
347
+ this._pending = [];
348
+ }
349
+
350
+ private _scheduleSubs(awaitSubs: Map<WeakRef<DerivedSignal<unknown, unknown[]>>, PromiseEdge>, dirty: boolean) {
351
+ // Await subscribers that have been added since the promise was set are specifically
352
+ // subscribers that were previously notified and MaybeDirty, were removed from the
353
+ // signal, and then were checked (e.g. checkSignal was called on them) and they
354
+ // halted and added themselves back as dependencies.
355
+ //
356
+ // If the value actually changed, then these consumers are Dirty and will notify and
357
+ // schedule themselves the standard way here. If the value did not change, then the
358
+ // consumers are not notified and end up back in the same state as before the promise
359
+ // was set (because nothing changed), and instead they will be scheduled to continue
360
+ // the computation from where they left off.
361
+ const newState = dirty ? SignalState.Dirty : SignalState.MaybeDirty;
362
+
363
+ for (const ref of awaitSubs.keys()) {
364
+ const signal = ref.deref();
365
+
366
+ if (signal === undefined) {
367
+ continue;
368
+ }
369
+
370
+ signal._state = newState;
371
+
372
+ scheduleAsyncPull(signal);
373
+ }
374
+ }
375
+
376
+ get value() {
377
+ this._consumeFlags(AsyncFlags.Value);
378
+
379
+ return this._value;
380
+ }
381
+
382
+ get error() {
383
+ this._consumeFlags(AsyncFlags.Error);
384
+
385
+ return this._error;
386
+ }
387
+
388
+ get isPending() {
389
+ this._consumeFlags(AsyncFlags.Pending);
390
+
391
+ return (this._flags & AsyncFlags.Pending) !== 0;
392
+ }
393
+
394
+ get isRejected() {
395
+ this._consumeFlags(AsyncFlags.Rejected);
396
+
397
+ return (this._flags & AsyncFlags.Rejected) !== 0;
398
+ }
399
+
400
+ get isResolved() {
401
+ this._consumeFlags(AsyncFlags.Resolved);
402
+
403
+ return (this._flags & AsyncFlags.Resolved) !== 0;
404
+ }
405
+
406
+ get isReady() {
407
+ this._consumeFlags(AsyncFlags.Ready);
408
+
409
+ return (this._flags & AsyncFlags.Ready) !== 0;
410
+ }
411
+
412
+ get isSettled() {
413
+ this._consumeFlags(AsyncFlags.Settled);
414
+
415
+ return (this._flags & AsyncFlags.Settled) !== 0;
416
+ }
417
+
418
+ // Aliases for backwards compatibility (TODO: Figure out how to do this better)
419
+ get data() {
420
+ return this.value;
421
+ }
422
+
423
+ get isFetching() {
424
+ return this.isPending;
425
+ }
426
+
427
+ get isSuccess() {
428
+ return this.isResolved;
429
+ }
430
+
431
+ get isError() {
432
+ return this.isRejected;
433
+ }
434
+
435
+ get isFetched() {
436
+ return this.isSettled;
437
+ }
438
+
439
+ run(...args: Args) {
440
+ if ((this._flags & AsyncFlags.isRunnable) === 0) {
441
+ throw new Error('ReactivePromise is not runnable, make sure you used `task` to create this promise');
442
+ }
443
+
444
+ const signal = this._signal as TaskFn<T, Args>;
445
+
446
+ this._setPromise(signal(...args));
447
+
448
+ return this;
449
+ }
450
+
451
+ rerun() {
452
+ const { _signal: signal } = this;
453
+
454
+ if (!signal) {
455
+ throw new Error('ReactivePromise is not associated with a signal');
456
+ }
457
+
458
+ dirtySignal(signal as DerivedSignal<any, any>);
459
+ }
460
+
461
+ then<TResult1 = T, TResult2 = never>(
462
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
463
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
464
+ ): Promise<TResult1 | TResult2> {
465
+ const flags = this._flags;
466
+
467
+ // Create a new Promise that will be returned
468
+ return new Promise<TResult1 | TResult2>((resolve, reject) => {
469
+ let ref, edge;
470
+
471
+ if (CURRENT_CONSUMER !== undefined) {
472
+ if ((flags & AsyncFlags.isSubscription) !== 0) {
473
+ this._connect();
474
+ }
475
+
476
+ ref = CURRENT_CONSUMER.ref;
477
+
478
+ const prevEdge =
479
+ this._awaitSubs.get(ref!) ?? findAndRemoveDirty(CURRENT_CONSUMER, this as ReactivePromise<any>);
480
+
481
+ edge = createEdge(prevEdge, EdgeType.Promise, this as ReactivePromise<any>, -1, CURRENT_CONSUMER.computedCount);
482
+ }
483
+ // Create wrapper functions that will call the original callbacks and then resolve/reject the new Promise
484
+ const wrappedFulfilled = onfulfilled
485
+ ? (value: T) => {
486
+ try {
487
+ const result = onfulfilled(value);
488
+ resolve(result);
489
+ } catch (error) {
490
+ reject(error);
491
+ }
492
+ }
493
+ : (resolve as unknown as (value: T) => void);
494
+
495
+ const wrappedRejected = onrejected
496
+ ? (reason: unknown) => {
497
+ try {
498
+ const result = onrejected(reason);
499
+ resolve(result);
500
+ } catch (error) {
501
+ reject(error);
502
+ }
503
+ }
504
+ : reject;
505
+
506
+ if (flags & AsyncFlags.Pending) {
507
+ this._pending.push({ ref, edge, resolve: wrappedFulfilled, reject: wrappedRejected });
508
+ } else {
509
+ if (flags & AsyncFlags.Resolved) {
510
+ wrappedFulfilled(this._value!);
511
+ } else if (flags & AsyncFlags.Rejected) {
512
+ wrappedRejected(this._error);
513
+ }
514
+
515
+ if (ref) {
516
+ this._awaitSubs.set(ref, edge!);
517
+ }
518
+ }
519
+ });
520
+ }
521
+
522
+ catch<TResult = never>(
523
+ onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null,
524
+ ): Promise<T | TResult> {
525
+ return this.then(null, onrejected);
526
+ }
527
+
528
+ finally(onfinally?: (() => void) | null): Promise<T> {
529
+ return this.then(
530
+ value => {
531
+ onfinally?.();
532
+ return value;
533
+ },
534
+ reason => {
535
+ onfinally?.();
536
+ throw reason;
537
+ },
538
+ );
539
+ }
540
+
541
+ get [Symbol.toStringTag](): string {
542
+ return 'ReactivePromise';
543
+ }
544
+ }
545
+
546
+ export function isReactivePromise(obj: unknown): obj is IReactivePromise<unknown> {
547
+ return obj instanceof ReactivePromise && (obj['_flags'] & (AsyncFlags.isSubscription & AsyncFlags.isRunnable)) === 0;
548
+ }
549
+
550
+ export function isReactiveTask(obj: unknown): obj is ReactiveTask<unknown, unknown[]> {
551
+ return obj instanceof ReactivePromise && (obj['_flags'] & AsyncFlags.isRunnable) !== 0;
552
+ }
553
+
554
+ export function isReactiveSubscription<T, Args extends unknown[]>(obj: unknown): obj is ReactiveSubscription<T> {
555
+ return obj instanceof ReactivePromise && (obj['_flags'] & AsyncFlags.isSubscription) !== 0;
556
+ }
@@ -0,0 +1,41 @@
1
+ import { DerivedSignal, isSubscription } from './derived.js';
2
+ import { checkSignal } from './get.js';
3
+
4
+ export function watchSignal(signal: DerivedSignal<any, any>): void {
5
+ const { watchCount } = signal;
6
+ const newWatchCount = watchCount + 1;
7
+
8
+ signal.watchCount = newWatchCount;
9
+
10
+ // If > 0, already watching, return
11
+ if (watchCount > 0) return;
12
+
13
+ for (const dep of signal.deps.keys()) {
14
+ watchSignal(dep);
15
+ }
16
+
17
+ if (isSubscription(signal)) {
18
+ // Bootstrap the subscription
19
+ checkSignal(signal);
20
+ }
21
+ }
22
+
23
+ export function unwatchSignal(signal: DerivedSignal<any, any>, count = 1) {
24
+ const { watchCount } = signal;
25
+ const newWatchCount = Math.max(watchCount - count, 0);
26
+
27
+ signal.watchCount = newWatchCount;
28
+
29
+ if (newWatchCount > 0) {
30
+ return;
31
+ }
32
+
33
+ for (const dep of signal.deps.keys()) {
34
+ unwatchSignal(dep);
35
+ }
36
+
37
+ if (isSubscription(signal)) {
38
+ // teardown the subscription
39
+ signal.value?.();
40
+ }
41
+ }
@@ -0,0 +1,13 @@
1
+ import { DerivedSignal } from './derived.js';
2
+
3
+ export let CURRENT_CONSUMER: DerivedSignal<any, any> | undefined;
4
+
5
+ export let IS_WATCHING = false;
6
+
7
+ export const setIsWatching = (isWatching: boolean) => {
8
+ IS_WATCHING = isWatching;
9
+ };
10
+
11
+ export const setCurrentConsumer = (consumer: DerivedSignal<any, any> | undefined) => {
12
+ CURRENT_CONSUMER = consumer;
13
+ };