signalium 0.3.7 → 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 +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 +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
@@ -1,14 +1,17 @@
1
- import { afterEach, Assertion, beforeEach, expect } from 'vitest';
1
+ import { afterEach, Assertion, expect } from 'vitest';
2
2
  import {
3
- asyncComputed as _asyncComputed,
4
- asyncTask as _asyncTask,
5
- computed as _computed,
3
+ reactive as _reactive,
6
4
  subscription as _subscription,
7
- watcher,
8
5
  SignalSubscribe,
9
- withContext,
6
+ withContexts,
7
+ task as _task,
8
+ watcher,
10
9
  } from '../../index.js';
11
- import { SignalOptionsWithInit, SignalSubscription, Watcher } from '../../types.js';
10
+ import { ReactiveTask, ReactiveValue, SignalOptionsWithInit, SignalSubscription } from '../../types.js';
11
+ import { Context, ContextImpl, getCurrentScope, ROOT_SCOPE, SignalScope } from '../../internals/contexts.js';
12
+ import { createDerivedSignal, DerivedSignal } from '../../internals/derived.js';
13
+ import { ReactivePromise } from '../../internals/async.js';
14
+ import { hashValue } from '../../internals/utils/hash.js';
12
15
 
13
16
  class SignalHookCounts {
14
17
  name: string;
@@ -24,6 +27,7 @@ class SignalHookCounts {
24
27
  unsubscribe = 0;
25
28
  internalGet = 0;
26
29
  internalSet = 0;
30
+ error = 0;
27
31
 
28
32
  effect = 0;
29
33
 
@@ -35,15 +39,16 @@ class SignalHookCounts {
35
39
  const countsKeys = Object.keys(new SignalHookCounts('')).filter(k => k !== 'name') as (keyof SignalHookCounts)[];
36
40
 
37
41
  let currentOrder: string[] | undefined = [];
38
- const COUNTS = new WeakMap<object, SignalHookCounts>();
42
+
43
+ type ContextPair<T extends unknown[]> = {
44
+ [K in keyof T]: [Context<T[K]>, NoInfer<T[K]>];
45
+ };
39
46
 
40
47
  interface CustomMatchers<R = unknown> {
41
- toHaveHookValue: (v: any) => Assertion<R>;
48
+ toHaveSignalValue: (v: any) => Assertion<R>;
42
49
  toHaveCounts: (counts: Partial<SignalHookCounts>) => Assertion<R>;
43
50
  toHaveValueAndCounts: (v: any, counts: Partial<SignalHookCounts>) => Assertion<R>;
44
51
  toHaveComputedOrder: (order: string[]) => Assertion<R>;
45
- withParams: R extends (...args: infer P) => any ? (...args: P) => Assertion<R> : (...args: any[]) => Assertion<R>;
46
- withContexts: (contexts: Record<symbol, any>) => Assertion<R>;
47
52
  }
48
53
 
49
54
  declare module 'vitest' {
@@ -53,66 +58,70 @@ declare module 'vitest' {
53
58
  interface AsymmetricMatchersContaining extends CustomMatchers {}
54
59
  }
55
60
 
56
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
57
- const NEXT_ARGS = new WeakMap<Function, any[]>();
58
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
59
- const NEXT_CONTEXTS = new WeakMap<Function, Record<symbol, any>>();
60
-
61
- let w: Watcher<unknown> | undefined;
62
-
63
61
  let unsubs: (() => void)[] = [];
64
62
 
65
63
  afterEach(() => {
66
64
  unsubs.forEach(unsub => unsub());
67
65
  });
68
66
 
69
- function toHaveHookValue(
70
- this: { equals(a: unknown, b: unknown): boolean },
71
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
72
- hook: Function,
73
- value: any,
74
- ) {
75
- const args = NEXT_ARGS.get(hook) ?? [];
76
- const contexts = NEXT_CONTEXTS.get(hook);
67
+ let TEST_ID = 0;
77
68
 
78
- NEXT_ARGS.delete(hook);
79
- NEXT_CONTEXTS.delete(hook);
69
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
70
+ const WATCHERS = new WeakMap<Function, DerivedSignal<unknown, unknown[]>>();
80
71
 
81
- let w = watcher(() => {
82
- if (contexts) {
83
- return withContext(contexts, () => {
84
- return hook(...args);
85
- });
86
- } else {
87
- return hook(...args);
88
- }
89
- });
72
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
73
+ function getWatcherForHook(hook: Function) {
74
+ let watcher = WATCHERS.get(hook);
90
75
 
91
- let signalValue: any;
92
- unsubs.push(
93
- w.addListener(
94
- (v: any) => {
95
- signalValue = v;
96
- },
97
- {
98
- immediate: true,
76
+ if (!watcher) {
77
+ watcher = createDerivedSignal(
78
+ () => {
79
+ let result = hook();
80
+
81
+ if (result instanceof ReactivePromise) {
82
+ result = result.value;
83
+ }
84
+
85
+ return result;
99
86
  },
100
- ),
101
- );
87
+ undefined,
88
+ undefined,
89
+ undefined,
90
+ { desc: 'test' + TEST_ID++ },
91
+ );
102
92
 
103
- if (signalValue && typeof signalValue === 'object' && 'result' in signalValue) {
104
- signalValue = signalValue.result;
93
+ unsubs.push(watcher.addListener(() => {}));
94
+
95
+ WATCHERS.set(hook, watcher);
105
96
  }
106
97
 
98
+ return watcher;
99
+ }
100
+
101
+ function toHaveSignalValue(
102
+ this: { equals(a: unknown, b: unknown): boolean },
103
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
104
+ hook: Function,
105
+ value: any,
106
+ ) {
107
+ if (hook instanceof ReactivePromise) {
108
+ return {
109
+ pass: this.equals(hook.value, value),
110
+ message: () =>
111
+ `Expected subscription value to be ${JSON.stringify(value)}, but got ${JSON.stringify(hook.value)}`,
112
+ };
113
+ }
114
+
115
+ const signalValue = getWatcherForHook(hook).get();
116
+
107
117
  return {
108
118
  pass: this.equals(signalValue, value),
109
- message: () => `Expected signal value to be ${JSON.stringify(value)}, but got ${JSON.stringify(signalValue)}`,
119
+ message: () => `Expected signal value to be ${value}, but got ${signalValue}`,
110
120
  };
111
121
  }
112
122
 
113
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
114
- function toHaveCounts(hook: Function, counts: SignalHookCounts) {
115
- const signalCounts = COUNTS.get(hook);
123
+ function toHaveCounts(hook: { [COUNTS]: SignalHookCounts }, counts: SignalHookCounts) {
124
+ const signalCounts = hook[COUNTS];
116
125
 
117
126
  if (!signalCounts) {
118
127
  return {
@@ -138,32 +147,24 @@ function toHaveCounts(hook: Function, counts: SignalHookCounts) {
138
147
  };
139
148
  }
140
149
 
141
- expect.extend({
142
- toHaveHookValue,
143
- toHaveCounts,
144
-
145
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
146
- withParams(fn: Function, ...args: any[]) {
147
- NEXT_ARGS.set(fn, args);
150
+ expect.addSnapshotSerializer({
151
+ serialize(val) {
152
+ const counts = val[COUNTS];
153
+ const value = getWatcherForHook(val).get();
148
154
 
149
- return {
150
- pass: true,
151
- message: () => 'Params match',
152
- };
155
+ return JSON.stringify([value, counts], null, 2);
153
156
  },
154
-
155
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
156
- withContexts(fn: Function, contexts: Record<symbol, any>) {
157
- NEXT_CONTEXTS.set(fn, contexts);
158
-
159
- return {
160
- pass: true,
161
- message: () => 'Contexts match',
162
- };
157
+ test(val) {
158
+ return val[COUNTS] !== undefined;
163
159
  },
160
+ });
161
+
162
+ expect.extend({
163
+ toHaveSignalValue,
164
+ toHaveCounts,
164
165
 
165
166
  toHaveValueAndCounts(signal, value, counts) {
166
- const valueResult = toHaveHookValue.call(this, signal, value);
167
+ const valueResult = toHaveSignalValue.call(this, signal, value);
167
168
  const countsResult = toHaveCounts.call(this, signal, counts);
168
169
 
169
170
  return {
@@ -193,83 +194,187 @@ expect.extend({
193
194
  },
194
195
  });
195
196
 
196
- export const wrapHook = <T, Args extends unknown[]>(original: object, fn: (...args: Args) => T) => {
197
- const counts = COUNTS.get(original);
197
+ function getContextKeys(scope: SignalScope): symbol[] {
198
+ const contexts = scope['contexts'];
198
199
 
199
- if (!counts) {
200
- throw new Error('Signal not found in counts map');
200
+ const keys = Object.getOwnPropertySymbols(contexts);
201
+
202
+ if (scope['parentScope']) {
203
+ return [...getContextKeys(scope['parentScope']), ...keys];
201
204
  }
202
205
 
203
- COUNTS.set(fn, counts);
206
+ return keys;
207
+ }
204
208
 
205
- return fn;
206
- };
209
+ function getSortedContexts(scope: SignalScope) {
210
+ const keys = getContextKeys(scope).sort((a, b) => a.toString().localeCompare(b.toString()));
207
211
 
208
- export const computed: typeof _computed = (fn, opts) => {
209
- const counts = new SignalHookCounts(opts?.desc ?? 'unknown');
212
+ return keys.map(key => {
213
+ const context = scope['contexts'][key];
210
214
 
211
- const wrapper = _computed((...args) => {
212
- counts.compute++;
215
+ return [key, context];
216
+ });
217
+ }
213
218
 
214
- return fn(...(args as any));
215
- }, opts);
219
+ function getCountsFor(name: string, map: Map<number, SignalHookCounts>, scope: SignalScope, args: unknown[] = []) {
220
+ const key = hashValue([args, getSortedContexts(scope)]);
221
+ let countsForArgs = map.get(key);
216
222
 
217
- COUNTS.set(wrapper, counts);
223
+ if (!countsForArgs) {
224
+ countsForArgs = new SignalHookCounts(name);
225
+ map.set(key, countsForArgs);
226
+ }
218
227
 
219
- return wrapper;
220
- };
228
+ return countsForArgs;
229
+ }
221
230
 
222
- export const asyncComputed: typeof _asyncComputed = (fn, opts) => {
223
- const counts = new SignalHookCounts(opts?.desc ?? 'unknown');
231
+ const COUNTS = Symbol('counts');
224
232
 
225
- const wrapper = _asyncComputed((...args) => {
226
- counts.compute++;
233
+ export type SubscriptionWithCounts<T> = ReactivePromise<T> & {
234
+ [COUNTS]: SignalHookCounts;
235
+ };
227
236
 
228
- return fn(...(args as any));
229
- }, opts) as any;
237
+ export type ReactiveTaskWithCounts<T, Args extends unknown[]> = ReactiveTask<T, Args> & {
238
+ [COUNTS]: SignalHookCounts;
239
+ };
230
240
 
231
- COUNTS.set(wrapper, counts);
241
+ export type ReactiveFunctionWithCounts<T, Args extends unknown[]> = ((...args: Args) => ReactiveValue<T>) & {
242
+ [COUNTS]: SignalHookCounts;
243
+ };
232
244
 
233
- return wrapper;
245
+ export type ReactiveBuilderFunction<T, Args extends unknown[]> = ((...args: Args) => ReactiveValue<T>) & {
246
+ [COUNTS]: SignalHookCounts;
247
+ watch: () => () => void;
248
+ withParams: (...args: Args) => ReactiveBuilderFunction<T, []>;
249
+ withContexts: (...contexts: [Context<unknown>, unknown][]) => ReactiveBuilderFunction<T, Args>;
234
250
  };
235
251
 
236
- export const asyncTask: typeof _asyncTask = (fn, opts) => {
237
- const counts = new SignalHookCounts(opts?.desc ?? 'unknown');
252
+ // Create a function-class hybrid for the builder pattern
253
+ function createBuilderFunction<T, Args extends unknown[]>(
254
+ originalFn: (...args: Args) => ReactiveValue<T>,
255
+ countsMap: Map<number, SignalHookCounts>,
256
+ args: Args,
257
+ contexts?: [Context<unknown>, unknown][],
258
+ ): ReactiveBuilderFunction<T, []>;
259
+ function createBuilderFunction<T, Args extends unknown[]>(
260
+ originalFn: (...args: Args) => ReactiveValue<T>,
261
+ countsMap: Map<number, SignalHookCounts>,
262
+ args?: undefined,
263
+ contexts?: [Context<unknown>, unknown][],
264
+ ): ReactiveBuilderFunction<T, Args>;
265
+ function createBuilderFunction<T, Args extends unknown[]>(
266
+ originalFn: (...args: Args) => ReactiveValue<T>,
267
+ countsMap: Map<number, SignalHookCounts>,
268
+ args?: Args,
269
+ contexts?: [Context<unknown>, unknown][],
270
+ ): ReactiveBuilderFunction<T, Args> {
271
+ // Cast the function to include our additional properties
272
+ const builderFn = ((...passedArgs: Args) => {
273
+ if (args && passedArgs.length > 0) {
274
+ throw new Error('reactive function already has parameters');
275
+ }
276
+
277
+ let usedArgs = args ?? passedArgs;
278
+
279
+ const scope = getCurrentScope();
280
+ const counts = getCountsFor(originalFn.name, countsMap, scope, usedArgs);
281
+
282
+ // increment the get count since each time this is called, we're getting the value
283
+ counts.get++;
284
+
285
+ if (contexts) {
286
+ return withContexts(contexts, () => originalFn(...usedArgs));
287
+ }
288
+
289
+ return originalFn(...usedArgs);
290
+ }) as ReactiveBuilderFunction<T, Args>;
291
+
292
+ // Add the builder methods
293
+ builderFn.watch = (...args: Args) => {
294
+ const unsub = watcher(() => builderFn(...args)).addListener(() => {});
295
+ unsubs.push(unsub);
296
+ return unsub;
297
+ };
298
+
299
+ builderFn.withParams = (...withArgs: Args) => {
300
+ if (args) {
301
+ throw new Error('reactive function already has parameters');
302
+ }
303
+
304
+ return createBuilderFunction(originalFn, countsMap, withArgs, contexts);
305
+ };
306
+
307
+ builderFn.withContexts = (...withContexts: [Context<unknown>, unknown][]) => {
308
+ if (contexts) {
309
+ throw new Error('reactive function already has contexts');
310
+ }
311
+
312
+ return createBuilderFunction(originalFn, countsMap, args as Args, withContexts) as ReactiveBuilderFunction<T, Args>;
313
+ };
314
+
315
+ const scope = contexts ? ROOT_SCOPE.getChild(contexts as [ContextImpl<unknown>, unknown][]) : ROOT_SCOPE;
316
+ builderFn[COUNTS] = getCountsFor(originalFn.name, countsMap, scope, args);
317
+
318
+ return builderFn;
319
+ }
238
320
 
239
- const wrapper = _asyncTask((...args) => {
321
+ export function reactive<T, Args extends unknown[]>(
322
+ fn: (...args: Args) => T,
323
+ opts?: Partial<SignalOptionsWithInit<T, Args>>,
324
+ ): ReactiveBuilderFunction<T, Args> {
325
+ const countsMap = new Map<number, SignalHookCounts>();
326
+
327
+ return createBuilderFunction(
328
+ _reactive((...args: any[]) => {
329
+ const scope = getCurrentScope();
330
+ const counts = getCountsFor(opts?.desc ?? 'unknownReactive', countsMap, scope, args);
331
+
332
+ counts.compute++;
333
+
334
+ return fn(...(args as any));
335
+ }, opts) as ReactiveFunctionWithCounts<T, Args>,
336
+ countsMap,
337
+ );
338
+ }
339
+
340
+ export const task: typeof _task = (fn, opts) => {
341
+ const counts = new SignalHookCounts(opts?.desc ?? 'unknownTask');
342
+
343
+ const wrapper = _task((...args: any[]) => {
240
344
  counts.compute++;
241
345
 
242
346
  return fn(...(args as any));
243
- }, opts) as any;
347
+ }, opts) as ReactiveTaskWithCounts<any, any>;
244
348
 
245
- COUNTS.set(wrapper, counts);
349
+ wrapper[COUNTS] = counts;
246
350
 
247
351
  return wrapper;
248
352
  };
249
353
 
250
- export const subscription = <T, Args extends unknown[]>(
251
- fn: SignalSubscribe<T, Args>,
252
- opts?: Partial<SignalOptionsWithInit<T, Args>>,
253
- ): ReturnType<typeof _subscription<T, Args>> => {
254
- const counts = new SignalHookCounts(opts?.desc ?? 'unknown');
354
+ export const subscription = <T>(
355
+ fn: SignalSubscribe<T>,
356
+ opts?: Partial<SignalOptionsWithInit<T, unknown[]>>,
357
+ ): ReturnType<typeof _subscription<T>> => {
358
+ const counts = new SignalHookCounts(opts?.desc ?? 'unknownSubscription');
255
359
 
256
- let wrapper = _subscription<T, Args>(({ get, set }, ...args) => {
360
+ let wrapper = _subscription<T>(({ get, set, setError }) => {
257
361
  counts.subscribe++;
258
362
  counts.compute++;
259
363
 
260
- const result = fn(
261
- {
262
- get: () => {
263
- counts.internalGet++;
264
- return get();
265
- },
266
- set: v => {
267
- counts.internalSet++;
268
- set(v);
269
- },
364
+ const result = fn({
365
+ get: () => {
366
+ counts.internalGet++;
367
+ return get();
270
368
  },
271
- ...args,
272
- );
369
+ set: v => {
370
+ counts.internalSet++;
371
+ set(v);
372
+ },
373
+ setError: (error: unknown) => {
374
+ counts.error++;
375
+ setError(error);
376
+ },
377
+ });
273
378
 
274
379
  let subscriptionWrapper: SignalSubscription | (() => unknown) | undefined;
275
380
 
@@ -300,9 +405,9 @@ export const subscription = <T, Args extends unknown[]>(
300
405
  }
301
406
 
302
407
  return subscriptionWrapper;
303
- }, opts);
408
+ }, opts) as SubscriptionWithCounts<T>;
304
409
 
305
- COUNTS.set(wrapper, counts);
410
+ wrapper[COUNTS] = counts;
306
411
 
307
- return wrapper;
412
+ return wrapper as ReturnType<typeof _subscription<T>>;
308
413
  };
@@ -1,22 +1,24 @@
1
1
  import { describe } from 'vitest';
2
- import { asyncComputed, computed, subscription, wrapHook } from './instrumented-hooks.js';
2
+ import { ReactiveBuilderFunction, reactive, subscription } from './instrumented-hooks.js';
3
3
  import { SignalOptionsWithInit } from '../../types.js';
4
4
 
5
5
  const createMethods = [
6
6
  {
7
7
  name: 'createComputed',
8
- create: computed,
8
+ create: reactive,
9
9
  },
10
10
  {
11
11
  name: 'createAsyncComputed',
12
12
  create: <T, Args extends unknown[]>(
13
13
  fn: (...args: Args) => T | Promise<T>,
14
- opts?: Partial<SignalOptionsWithInit<T, Args>>,
15
- ) => {
16
- const computed = asyncComputed(fn, opts);
14
+ opts?: Partial<SignalOptionsWithInit<Promise<T>, Args>>,
15
+ ): ReactiveBuilderFunction<T, Args> => {
16
+ const computed = reactive(async (...args: Args) => {
17
+ return fn(...args);
18
+ }, opts);
17
19
 
18
- return wrapHook(computed, (...args: Args) => {
19
- return computed(...args).result;
20
+ return reactive((...args: Args) => {
21
+ return computed(...args).value as T;
20
22
  });
21
23
  },
22
24
  },
@@ -25,15 +27,24 @@ const createMethods = [
25
27
  create: function _createSubscription<T, Args extends unknown[]>(
26
28
  fn: (...args: Args) => T,
27
29
  opts?: Partial<SignalOptionsWithInit<T, Args>>,
28
- ): (...args: Args) => T {
29
- return subscription((state, ...args) => {
30
- state.set(fn(...args));
31
-
32
- return {
33
- update: () => {
30
+ ): ReactiveBuilderFunction<T, Args> {
31
+ const computed = reactive((...args: Args) => {
32
+ return subscription(
33
+ state => {
34
34
  state.set(fn(...args));
35
+
36
+ return {
37
+ update: () => {
38
+ state.set(fn(...args));
39
+ },
40
+ };
35
41
  },
36
- };
42
+ opts as Partial<SignalOptionsWithInit<T, unknown[]>>,
43
+ );
44
+ });
45
+
46
+ return reactive((...args: Args) => {
47
+ return computed(...args).value as T;
37
48
  }, opts);
38
49
  },
39
50
  },
package/src/config.ts CHANGED
@@ -1,5 +1,8 @@
1
- import { SignalScope } from './hooks.js';
2
- import { getCurrentConsumer } from './signals.js';
1
+ import { DerivedSignal } from './internals/derived.js';
2
+ import { SignalScope } from './internals/contexts.js';
3
+ import { ReactiveValue } from './types.js';
4
+ import { StateSignal } from './internals/state.js';
5
+ import { CURRENT_CONSUMER } from './internals/get.js';
3
6
 
4
7
  export type FlushCallback = () => void;
5
8
 
@@ -10,7 +13,8 @@ interface SignalHooksConfig {
10
13
  scheduleFlush: FlushFn;
11
14
  runBatch: BatchFn;
12
15
  getFrameworkScope: () => SignalScope | undefined;
13
- useSignalValue: <T>(key: string, fn: () => T) => T;
16
+ useStateSignal: <T>(signal: StateSignal<T>) => T;
17
+ useDerivedSignal: <T>(signal: DerivedSignal<T, unknown[]>) => ReactiveValue<T>;
14
18
  }
15
19
 
16
20
  export let scheduleFlush: FlushFn = flushWatchers => {
@@ -23,20 +27,26 @@ export let runBatch: BatchFn = fn => fn();
23
27
 
24
28
  export let getFrameworkScope: () => SignalScope | undefined = () => undefined;
25
29
 
26
- let useFrameworkSignalValue: <T>(key: string, fn: () => T) => T = (key, fn) => fn();
30
+ let useFrameworkStateSignal: <T>(signal: StateSignal<T>) => T = signal => signal.get();
31
+ let useFrameworkDerivedSignal: <T>(signal: DerivedSignal<T, unknown[]>) => ReactiveValue<T> = signal => signal.get();
27
32
 
28
- export function useSignalValue<T>(key: string, fn: () => T): T {
29
- if (getCurrentConsumer()) {
30
- return fn();
33
+ export function useDerivedSignal<T>(signal: DerivedSignal<T, any[]>): ReactiveValue<T> {
34
+ if (CURRENT_CONSUMER !== undefined) {
35
+ return signal.get();
31
36
  } else {
32
37
  // eslint-disable-next-line react-hooks/rules-of-hooks
33
- return useFrameworkSignalValue(key, fn);
38
+ return useFrameworkDerivedSignal(signal);
34
39
  }
35
40
  }
36
41
 
42
+ export function useStateSignal<T>(signal: StateSignal<T>): T {
43
+ return useFrameworkStateSignal(signal);
44
+ }
45
+
37
46
  export function setConfig(cfg: Partial<SignalHooksConfig>) {
38
47
  scheduleFlush = cfg.scheduleFlush ?? scheduleFlush;
39
48
  runBatch = cfg.runBatch ?? runBatch;
40
49
  getFrameworkScope = cfg.getFrameworkScope ?? getFrameworkScope;
41
- useFrameworkSignalValue = cfg.useSignalValue ?? useFrameworkSignalValue;
50
+ useFrameworkStateSignal = cfg.useStateSignal ?? useFrameworkStateSignal;
51
+ useFrameworkDerivedSignal = cfg.useDerivedSignal ?? useFrameworkDerivedSignal;
42
52
  }
package/src/debug.ts CHANGED
@@ -5,10 +5,10 @@ export {
5
5
  removeTracer,
6
6
  VisualizerNode,
7
7
  type VisualizerLink,
8
- VisualizerNodeType,
9
8
  TracerEventType,
10
9
  Tracer,
11
10
  TRACER,
11
+ SignalType,
12
12
  } from './trace.js';
13
13
 
14
- export { scheduleTracer } from './scheduling.js';
14
+ export { scheduleTracer } from './internals/scheduling.js';