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,133 @@
1
+ import { getFrameworkScope } from '../config.js';
2
+ import { SignalOptionsWithInit } from '../types.js';
3
+ import { DerivedSignal, createDerivedSignal } from './derived.js';
4
+ import { CURRENT_CONSUMER } from './get.js';
5
+ import { hashReactiveFn, hashValue } from './utils/hash.js';
6
+
7
+ export const CONTEXT_KEY = Symbol('signalium:context');
8
+
9
+ export type Context<T> = {
10
+ defaultValue: T;
11
+ };
12
+
13
+ export type ContextPair<T extends unknown[]> = {
14
+ [K in keyof T]: [Context<T[K]>, NoInfer<T[K]>];
15
+ };
16
+
17
+ let CONTEXT_ID = 0;
18
+
19
+ export class ContextImpl<T> {
20
+ _key: symbol;
21
+ _description: string;
22
+
23
+ constructor(
24
+ public readonly defaultValue: T,
25
+ desc?: string,
26
+ ) {
27
+ this._description = desc ?? `context:${CONTEXT_ID++}`;
28
+ this._key = Symbol(this._description);
29
+ }
30
+ }
31
+
32
+ export const createContext = <T>(initialValue: T, description?: string): Context<T> => {
33
+ return new ContextImpl(initialValue, description);
34
+ };
35
+
36
+ export class SignalScope {
37
+ constructor(contexts: [ContextImpl<unknown>, unknown][], parent?: SignalScope) {
38
+ this.parentScope = parent;
39
+ this.contexts = Object.create(parent?.contexts || null);
40
+
41
+ for (const [context, value] of contexts) {
42
+ this.contexts[context._key] = value;
43
+ }
44
+ }
45
+
46
+ private parentScope?: SignalScope = undefined;
47
+ private contexts: Record<symbol, unknown>;
48
+ private children = new Map<number, SignalScope>();
49
+ private signals = new Map<number, DerivedSignal<any, any[]>>();
50
+
51
+ getChild(contexts: [ContextImpl<unknown>, unknown][]) {
52
+ const key = hashValue(contexts);
53
+
54
+ let child = this.children.get(key);
55
+
56
+ if (child === undefined) {
57
+ child = new SignalScope(contexts, this);
58
+ this.children.set(key, child);
59
+ }
60
+
61
+ return child;
62
+ }
63
+
64
+ getContext<T>(_context: Context<T>): T | undefined {
65
+ const context = _context as unknown as ContextImpl<T>;
66
+
67
+ return this.contexts[context._key] as T | undefined;
68
+ }
69
+
70
+ get<T, Args extends unknown[]>(
71
+ fn: (...args: Args) => T,
72
+ args: Args,
73
+ opts?: Partial<SignalOptionsWithInit<T, Args>>,
74
+ ): DerivedSignal<T, Args> {
75
+ const paramKey = opts?.paramKey?.(...args);
76
+ const key = hashReactiveFn(fn, paramKey ? [paramKey] : args);
77
+ let signal = this.signals.get(key) as DerivedSignal<T, Args> | undefined;
78
+
79
+ if (signal === undefined) {
80
+ signal = createDerivedSignal(fn, args, key, this, opts);
81
+ this.signals.set(key, signal);
82
+ }
83
+
84
+ return signal;
85
+ }
86
+ }
87
+
88
+ export let ROOT_SCOPE = new SignalScope([]);
89
+
90
+ export const clearRootScope = () => {
91
+ ROOT_SCOPE = new SignalScope([]);
92
+ };
93
+
94
+ let OVERRIDE_SCOPE: SignalScope | undefined;
95
+
96
+ export const getCurrentScope = (): SignalScope => {
97
+ return OVERRIDE_SCOPE ?? CURRENT_CONSUMER?.scope ?? getFrameworkScope() ?? ROOT_SCOPE;
98
+ };
99
+
100
+ export function withContexts<C extends unknown[], U>(contexts: [...ContextPair<C>], fn: () => U): U {
101
+ const prevScope = OVERRIDE_SCOPE;
102
+ const currentScope = getCurrentScope();
103
+
104
+ try {
105
+ OVERRIDE_SCOPE = currentScope.getChild(contexts as [ContextImpl<unknown>, unknown][]);
106
+ return fn();
107
+ } finally {
108
+ OVERRIDE_SCOPE = prevScope;
109
+ }
110
+ }
111
+
112
+ export const withScope = <T>(scope: SignalScope, fn: () => T) => {
113
+ const prevScope = OVERRIDE_SCOPE;
114
+
115
+ try {
116
+ OVERRIDE_SCOPE = scope;
117
+ return fn();
118
+ } finally {
119
+ OVERRIDE_SCOPE = prevScope;
120
+ }
121
+ };
122
+
123
+ export const useContext = <T>(context: Context<T>): T => {
124
+ const scope = OVERRIDE_SCOPE ?? CURRENT_CONSUMER?.scope ?? getFrameworkScope();
125
+
126
+ if (scope === undefined) {
127
+ throw new Error(
128
+ 'useContext must be used within a signal hook, a withContext, or within a framework-specific context provider.',
129
+ );
130
+ }
131
+
132
+ return scope.getContext(context) ?? (context as unknown as ContextImpl<T>).defaultValue;
133
+ };
@@ -0,0 +1,208 @@
1
+ import WeakRef from '../weakref.js';
2
+ import { Tracer, TRACER, TracerMeta } from '../trace.js';
3
+ import { ReactiveValue, Signal, SignalEquals, SignalListener, SignalOptionsWithInit } from '../types.js';
4
+ import { getUnknownSignalFnName } from './utils/debug-name.js';
5
+ import { SignalScope } from './contexts.js';
6
+ import { checkAndRunListeners, getSignal } from './get.js';
7
+ import { Edge, EdgeType, SignalEdge } from './edge.js';
8
+ import { schedulePull, scheduleUnwatch } from './scheduling.js';
9
+ import { hashValue } from './utils/hash.js';
10
+ import { stringifyValue } from './utils/stringify.js';
11
+ import { equalsFrom } from './utils/equals.js';
12
+
13
+ /**
14
+ * This file contains computed signal base types and struct definitions.
15
+ *
16
+ * Computed signals are monomorphic to make them more efficient, but this also
17
+ * means that multiple fields differ based on the type of the signal. Defining
18
+ * them using this pattern rather than a class allows us to switch on the `type`
19
+ * field to get strong typing in branches everywhere else.
20
+ *
21
+ * "Methods" for this struct are defined in other files for better organization.
22
+ */
23
+
24
+ export type SignalId = number;
25
+
26
+ export const enum SignalState {
27
+ Clean = 0,
28
+ Pending = 1,
29
+ Dirty = 2,
30
+ MaybeDirty = 3,
31
+ }
32
+
33
+ export const enum SignalFlags {
34
+ // State
35
+ State = 0b11,
36
+
37
+ // Properties
38
+ isSubscription = 0b100,
39
+ isListener = 0b1000,
40
+ }
41
+
42
+ let ID = 0;
43
+
44
+ interface ListenerMeta {
45
+ updatedAt: number;
46
+ current: Set<SignalListener>;
47
+
48
+ // Cached bound add method to avoid creating a new one on each call, this is
49
+ // specifically for React hooks where useSyncExternalStore will resubscribe each
50
+ // time if the method is not cached. This prevents us from having to add a
51
+ // useCallback for the listener.
52
+ cachedBoundAdd: (listener: SignalListener) => () => void;
53
+ }
54
+
55
+ export class DerivedSignal<T, Args extends unknown[]> implements Signal<ReactiveValue<T>> {
56
+ // Bitmask containing state in the first 2 bits and boolean properties in the remaining bits
57
+ private flags: number;
58
+ scope: SignalScope | undefined = undefined;
59
+
60
+ subs = new Map<WeakRef<DerivedSignal<any, any>>, Edge>();
61
+ deps = new Map<DerivedSignal<any, any>, Edge>();
62
+
63
+ ref: WeakRef<DerivedSignal<T, Args>> = new WeakRef(this);
64
+
65
+ equals: SignalEquals<any>;
66
+ dirtyHead: Edge | undefined = undefined;
67
+
68
+ updatedCount: number = 0;
69
+ computedCount: number = 0;
70
+
71
+ watchCount: number = 0;
72
+
73
+ _listeners: ListenerMeta | null = null;
74
+
75
+ compute: (...args: Args) => T;
76
+ args: Args;
77
+ value: ReactiveValue<T> | undefined;
78
+
79
+ tracerMeta?: TracerMeta;
80
+
81
+ constructor(
82
+ isSubscription: boolean,
83
+ compute: (...args: Args) => T,
84
+ args: Args,
85
+ key?: SignalId,
86
+ scope?: SignalScope,
87
+ opts?: Partial<SignalOptionsWithInit<T, Args>> & { tracer?: Tracer },
88
+ ) {
89
+ this.flags = (isSubscription ? SignalFlags.isSubscription : 0) | SignalState.Dirty;
90
+ this.scope = scope;
91
+ this.compute = compute;
92
+ this.args = args;
93
+
94
+ this.equals = equalsFrom(opts?.equals);
95
+ this.value = opts?.initValue as ReactiveValue<T>;
96
+
97
+ if (TRACER) {
98
+ this.tracerMeta = {
99
+ id: opts?.id ?? key ?? hashValue([compute, ID++]),
100
+ desc: opts?.desc ?? compute.name ?? getUnknownSignalFnName(compute),
101
+ params: args.map(arg => stringifyValue(arg)).join(', '),
102
+ tracer: opts?.tracer,
103
+ };
104
+ }
105
+ }
106
+
107
+ get _state() {
108
+ return this.flags & SignalFlags.State;
109
+ }
110
+
111
+ set _state(state: SignalState) {
112
+ this.flags = (this.flags & ~SignalFlags.State) | state;
113
+ }
114
+
115
+ get _isListener() {
116
+ return (this.flags & SignalFlags.isListener) !== 0;
117
+ }
118
+
119
+ set _isListener(isListener: boolean) {
120
+ if (isListener) {
121
+ this.flags |= SignalFlags.isListener;
122
+ } else {
123
+ this.flags &= ~SignalFlags.isListener;
124
+ }
125
+ }
126
+
127
+ get listeners() {
128
+ return (
129
+ this._listeners ??
130
+ (this._listeners = {
131
+ updatedAt: 0,
132
+ current: new Set(),
133
+ cachedBoundAdd: this.addListener.bind(this),
134
+ })
135
+ );
136
+ }
137
+
138
+ get(): ReactiveValue<T> {
139
+ return getSignal(this);
140
+ }
141
+
142
+ addListener(listener: SignalListener) {
143
+ const { current } = this.listeners;
144
+
145
+ if (!current.has(listener)) {
146
+ if (!this._isListener) {
147
+ this.watchCount++;
148
+ this.flags |= SignalFlags.isListener;
149
+ }
150
+
151
+ schedulePull(this);
152
+
153
+ current.add(listener);
154
+ }
155
+
156
+ return () => {
157
+ if (current.has(listener)) {
158
+ current.delete(listener);
159
+
160
+ if (current.size === 0) {
161
+ scheduleUnwatch(this);
162
+ this.flags &= ~SignalFlags.isListener;
163
+ }
164
+ }
165
+ };
166
+ }
167
+
168
+ // This method is used in React hooks specifically. It returns a bound add method
169
+ // that is cached to avoid creating a new one on each call, and it eagerly sets
170
+ // the listener as watched so that subscriptions that are accessed will be activated.
171
+ addListenerLazy() {
172
+ if (!this._isListener) {
173
+ this.watchCount++;
174
+ this.flags |= SignalFlags.isListener;
175
+ }
176
+
177
+ return this.listeners.cachedBoundAdd;
178
+ }
179
+ }
180
+
181
+ export const runListeners = (signal: DerivedSignal<any, any>) => {
182
+ const { listeners } = signal;
183
+
184
+ if (listeners === null) {
185
+ return;
186
+ }
187
+
188
+ const { current } = listeners;
189
+
190
+ for (const listener of current) {
191
+ listener();
192
+ }
193
+ };
194
+
195
+ export const isSubscription = (signal: unknown): boolean => {
196
+ return ((signal as any).flags & SignalFlags.isSubscription) !== 0;
197
+ };
198
+
199
+ export function createDerivedSignal<T, Args extends unknown[]>(
200
+ compute: (...args: Args) => T,
201
+ args: Args = [] as any,
202
+ key?: SignalId,
203
+ scope?: SignalScope,
204
+ opts?: Partial<SignalOptionsWithInit<T, Args>> & { tracer?: Tracer },
205
+ isSubscription: boolean = false,
206
+ ): DerivedSignal<T, Args> {
207
+ return new DerivedSignal(isSubscription, compute, args, key, scope, opts);
208
+ }
@@ -0,0 +1,91 @@
1
+ import { scheduleAsyncPull, schedulePull } from './scheduling.js';
2
+ import { DerivedSignal, isSubscription, SignalState } from './derived.js';
3
+ import { CURRENT_CONSUMER } from './get.js';
4
+ import { Edge } from './edge.js';
5
+
6
+ export function dirtySignal(signal: DerivedSignal<any, any>) {
7
+ const prevState = signal._state;
8
+
9
+ if (prevState === SignalState.Dirty) {
10
+ return;
11
+ }
12
+
13
+ signal._state = SignalState.Dirty;
14
+
15
+ if (prevState !== SignalState.MaybeDirty) {
16
+ propagateDirty(signal);
17
+ }
18
+ }
19
+
20
+ function propagateDirty(signal: DerivedSignal<any, any>) {
21
+ if (CURRENT_CONSUMER === signal) {
22
+ throw new Error(
23
+ 'A signal was dirtied after it was consumed by the current function. This can cause race conditions and infinite rerenders and is not allowed.',
24
+ );
25
+ }
26
+
27
+ if (isSubscription(signal)) {
28
+ if (signal.watchCount > 0) {
29
+ scheduleAsyncPull(signal);
30
+ }
31
+
32
+ // else do nothing, only schedule if connected
33
+ } else {
34
+ if (signal._isListener) {
35
+ schedulePull(signal);
36
+ }
37
+
38
+ dirtySignalConsumers(signal.subs);
39
+ signal.subs = new Map();
40
+ }
41
+ }
42
+
43
+ export function dirtySignalConsumers(map: Map<WeakRef<DerivedSignal<any, any>>, Edge>) {
44
+ for (const [subRef, edge] of map) {
45
+ const sub = subRef.deref();
46
+
47
+ if (sub === undefined || sub.computedCount !== edge.consumedAt) continue;
48
+
49
+ const dirtyState = sub._state;
50
+
51
+ switch (dirtyState) {
52
+ case SignalState.Clean:
53
+ sub._state = SignalState.MaybeDirty;
54
+ sub.dirtyHead = edge;
55
+ edge.nextDirty = undefined;
56
+ propagateDirty(sub);
57
+ break;
58
+
59
+ case SignalState.Pending:
60
+ case SignalState.MaybeDirty: {
61
+ let subEdge = sub.dirtyHead!;
62
+ const ord = edge.ord;
63
+
64
+ if (subEdge.ord > ord) {
65
+ sub.dirtyHead = edge;
66
+ edge.nextDirty = subEdge;
67
+
68
+ if (dirtyState === SignalState.Pending) {
69
+ // If the signal is pending, the first edge is the halt edge. If the
70
+ // new dirty edge is BEFORE the halt edge, then it means that something
71
+ // changed before the current halt, so we need to cancel the current computation
72
+ // and recompute.
73
+ sub._state = SignalState.MaybeDirty;
74
+ propagateDirty(sub);
75
+ }
76
+ } else {
77
+ let nextDirty = subEdge.nextDirty;
78
+
79
+ while (nextDirty !== undefined && nextDirty.ord < ord) {
80
+ subEdge = nextDirty;
81
+ nextDirty = subEdge.nextDirty;
82
+ }
83
+
84
+ edge.nextDirty = nextDirty;
85
+ subEdge!.nextDirty = edge;
86
+ }
87
+ break;
88
+ }
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,109 @@
1
+ import { ReactivePromise } from './async.js';
2
+ import type { DerivedSignal } from './derived.js';
3
+
4
+ let CURRENT_ORD = 0;
5
+
6
+ export const enum EdgeType {
7
+ Signal = 0,
8
+ Promise = 1,
9
+ }
10
+
11
+ export interface EdgeTypeDep {
12
+ [EdgeType.Signal]: DerivedSignal<any, any>;
13
+ [EdgeType.Promise]: ReactivePromise<any>;
14
+ }
15
+
16
+ interface BaseEdge {
17
+ type: EdgeType;
18
+ dep: EdgeTypeDep[EdgeType];
19
+ ord: number;
20
+ updatedAt: number;
21
+ consumedAt: number;
22
+
23
+ nextDirty: Edge | undefined;
24
+ }
25
+
26
+ export interface SignalEdge extends BaseEdge {
27
+ type: EdgeType.Signal;
28
+ dep: DerivedSignal<any, any>;
29
+ }
30
+
31
+ export interface PromiseEdge extends BaseEdge {
32
+ type: EdgeType.Promise;
33
+ dep: ReactivePromise<any>;
34
+ }
35
+
36
+ export type Edge = SignalEdge | PromiseEdge;
37
+
38
+ export function createEdge<T extends EdgeType, R extends T extends EdgeType.Signal ? SignalEdge : PromiseEdge>(
39
+ prevEdge: Edge | undefined,
40
+ type: T,
41
+ dep: EdgeTypeDep[T],
42
+ updatedAt: number,
43
+ consumedAt: number,
44
+ ): R {
45
+ if (prevEdge === undefined) {
46
+ return {
47
+ type,
48
+ dep,
49
+ ord: CURRENT_ORD++,
50
+ updatedAt,
51
+ consumedAt,
52
+ nextDirty: undefined,
53
+ } as R;
54
+ }
55
+
56
+ prevEdge.ord = CURRENT_ORD++;
57
+ prevEdge.updatedAt = updatedAt;
58
+ prevEdge.consumedAt = consumedAt;
59
+ prevEdge.nextDirty = undefined;
60
+ return prevEdge as R;
61
+ }
62
+
63
+ export function insertDirty(node: DerivedSignal<any, any>, edge: Edge) {
64
+ const ord = edge.ord;
65
+ let currentEdge = node.dirtyHead;
66
+
67
+ if (currentEdge === undefined || currentEdge.ord > ord) {
68
+ node.dirtyHead = edge;
69
+ } else {
70
+ let nextEdge = currentEdge.nextDirty;
71
+
72
+ while (nextEdge !== undefined && nextEdge.ord < ord) {
73
+ currentEdge = nextEdge;
74
+ nextEdge = currentEdge.nextDirty;
75
+ }
76
+
77
+ currentEdge.nextDirty = edge;
78
+ }
79
+ }
80
+
81
+ export function findAndRemoveDirty(
82
+ sub: DerivedSignal<any, any>,
83
+ dep: DerivedSignal<any, any> | ReactivePromise<any>,
84
+ ): Edge | undefined {
85
+ let edge = sub.dirtyHead;
86
+
87
+ if (edge === undefined) {
88
+ return undefined;
89
+ }
90
+
91
+ if (edge.dep === dep) {
92
+ sub.dirtyHead = edge.nextDirty;
93
+ return edge;
94
+ }
95
+
96
+ let nextLink = edge.nextDirty;
97
+
98
+ while (nextLink !== undefined) {
99
+ if (nextLink.dep === dep) {
100
+ edge.nextDirty = nextLink.nextDirty;
101
+ return nextLink;
102
+ }
103
+
104
+ edge = nextLink;
105
+ nextLink = edge.nextDirty;
106
+ }
107
+
108
+ return undefined;
109
+ }