@fluixi/reactive 1.0.0-alpha.100

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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -0
  3. package/dist/cdn/reactive.cjs +2803 -0
  4. package/dist/cdn/reactive.d.ts +11 -0
  5. package/dist/cdn/reactive.d.ts.map +1 -0
  6. package/dist/cdn/reactive.global.js +2 -0
  7. package/dist/cdn/reactive.js +10 -0
  8. package/dist/cdn/reactive.mjs +2787 -0
  9. package/dist/cdn/signal.cjs +299 -0
  10. package/dist/cdn/signal.d.ts +19 -0
  11. package/dist/cdn/signal.d.ts.map +1 -0
  12. package/dist/cdn/signal.global.js +2 -0
  13. package/dist/cdn/signal.js +18 -0
  14. package/dist/cdn/signal.mjs +276 -0
  15. package/dist/index.cjs +2803 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/index.mjs +2787 -0
  20. package/dist/lib/index.cjs +2804 -0
  21. package/dist/lib/index.d.ts +8 -0
  22. package/dist/lib/index.d.ts.map +1 -0
  23. package/dist/lib/index.js +11 -0
  24. package/dist/lib/index.mjs +2789 -0
  25. package/dist/lib/observable/index.cjs +386 -0
  26. package/dist/lib/observable/index.d.ts +4 -0
  27. package/dist/lib/observable/index.d.ts.map +1 -0
  28. package/dist/lib/observable/index.js +3 -0
  29. package/dist/lib/observable/index.mjs +364 -0
  30. package/dist/lib/observable/observable.cjs +384 -0
  31. package/dist/lib/observable/observable.d.ts +246 -0
  32. package/dist/lib/observable/observable.d.ts.map +1 -0
  33. package/dist/lib/observable/observable.js +268 -0
  34. package/dist/lib/observable/observable.mjs +361 -0
  35. package/dist/lib/observable/subscription.cjs +180 -0
  36. package/dist/lib/observable/subscription.d.ts +58 -0
  37. package/dist/lib/observable/subscription.d.ts.map +1 -0
  38. package/dist/lib/observable/subscription.js +177 -0
  39. package/dist/lib/observable/subscription.mjs +157 -0
  40. package/dist/lib/observable/types.cjs +18 -0
  41. package/dist/lib/observable/types.d.ts +44 -0
  42. package/dist/lib/observable/types.d.ts.map +1 -0
  43. package/dist/lib/observable/types.js +1 -0
  44. package/dist/lib/observable/types.mjs +0 -0
  45. package/dist/lib/predicates.cjs +44 -0
  46. package/dist/lib/predicates.d.ts +37 -0
  47. package/dist/lib/predicates.d.ts.map +1 -0
  48. package/dist/lib/predicates.js +62 -0
  49. package/dist/lib/predicates.mjs +23 -0
  50. package/dist/lib/signal/api.cjs +592 -0
  51. package/dist/lib/signal/api.d.ts +227 -0
  52. package/dist/lib/signal/api.d.ts.map +1 -0
  53. package/dist/lib/signal/api.js +228 -0
  54. package/dist/lib/signal/api.mjs +569 -0
  55. package/dist/lib/signal/cached-resource.cjs +726 -0
  56. package/dist/lib/signal/cached-resource.d.ts +86 -0
  57. package/dist/lib/signal/cached-resource.d.ts.map +1 -0
  58. package/dist/lib/signal/cached-resource.js +250 -0
  59. package/dist/lib/signal/cached-resource.mjs +704 -0
  60. package/dist/lib/signal/graph/children.cjs +326 -0
  61. package/dist/lib/signal/graph/children.d.ts +2 -0
  62. package/dist/lib/signal/graph/children.d.ts.map +1 -0
  63. package/dist/lib/signal/graph/children.js +30 -0
  64. package/dist/lib/signal/graph/children.mjs +303 -0
  65. package/dist/lib/signal/graph/compat.cjs +590 -0
  66. package/dist/lib/signal/graph/compat.d.ts +66 -0
  67. package/dist/lib/signal/graph/compat.d.ts.map +1 -0
  68. package/dist/lib/signal/graph/compat.js +197 -0
  69. package/dist/lib/signal/graph/compat.mjs +568 -0
  70. package/dist/lib/signal/graph/core.cjs +295 -0
  71. package/dist/lib/signal/graph/core.d.ts +99 -0
  72. package/dist/lib/signal/graph/core.d.ts.map +1 -0
  73. package/dist/lib/signal/graph/core.js +268 -0
  74. package/dist/lib/signal/graph/core.mjs +273 -0
  75. package/dist/lib/signal/graph/debounced.cjs +680 -0
  76. package/dist/lib/signal/graph/debounced.d.ts +67 -0
  77. package/dist/lib/signal/graph/debounced.d.ts.map +1 -0
  78. package/dist/lib/signal/graph/debounced.js +92 -0
  79. package/dist/lib/signal/graph/debounced.mjs +657 -0
  80. package/dist/lib/signal/graph/derived.cjs +508 -0
  81. package/dist/lib/signal/graph/derived.d.ts +14 -0
  82. package/dist/lib/signal/graph/derived.d.ts.map +1 -0
  83. package/dist/lib/signal/graph/derived.js +65 -0
  84. package/dist/lib/signal/graph/derived.mjs +485 -0
  85. package/dist/lib/signal/graph/detect.cjs +66 -0
  86. package/dist/lib/signal/graph/detect.d.ts +17 -0
  87. package/dist/lib/signal/graph/detect.d.ts.map +1 -0
  88. package/dist/lib/signal/graph/detect.js +39 -0
  89. package/dist/lib/signal/graph/detect.mjs +45 -0
  90. package/dist/lib/signal/graph/error-boundary.cjs +313 -0
  91. package/dist/lib/signal/graph/error-boundary.d.ts +16 -0
  92. package/dist/lib/signal/graph/error-boundary.d.ts.map +1 -0
  93. package/dist/lib/signal/graph/error-boundary.js +34 -0
  94. package/dist/lib/signal/graph/error-boundary.mjs +290 -0
  95. package/dist/lib/signal/graph/index.cjs +305 -0
  96. package/dist/lib/signal/graph/index.d.ts +22 -0
  97. package/dist/lib/signal/graph/index.d.ts.map +1 -0
  98. package/dist/lib/signal/graph/index.js +21 -0
  99. package/dist/lib/signal/graph/index.mjs +283 -0
  100. package/dist/lib/signal/graph/observe.cjs +198 -0
  101. package/dist/lib/signal/graph/observe.d.ts +185 -0
  102. package/dist/lib/signal/graph/observe.d.ts.map +1 -0
  103. package/dist/lib/signal/graph/observe.js +198 -0
  104. package/dist/lib/signal/graph/observe.mjs +175 -0
  105. package/dist/lib/signal/graph/resource.cjs +632 -0
  106. package/dist/lib/signal/graph/resource.d.ts +39 -0
  107. package/dist/lib/signal/graph/resource.d.ts.map +1 -0
  108. package/dist/lib/signal/graph/resource.js +165 -0
  109. package/dist/lib/signal/graph/resource.mjs +609 -0
  110. package/dist/lib/signal/graph/runtime.cjs +522 -0
  111. package/dist/lib/signal/graph/runtime.d.ts +80 -0
  112. package/dist/lib/signal/graph/runtime.d.ts.map +1 -0
  113. package/dist/lib/signal/graph/runtime.js +358 -0
  114. package/dist/lib/signal/graph/runtime.mjs +499 -0
  115. package/dist/lib/signal/graph/signal-next.cjs +1249 -0
  116. package/dist/lib/signal/graph/signal-next.d.ts +38 -0
  117. package/dist/lib/signal/graph/signal-next.d.ts.map +1 -0
  118. package/dist/lib/signal/graph/signal-next.js +44 -0
  119. package/dist/lib/signal/graph/signal-next.mjs +1227 -0
  120. package/dist/lib/signal/graph/ssr-hooks.cjs +89 -0
  121. package/dist/lib/signal/graph/ssr-hooks.d.ts +51 -0
  122. package/dist/lib/signal/graph/ssr-hooks.d.ts.map +1 -0
  123. package/dist/lib/signal/graph/ssr-hooks.js +80 -0
  124. package/dist/lib/signal/graph/ssr-hooks.mjs +68 -0
  125. package/dist/lib/signal/graph/state.cjs +72 -0
  126. package/dist/lib/signal/graph/state.d.ts +76 -0
  127. package/dist/lib/signal/graph/state.d.ts.map +1 -0
  128. package/dist/lib/signal/graph/state.js +99 -0
  129. package/dist/lib/signal/graph/state.mjs +49 -0
  130. package/dist/lib/signal/graph/suspense.cjs +313 -0
  131. package/dist/lib/signal/graph/suspense.d.ts +13 -0
  132. package/dist/lib/signal/graph/suspense.d.ts.map +1 -0
  133. package/dist/lib/signal/graph/suspense.js +36 -0
  134. package/dist/lib/signal/graph/suspense.mjs +290 -0
  135. package/dist/lib/signal/index-legacy.cjs +2582 -0
  136. package/dist/lib/signal/index-legacy.d.ts +12 -0
  137. package/dist/lib/signal/index-legacy.d.ts.map +1 -0
  138. package/dist/lib/signal/index-legacy.js +11 -0
  139. package/dist/lib/signal/index-legacy.mjs +2560 -0
  140. package/dist/lib/signal/index.cjs +1350 -0
  141. package/dist/lib/signal/index.d.ts +19 -0
  142. package/dist/lib/signal/index.d.ts.map +1 -0
  143. package/dist/lib/signal/index.js +19 -0
  144. package/dist/lib/signal/index.mjs +1328 -0
  145. package/dist/lib/signal/list.cjs +561 -0
  146. package/dist/lib/signal/list.d.ts +39 -0
  147. package/dist/lib/signal/list.d.ts.map +1 -0
  148. package/dist/lib/signal/list.js +172 -0
  149. package/dist/lib/signal/list.mjs +538 -0
  150. package/dist/lib/signal/resource-api.cjs +638 -0
  151. package/dist/lib/signal/resource-api.d.ts +19 -0
  152. package/dist/lib/signal/resource-api.d.ts.map +1 -0
  153. package/dist/lib/signal/resource-api.js +27 -0
  154. package/dist/lib/signal/resource-api.mjs +615 -0
  155. package/dist/lib/signal/signal.cjs +2035 -0
  156. package/dist/lib/signal/signal.d.ts +303 -0
  157. package/dist/lib/signal/signal.d.ts.map +1 -0
  158. package/dist/lib/signal/signal.js +1233 -0
  159. package/dist/lib/signal/signal.mjs +2012 -0
  160. package/dist/lib/signal/types.cjs +38 -0
  161. package/dist/lib/signal/types.d.ts +133 -0
  162. package/dist/lib/signal/types.d.ts.map +1 -0
  163. package/dist/lib/signal/types.js +38 -0
  164. package/dist/lib/signal/types.mjs +17 -0
  165. package/dist/lib/signal/utilities.cjs +1024 -0
  166. package/dist/lib/signal/utilities.d.ts +247 -0
  167. package/dist/lib/signal/utilities.d.ts.map +1 -0
  168. package/dist/lib/signal/utilities.js +346 -0
  169. package/dist/lib/signal/utilities.mjs +1002 -0
  170. package/dist/lib/store/api.cjs +662 -0
  171. package/dist/lib/store/api.d.ts +40 -0
  172. package/dist/lib/store/api.d.ts.map +1 -0
  173. package/dist/lib/store/api.js +63 -0
  174. package/dist/lib/store/api.mjs +646 -0
  175. package/dist/lib/store/index.cjs +741 -0
  176. package/dist/lib/store/index.d.ts +16 -0
  177. package/dist/lib/store/index.d.ts.map +1 -0
  178. package/dist/lib/store/index.js +13 -0
  179. package/dist/lib/store/index.mjs +726 -0
  180. package/dist/lib/store/setter.types.cjs +18 -0
  181. package/dist/lib/store/setter.types.d.ts +131 -0
  182. package/dist/lib/store/setter.types.d.ts.map +1 -0
  183. package/dist/lib/store/setter.types.js +65 -0
  184. package/dist/lib/store/setter.types.mjs +0 -0
  185. package/dist/lib/store/store.cjs +708 -0
  186. package/dist/lib/store/store.d.ts +142 -0
  187. package/dist/lib/store/store.d.ts.map +1 -0
  188. package/dist/lib/store/store.js +812 -0
  189. package/dist/lib/store/store.mjs +694 -0
  190. package/dist/lib/store/types.cjs +18 -0
  191. package/dist/lib/store/types.d.ts +57 -0
  192. package/dist/lib/store/types.d.ts.map +1 -0
  193. package/dist/lib/store/types.js +1 -0
  194. package/dist/lib/store/types.mjs +0 -0
  195. package/dist/lib/store/utils.cjs +43 -0
  196. package/dist/lib/store/utils.d.ts +13 -0
  197. package/dist/lib/store/utils.d.ts.map +1 -0
  198. package/dist/lib/store/utils.js +29 -0
  199. package/dist/lib/store/utils.mjs +22 -0
  200. package/dist/lib/subject.cjs +450 -0
  201. package/dist/lib/subject.d.ts +14 -0
  202. package/dist/lib/subject.d.ts.map +1 -0
  203. package/dist/lib/subject.js +81 -0
  204. package/dist/lib/subject.mjs +427 -0
  205. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  206. package/dist/version.generated.cjs +27 -0
  207. package/dist/version.generated.d.ts +3 -0
  208. package/dist/version.generated.d.ts.map +1 -0
  209. package/dist/version.generated.js +3 -0
  210. package/dist/version.generated.mjs +7 -0
  211. package/package.json +115 -0
@@ -0,0 +1,295 @@
1
+ /*! @fluixi/reactive v1.0.0-alpha.100 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/lib/signal/graph/core.ts
22
+ var core_exports = {};
23
+ __export(core_exports, {
24
+ Computed: () => Computed,
25
+ State: () => State,
26
+ Watcher: () => Watcher,
27
+ currentComputed: () => currentComputed,
28
+ hasSinks: () => hasSinks,
29
+ untrack: () => untrack
30
+ });
31
+ module.exports = __toCommonJS(core_exports);
32
+
33
+ // src/version.generated.ts
34
+ var VERSION = "1.0.0-alpha.100";
35
+
36
+ // src/lib/signal/graph/state.ts
37
+ var MAJOR = VERSION.split(".")[0];
38
+ var KEY = `__fluixi_signal_state_v${MAJOR}__`;
39
+ var VERSIONS_KEY = `__fluixi_signal_builds_v${MAJOR}__`;
40
+ var defaultSchedule = (run) => {
41
+ if (typeof queueMicrotask === "function") queueMicrotask(run);
42
+ else void Promise.resolve().then(run);
43
+ };
44
+ function defaults() {
45
+ return {
46
+ consumer: null,
47
+ untracked: false,
48
+ owner: null,
49
+ queue: [],
50
+ scheduled: false,
51
+ flushing: false,
52
+ batchDepth: 0,
53
+ hostSchedule: defaultSchedule,
54
+ onSuspend: null,
55
+ onError: null,
56
+ observer: null
57
+ };
58
+ }
59
+ var realm = globalThis;
60
+ function backfillState(existing) {
61
+ const fresh = defaults();
62
+ const target = existing;
63
+ for (const field of Object.keys(fresh)) {
64
+ if (!(field in existing)) target[field] = fresh[field];
65
+ }
66
+ return existing;
67
+ }
68
+ function claim() {
69
+ const existing = realm[KEY];
70
+ return existing ? backfillState(existing) : realm[KEY] = defaults();
71
+ }
72
+ var G = claim();
73
+ (realm[VERSIONS_KEY] ??= []).push(VERSION);
74
+
75
+ // src/lib/signal/graph/observe.ts
76
+ function observeReactiveNodes(next) {
77
+ G.observer = next;
78
+ return () => {
79
+ if (G.observer === next) G.observer = null;
80
+ };
81
+ }
82
+ var running = [];
83
+ function enterReactiveRun(node) {
84
+ if (!G.observer) return false;
85
+ running.push(node);
86
+ return true;
87
+ }
88
+ function exitReactiveRun() {
89
+ running.pop();
90
+ }
91
+ function reportReactiveRun(node) {
92
+ G.observer?.ran?.(node);
93
+ }
94
+ function reportReactiveWrite(node, by) {
95
+ G.observer?.wrote?.(node, by);
96
+ }
97
+ var HOOK = "__FLUIXI_DEVTOOLS_HOOK__";
98
+ var hook = globalThis[HOOK];
99
+ if (typeof hook?.inject === "function") {
100
+ try {
101
+ hook.inject({ observeReactiveNodes, version: VERSION });
102
+ } catch {
103
+ }
104
+ }
105
+
106
+ // src/lib/signal/graph/core.ts
107
+ var CLEAN = 0;
108
+ var CHECK = 1;
109
+ var DIRTY = 2;
110
+ var Source = class {
111
+ constructor() {
112
+ /** Computeds, and watched leaves, that read this node. */
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ this.observers = null;
115
+ /** Watchers eagerly observing this node, which is how `runtime.ts` schedules effects. */
116
+ this.watchers = null;
117
+ /** Bumped whenever this node's value actually changes (equality-gated). */
118
+ this.version = 0;
119
+ }
120
+ /** Settle this node so a reader sees a current value/version. Overridden by Computed. */
121
+ updateIfNecessary() {
122
+ }
123
+ /** Link the current consumer (if any) as an observer of this node. */
124
+ track() {
125
+ if (G.untracked) return;
126
+ const consumer = G.consumer;
127
+ if (!consumer) return;
128
+ (this.observers ??= /* @__PURE__ */ new Set()).add(consumer);
129
+ (consumer.sources ??= /* @__PURE__ */ new Set()).add(this);
130
+ }
131
+ };
132
+ var State = class extends Source {
133
+ constructor(value, options) {
134
+ super();
135
+ this._value = value;
136
+ this._equals = options?.equals === false ? () => false : options?.equals ?? Object.is;
137
+ }
138
+ get() {
139
+ this.track();
140
+ return this._value;
141
+ }
142
+ set(next) {
143
+ if (this._equals(this._value, next)) return;
144
+ this._value = next;
145
+ this.version++;
146
+ reportReactiveWrite(this, G.consumer);
147
+ if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
148
+ notifyWatchers(this);
149
+ }
150
+ /** Read without subscribing (peek). */
151
+ peek() {
152
+ return this._value;
153
+ }
154
+ };
155
+ var Computed = class extends Source {
156
+ constructor(fn, options) {
157
+ super();
158
+ this._threw = false;
159
+ this._initialized = false;
160
+ this.state = DIRTY;
161
+ /** Nodes this computation read on its last run. Rebuilt on every run. */
162
+ this.sources = null;
163
+ this._fn = fn;
164
+ this._equals = options?.equals === false ? () => false : options?.equals ?? Object.is;
165
+ }
166
+ get() {
167
+ if (G.consumer === this) {
168
+ throw new Error("Signal.Computed cannot read itself");
169
+ }
170
+ this.track();
171
+ this.updateIfNecessary();
172
+ if (this._threw) throw this._error;
173
+ return this._value;
174
+ }
175
+ /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
176
+ updateIfNecessary() {
177
+ if (this.state === CHECK) {
178
+ if (this.sources) {
179
+ for (const s of this.sources) {
180
+ s.updateIfNecessary();
181
+ if (this.state === DIRTY) break;
182
+ }
183
+ }
184
+ }
185
+ if (this.state === DIRTY || !this._initialized) this.recompute();
186
+ this.state = CLEAN;
187
+ }
188
+ recompute() {
189
+ if (this.sources) {
190
+ for (const s of this.sources) s.observers?.delete(this);
191
+ this.sources.clear();
192
+ }
193
+ const prevConsumer = G.consumer;
194
+ const prevUntracked = G.untracked;
195
+ const watched = enterReactiveRun(this);
196
+ G.consumer = this;
197
+ G.untracked = false;
198
+ let next;
199
+ let threw = false;
200
+ let err;
201
+ try {
202
+ next = this._fn();
203
+ } catch (e) {
204
+ threw = true;
205
+ err = e;
206
+ next = this._value;
207
+ } finally {
208
+ G.consumer = prevConsumer;
209
+ G.untracked = prevUntracked;
210
+ if (watched) exitReactiveRun();
211
+ }
212
+ const changed = !this._initialized || this._threw !== threw || (threw ? err !== this._error : !this._equals(this._value, next));
213
+ this._initialized = true;
214
+ this._threw = threw;
215
+ this._error = err;
216
+ this._value = next;
217
+ reportReactiveRun(this);
218
+ if (changed) {
219
+ this.version++;
220
+ if (this.observers) for (const o of [...this.observers]) o.markDirty(DIRTY);
221
+ notifyWatchers(this);
222
+ }
223
+ }
224
+ /**
225
+ * Push a maybe-dirty mark. A direct observer of a changed node gets DIRTY; its
226
+ * own observers get CHECK (recurse), stopping where already marked, so the
227
+ * push is O(newly-dirtied) and never recomputes.
228
+ */
229
+ markDirty(level) {
230
+ if (this.state >= level) return;
231
+ const wasClean = this.state === CLEAN;
232
+ this.state = level;
233
+ if (wasClean) {
234
+ if (this.observers) for (const o of [...this.observers]) o.markDirty(CHECK);
235
+ notifyWatchers(this);
236
+ }
237
+ }
238
+ peek() {
239
+ this.updateIfNecessary();
240
+ if (this._threw) throw this._error;
241
+ return this._value;
242
+ }
243
+ };
244
+ function notifyWatchers(node) {
245
+ if (!node.watchers) return;
246
+ for (const w of [...node.watchers]) w._notify(node);
247
+ }
248
+ var Watcher = class {
249
+ constructor(notify) {
250
+ this._watched = /* @__PURE__ */ new Set();
251
+ this._pending = /* @__PURE__ */ new Set();
252
+ this._notifyFn = notify;
253
+ }
254
+ watch(...nodes) {
255
+ for (const n of nodes) {
256
+ this._watched.add(n);
257
+ (n.watchers ??= /* @__PURE__ */ new Set()).add(this);
258
+ }
259
+ }
260
+ unwatch(...nodes) {
261
+ for (const n of nodes) {
262
+ this._watched.delete(n);
263
+ n.watchers?.delete(this);
264
+ this._pending.delete(n);
265
+ }
266
+ }
267
+ /** Watched nodes that may have changed since the last getPending(). */
268
+ getPending() {
269
+ const out = [...this._pending];
270
+ this._pending.clear();
271
+ return out;
272
+ }
273
+ /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
274
+ _notify(node) {
275
+ if (!this._watched.has(node) || this._pending.has(node)) return;
276
+ const wasEmpty = this._pending.size === 0;
277
+ this._pending.add(node);
278
+ if (wasEmpty) this._notifyFn();
279
+ }
280
+ };
281
+ function untrack(fn) {
282
+ if (G.untracked) return fn();
283
+ G.untracked = true;
284
+ try {
285
+ return fn();
286
+ } finally {
287
+ G.untracked = false;
288
+ }
289
+ }
290
+ function currentComputed() {
291
+ return G.consumer;
292
+ }
293
+ function hasSinks(node) {
294
+ return !!(node.observers && node.observers.size) || !!(node.watchers && node.watchers.size);
295
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The TC39-shaped reactive graph core.
3
+ *
4
+ * `State`, `Computed` and `Watcher` own the dependency graph and nothing else:
5
+ * values, versions, source and observer edges, the lazy state machine, and
6
+ * glitch-free pull evaluation. Ownership, scheduling, effects, suspense and
7
+ * resources all live in `runtime.ts`, with the `create*` surface over them in
8
+ * `compat.ts`.
9
+ *
10
+ * `../signal.ts` also implements a graph and is not the one that runs. It is
11
+ * archive, reachable only through the `@fluixi/reactive/signal-legacy` entry, and
12
+ * editing it changes nothing here. Its `Computation` and `signalObservers` are the
13
+ * earlier spelling of what `Computed` and `Source.observers` do below, which is
14
+ * the only reason to open it.
15
+ *
16
+ * The scheme is push the dirt, pull the value, as in Reactively, SolidJS and the
17
+ * TC39 polyfill. A write pushes a maybe-dirty mark down to observers without
18
+ * recomputing anything. A read pulls: upstream settles lazily and only what
19
+ * actually changed recomputes, gated on equality. A read outside a tracking scope
20
+ * subscribes to nothing.
21
+ */
22
+ declare const CLEAN = 0;
23
+ declare const CHECK = 1;
24
+ declare const DIRTY = 2;
25
+ type NodeState = typeof CLEAN | typeof CHECK | typeof DIRTY;
26
+ /** A thing that can be depended upon: holds observers + a monotonic version. */
27
+ declare abstract class Source {
28
+ /** Computeds, and watched leaves, that read this node. */
29
+ observers: Set<Computed<any>> | null;
30
+ /** Watchers eagerly observing this node, which is how `runtime.ts` schedules effects. */
31
+ watchers: Set<Watcher> | null;
32
+ /** Bumped whenever this node's value actually changes (equality-gated). */
33
+ version: number;
34
+ /** Settle this node so a reader sees a current value/version. Overridden by Computed. */
35
+ updateIfNecessary(): void;
36
+ /** Link the current consumer (if any) as an observer of this node. */
37
+ protected track(): void;
38
+ }
39
+ export declare class State<T> extends Source {
40
+ private _value;
41
+ private readonly _equals;
42
+ constructor(value: T, options?: {
43
+ equals?: ((a: T, b: T) => boolean) | false;
44
+ });
45
+ get(): T;
46
+ set(next: T): void;
47
+ /** Read without subscribing (peek). */
48
+ peek(): T;
49
+ }
50
+ export declare class Computed<T> extends Source {
51
+ private _value;
52
+ private _error;
53
+ private _threw;
54
+ private _initialized;
55
+ state: NodeState;
56
+ /** Nodes this computation read on its last run. Rebuilt on every run. */
57
+ sources: Set<Source> | null;
58
+ private readonly _fn;
59
+ private readonly _equals;
60
+ constructor(fn: () => T, options?: {
61
+ equals?: ((a: T, b: T) => boolean) | false;
62
+ });
63
+ get(): T;
64
+ /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
65
+ updateIfNecessary(): void;
66
+ private recompute;
67
+ /**
68
+ * Push a maybe-dirty mark. A direct observer of a changed node gets DIRTY; its
69
+ * own observers get CHECK (recurse), stopping where already marked, so the
70
+ * push is O(newly-dirtied) and never recomputes.
71
+ */
72
+ markDirty(level: NodeState): void;
73
+ peek(): T;
74
+ }
75
+ /**
76
+ * Signal.subtle.Watcher, the only eager observation primitive. Its callback fires
77
+ * when a watched signal MAY have changed; the callback must not read or write
78
+ * signals. Effects are built on this and a scheduler, in `runtime.ts`.
79
+ */
80
+ export declare class Watcher {
81
+ private readonly _notifyFn;
82
+ private readonly _watched;
83
+ private _pending;
84
+ constructor(notify: () => void);
85
+ watch(...nodes: Source[]): void;
86
+ unwatch(...nodes: Source[]): void;
87
+ /** Watched nodes that may have changed since the last getPending(). */
88
+ getPending(): Source[];
89
+ /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
90
+ _notify(node: Source): void;
91
+ }
92
+ /** Signal.subtle.untrack: run `fn` without subscribing the current consumer. */
93
+ export declare function untrack<T>(fn: () => T): T;
94
+ /** Signal.subtle.currentComputed, the computed currently evaluating, or null. */
95
+ export declare function currentComputed(): Computed<unknown> | null;
96
+ /** Whether a node currently has observers/watchers (Signal.subtle.hasSinks). */
97
+ export declare function hasSinks(node: Source): boolean;
98
+ export {};
99
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../../src/lib/signal/graph/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * The TC39-shaped reactive graph core.
3
+ *
4
+ * `State`, `Computed` and `Watcher` own the dependency graph and nothing else:
5
+ * values, versions, source and observer edges, the lazy state machine, and
6
+ * glitch-free pull evaluation. Ownership, scheduling, effects, suspense and
7
+ * resources all live in `runtime.ts`, with the `create*` surface over them in
8
+ * `compat.ts`.
9
+ *
10
+ * `../signal.ts` also implements a graph and is not the one that runs. It is
11
+ * archive, reachable only through the `@fluixi/reactive/signal-legacy` entry, and
12
+ * editing it changes nothing here. Its `Computation` and `signalObservers` are the
13
+ * earlier spelling of what `Computed` and `Source.observers` do below, which is
14
+ * the only reason to open it.
15
+ *
16
+ * The scheme is push the dirt, pull the value, as in Reactively, SolidJS and the
17
+ * TC39 polyfill. A write pushes a maybe-dirty mark down to observers without
18
+ * recomputing anything. A read pulls: upstream settles lazily and only what
19
+ * actually changed recomputes, gated on equality. A read outside a tracking scope
20
+ * subscribes to nothing.
21
+ */
22
+ import { G } from './state.js';
23
+ import { enterReactiveRun, exitReactiveRun, reportReactiveRun, reportReactiveWrite } from './observe.js';
24
+ // CLEAN, the cached value is current. CHECK, a source may have changed, so verify on
25
+ // read. DIRTY, recompute without checking.
26
+ // CHECK is what keeps a diamond from recomputing twice: a write marks direct observers
27
+ // DIRTY and everything downstream of them CHECK, so a transitive reader asks its
28
+ // sources whether their versions moved before it runs its own function.
29
+ const CLEAN = 0;
30
+ const CHECK = 1;
31
+ const DIRTY = 2;
32
+ // The current consumer + untrack flag live on the shared globalThis-backed state (G)
33
+ // so duplicate module copies share one graph. `consumer`/`untracked` proxy them.
34
+ /** A thing that can be depended upon: holds observers + a monotonic version. */
35
+ class Source {
36
+ /** Computeds, and watched leaves, that read this node. */
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ observers = null;
39
+ /** Watchers eagerly observing this node, which is how `runtime.ts` schedules effects. */
40
+ watchers = null;
41
+ /** Bumped whenever this node's value actually changes (equality-gated). */
42
+ version = 0;
43
+ /** Settle this node so a reader sees a current value/version. Overridden by Computed. */
44
+ updateIfNecessary() { }
45
+ /** Link the current consumer (if any) as an observer of this node. */
46
+ track() {
47
+ if (G.untracked)
48
+ return;
49
+ const consumer = G.consumer;
50
+ if (!consumer)
51
+ return;
52
+ (this.observers ??= new Set()).add(consumer);
53
+ (consumer.sources ??= new Set()).add(this);
54
+ }
55
+ }
56
+ export class State extends Source {
57
+ _value;
58
+ _equals;
59
+ constructor(value, options) {
60
+ super();
61
+ this._value = value;
62
+ this._equals = options?.equals === false ? () => false : options?.equals ?? Object.is;
63
+ }
64
+ get() {
65
+ this.track();
66
+ return this._value;
67
+ }
68
+ set(next) {
69
+ if (this._equals(this._value, next))
70
+ return;
71
+ this._value = next;
72
+ this.version++;
73
+ // Who wrote it: once a write has propagated, nothing else records what caused it.
74
+ reportReactiveWrite(this, G.consumer);
75
+ // Push: direct observers go DIRTY, their transitive observers CHECK.
76
+ // Snapshot first. The scheduler is synchronous, so an effect can re-run part way
77
+ // through this loop, and re-running re-subscribes it, which mutates `observers`.
78
+ // Iterating the live Set would then reach the re-added node again, and again.
79
+ if (this.observers)
80
+ for (const o of [...this.observers])
81
+ o.markDirty(DIRTY);
82
+ notifyWatchers(this);
83
+ }
84
+ /** Read without subscribing (peek). */
85
+ peek() {
86
+ return this._value;
87
+ }
88
+ }
89
+ export class Computed extends Source {
90
+ _value;
91
+ _error;
92
+ _threw = false;
93
+ _initialized = false;
94
+ state = DIRTY;
95
+ /** Nodes this computation read on its last run. Rebuilt on every run. */
96
+ sources = null;
97
+ _fn;
98
+ _equals;
99
+ constructor(fn, options) {
100
+ super();
101
+ this._fn = fn;
102
+ this._equals = options?.equals === false ? () => false : options?.equals ?? Object.is;
103
+ }
104
+ get() {
105
+ // Cycle guard: a computed reading itself.
106
+ if (G.consumer === this) {
107
+ throw new Error('Signal.Computed cannot read itself');
108
+ }
109
+ this.track();
110
+ this.updateIfNecessary();
111
+ if (this._threw)
112
+ throw this._error;
113
+ return this._value;
114
+ }
115
+ /** Settle, if CHECK, verify sources; if DIRTY (or never run), recompute. */
116
+ updateIfNecessary() {
117
+ if (this.state === CHECK) {
118
+ // Pull each source; if one recomputes to a new value it will have flipped
119
+ // us to DIRTY (markDirty), so stop early.
120
+ if (this.sources) {
121
+ for (const s of this.sources) {
122
+ s.updateIfNecessary();
123
+ if (this.state === DIRTY)
124
+ break;
125
+ }
126
+ }
127
+ }
128
+ if (this.state === DIRTY || !this._initialized)
129
+ this.recompute();
130
+ this.state = CLEAN;
131
+ }
132
+ recompute() {
133
+ // Drop old edges (we re-collect during the run).
134
+ if (this.sources) {
135
+ for (const s of this.sources)
136
+ s.observers?.delete(this);
137
+ this.sources.clear();
138
+ }
139
+ const prevConsumer = G.consumer;
140
+ const prevUntracked = G.untracked;
141
+ const watched = enterReactiveRun(this);
142
+ G.consumer = this;
143
+ G.untracked = false;
144
+ let next;
145
+ let threw = false;
146
+ let err;
147
+ try {
148
+ next = this._fn();
149
+ }
150
+ catch (e) {
151
+ threw = true;
152
+ err = e;
153
+ next = this._value;
154
+ }
155
+ finally {
156
+ G.consumer = prevConsumer;
157
+ G.untracked = prevUntracked;
158
+ if (watched)
159
+ exitReactiveRun();
160
+ }
161
+ const changed = !this._initialized ||
162
+ this._threw !== threw ||
163
+ (threw ? err !== this._error : !this._equals(this._value, next));
164
+ this._initialized = true;
165
+ this._threw = threw;
166
+ this._error = err;
167
+ this._value = next;
168
+ reportReactiveRun(this);
169
+ if (changed) {
170
+ this.version++;
171
+ // Our value moved -> observers that were merely CHECK must now recompute.
172
+ if (this.observers)
173
+ for (const o of [...this.observers])
174
+ o.markDirty(DIRTY);
175
+ notifyWatchers(this);
176
+ }
177
+ }
178
+ /**
179
+ * Push a maybe-dirty mark. A direct observer of a changed node gets DIRTY; its
180
+ * own observers get CHECK (recurse), stopping where already marked, so the
181
+ * push is O(newly-dirtied) and never recomputes.
182
+ */
183
+ markDirty(level) {
184
+ if (this.state >= level)
185
+ return;
186
+ const wasClean = this.state === CLEAN;
187
+ this.state = level;
188
+ if (wasClean) {
189
+ if (this.observers)
190
+ for (const o of [...this.observers])
191
+ o.markDirty(CHECK);
192
+ notifyWatchers(this);
193
+ }
194
+ }
195
+ peek() {
196
+ this.updateIfNecessary();
197
+ if (this._threw)
198
+ throw this._error;
199
+ return this._value;
200
+ }
201
+ }
202
+ function notifyWatchers(node) {
203
+ if (!node.watchers)
204
+ return;
205
+ for (const w of [...node.watchers])
206
+ w._notify(node);
207
+ }
208
+ /**
209
+ * Signal.subtle.Watcher, the only eager observation primitive. Its callback fires
210
+ * when a watched signal MAY have changed; the callback must not read or write
211
+ * signals. Effects are built on this and a scheduler, in `runtime.ts`.
212
+ */
213
+ export class Watcher {
214
+ _notifyFn;
215
+ _watched = new Set();
216
+ _pending = new Set();
217
+ constructor(notify) {
218
+ this._notifyFn = notify;
219
+ }
220
+ watch(...nodes) {
221
+ for (const n of nodes) {
222
+ this._watched.add(n);
223
+ (n.watchers ??= new Set()).add(this);
224
+ }
225
+ }
226
+ unwatch(...nodes) {
227
+ for (const n of nodes) {
228
+ this._watched.delete(n);
229
+ n.watchers?.delete(this);
230
+ this._pending.delete(n);
231
+ }
232
+ }
233
+ /** Watched nodes that may have changed since the last getPending(). */
234
+ getPending() {
235
+ const out = [...this._pending];
236
+ this._pending.clear();
237
+ return out;
238
+ }
239
+ /** @internal, a watched node became non-clean. Edge-triggered: notify once. */
240
+ _notify(node) {
241
+ if (!this._watched.has(node) || this._pending.has(node))
242
+ return;
243
+ const wasEmpty = this._pending.size === 0;
244
+ this._pending.add(node);
245
+ if (wasEmpty)
246
+ this._notifyFn();
247
+ }
248
+ }
249
+ /** Signal.subtle.untrack: run `fn` without subscribing the current consumer. */
250
+ export function untrack(fn) {
251
+ if (G.untracked)
252
+ return fn();
253
+ G.untracked = true;
254
+ try {
255
+ return fn();
256
+ }
257
+ finally {
258
+ G.untracked = false;
259
+ }
260
+ }
261
+ /** Signal.subtle.currentComputed, the computed currently evaluating, or null. */
262
+ export function currentComputed() {
263
+ return G.consumer;
264
+ }
265
+ /** Whether a node currently has observers/watchers (Signal.subtle.hasSinks). */
266
+ export function hasSinks(node) {
267
+ return !!(node.observers && node.observers.size) || !!(node.watchers && node.watchers.size);
268
+ }