@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,358 @@
1
+ /**
2
+ * The runtime layer, on top of the graph core in `core.ts`.
3
+ *
4
+ * TC39 ships `State`, `Computed` and `Watcher` and stops there. Everything else a
5
+ * framework needs is left to userland, and this module is that userland:
6
+ *
7
+ * - Owner tree: createRoot, onCleanup, getOwner, runWithOwner. No graph edges;
8
+ * ownership is about disposal, not dependency.
9
+ * - Scheduler: an effect queue with flushSync and batch.
10
+ * - effect(): a re-running side effect, built from a Computed, a Watcher and
11
+ * an owner.
12
+ *
13
+ * This is the live implementation. `@fluixi/reactive/signal` re-exports it through
14
+ * `signal-next.ts`, and `compat.ts` puts the `create*` names over the top, so the
15
+ * long-standing public API lands here.
16
+ *
17
+ * Effects are built on `Signal.subtle.Watcher` the way the proposal intends: the
18
+ * watcher's notify only schedules, and the re-run, a tracked recompute, happens on
19
+ * flush.
20
+ */
21
+ import { Computed, Watcher, untrack } from './core.js';
22
+ import { G } from './state.js';
23
+ import { reportReactiveContext, reportReactiveContextCreated, reportReactiveDispose, reportReactiveNode, } from './observe.js';
24
+ /**
25
+ * A child scope of `parent`, attached so the parent's teardown disposes it too.
26
+ *
27
+ * For a derivation that re-runs: context still resolves up the chain from the
28
+ * creation-time owner, while anything registered during a run belongs to this
29
+ * scope and can be torn down before the next one: instead of accumulating on
30
+ * the enclosing scope and only running when that finally dies.
31
+ */
32
+ export function createChildOwner(parent) {
33
+ const owner = makeOwner(parent);
34
+ if (parent)
35
+ (parent.owned ??= []).push(owner);
36
+ return owner;
37
+ }
38
+ /**
39
+ * Tear down a scope's children and cleanups.
40
+ *
41
+ * `final: false` keeps the scope itself alive and reusable, what a re-running
42
+ * derivation needs between runs.
43
+ */
44
+ export function disposeScope(owner, final = true) {
45
+ disposeOwner(owner, final);
46
+ }
47
+ /** True once `owner` has been disposed. Deferred work should bail rather than run. */
48
+ export function isOwnerDisposed(owner) {
49
+ return !!owner && owner.disposed === true;
50
+ }
51
+ // Owner / scheduler / boundary-hook state all live on the shared globalThis-backed G
52
+ // (see ./state.ts) so duplicate module copies share one graph.
53
+ function makeOwner(parent) {
54
+ return { cleanups: null, owned: null, parent, contexts: null };
55
+ }
56
+ export function createContext(defaultValue, name) {
57
+ // Named so a watcher can tell two of them apart. Nothing else reads the description.
58
+ const id = Symbol(name ?? 'context');
59
+ reportReactiveContextCreated(id);
60
+ const Provider = (props) => {
61
+ // Stamp the value on a DEDICATED child owner, then read `children` under it: never
62
+ // on the current owner directly. Two sibling <Ctx.Provider> elements used inline (no
63
+ // wrapping component) run under the same ambient owner, so stamping there lets the
64
+ // second clobber the first's entry, every descendant then resolves the last value.
65
+ // A child scope isolates each Provider; because createMemo captures the owner at
66
+ // creation, the children memos built while reading `props.children` here capture this
67
+ // scope and resolve the right value even when rendered lazily (Portal/Show/For).
68
+ reportReactiveContext(id, 'provide', props.value);
69
+ const parent = G.owner;
70
+ const owner = makeOwner(parent);
71
+ (owner.contexts = new Map()).set(id, props.value);
72
+ if (parent)
73
+ (parent.owned ??= []).push(owner);
74
+ G.owner = owner;
75
+ try {
76
+ return props.children;
77
+ }
78
+ finally {
79
+ G.owner = parent;
80
+ }
81
+ };
82
+ return { id, defaultValue, Provider };
83
+ }
84
+ /** Read the nearest provided value for `context`, or its default. */
85
+ export function useContext(context) {
86
+ let o = G.owner;
87
+ while (o) {
88
+ if (o.contexts && o.contexts.has(context.id)) {
89
+ const found = o.contexts.get(context.id);
90
+ reportReactiveContext(context.id, 'read', found);
91
+ return found;
92
+ }
93
+ o = o.parent;
94
+ }
95
+ reportReactiveContext(context.id, 'read', context.defaultValue);
96
+ return context.defaultValue;
97
+ }
98
+ /** Read `context` starting from a specific owner chain (not the current scope). */
99
+ export function readContextFrom(owner, context) {
100
+ let o = owner;
101
+ while (o) {
102
+ if (o.contexts && o.contexts.has(context.id))
103
+ return o.contexts.get(context.id);
104
+ o = o.parent;
105
+ }
106
+ return context.defaultValue;
107
+ }
108
+ /** Run `fn` in a child scope that provides `value` for `context`. */
109
+ export function provide(context, value, fn) {
110
+ const owner = makeOwner(G.owner);
111
+ (owner.contexts = new Map()).set(context.id, value);
112
+ if (G.owner)
113
+ (G.owner.owned ??= []).push(owner);
114
+ const prev = G.owner;
115
+ G.owner = owner;
116
+ try {
117
+ return fn();
118
+ }
119
+ finally {
120
+ G.owner = prev;
121
+ }
122
+ }
123
+ // ── Suspend / error hooks, injected by the boundary layers so this file stays clear of them ──
124
+ /** Register how a thrown promise (a suspended read) is reported to a boundary. */
125
+ export function setSuspendHandler(fn) {
126
+ G.onSuspend = fn;
127
+ }
128
+ /** Report a suspended read (thrown promise) to the nearest boundary from the current owner. */
129
+ export function reportSuspend(promise) {
130
+ if (G.onSuspend) {
131
+ G.onSuspend(promise, G.owner);
132
+ return true;
133
+ }
134
+ return false;
135
+ }
136
+ /** Register how a thrown error is routed to an error boundary. Return true if handled. */
137
+ export function setErrorHandler(fn) {
138
+ G.onError = fn;
139
+ }
140
+ export function getOwner() {
141
+ return G.owner;
142
+ }
143
+ /** Set the current owner directly, returning the previous one. (Framework render glue.) */
144
+ export function setOwner(owner) {
145
+ const prev = G.owner;
146
+ G.owner = owner;
147
+ return prev;
148
+ }
149
+ export function runWithOwner(owner, fn) {
150
+ const prev = G.owner;
151
+ G.owner = owner;
152
+ try {
153
+ return fn();
154
+ }
155
+ finally {
156
+ G.owner = prev;
157
+ }
158
+ }
159
+ /** Register a teardown for the current owner scope. */
160
+ export function onCleanup(fn) {
161
+ if (!G.owner)
162
+ return; // no owner -> not retained, runs never (matches Solid)
163
+ // A disposed owner is dead, pushing here would recreate its (nulled) cleanups
164
+ // array and never run, a leak. Run the teardown immediately instead, so a
165
+ // teardown registered from deferred work (a queued onMount) still fires.
166
+ if (G.owner.disposed) {
167
+ fn();
168
+ return;
169
+ }
170
+ (G.owner.cleanups ??= []).push(fn);
171
+ }
172
+ // `final` distinguishes a permanent teardown (the scope is gone: mark it disposed so
173
+ // deferred work bails) from an effect's between-runs cleanup (the owner is REUSED for
174
+ // the next run, so it must not be marked disposed). Children are always torn down
175
+ // permanently, only the effect's own owner survives a re-run.
176
+ function disposeOwner(owner, final = true) {
177
+ if (owner.disposed)
178
+ return;
179
+ if (owner.owned) {
180
+ for (let i = owner.owned.length - 1; i >= 0; i--)
181
+ disposeOwner(owner.owned[i]);
182
+ owner.owned = null;
183
+ }
184
+ if (owner.cleanups) {
185
+ for (let i = owner.cleanups.length - 1; i >= 0; i--)
186
+ owner.cleanups[i]();
187
+ owner.cleanups = null;
188
+ }
189
+ if (final)
190
+ owner.disposed = true;
191
+ }
192
+ /**
193
+ * Create a root reactive scope with explicit disposal (TC39 has no ownership). Untracked: what
194
+ * runs in it belongs to it, so the computation that created it does not depend on its reads.
195
+ */
196
+ export function createRoot(fn) {
197
+ const owner = makeOwner(G.owner);
198
+ if (G.owner)
199
+ (G.owner.owned ??= []).push(owner);
200
+ const prev = G.owner;
201
+ G.owner = owner;
202
+ try {
203
+ return untrack(() => fn(() => disposeOwner(owner)));
204
+ }
205
+ finally {
206
+ G.owner = prev;
207
+ }
208
+ }
209
+ // ── Scheduler (TC39 has none): state on G (see ./state.ts) ───────────────────
210
+ export function setScheduler(fn) {
211
+ G.hostSchedule = fn ?? ((run) => void Promise.resolve().then(run));
212
+ }
213
+ function enqueue(e) {
214
+ if (e.queued)
215
+ return;
216
+ e.queued = true;
217
+ G.queue.push(e);
218
+ if (!G.scheduled && G.batchDepth === 0) {
219
+ G.scheduled = true;
220
+ G.hostSchedule(flushSync);
221
+ }
222
+ }
223
+ /** Drain all pending effects now (synchronously). */
224
+ export function flushSync() {
225
+ G.scheduled = false;
226
+ // Re-entrancy guard: an effect that writes a signal during its run re-enqueues;
227
+ // the running loop below picks that up, so a nested flushSync must be a no-op
228
+ // (otherwise the same G.queue drains twice).
229
+ if (G.flushing)
230
+ return;
231
+ G.flushing = true;
232
+ let guard = 0;
233
+ // An effect that throws must not stop the rest: those queued after it would be dropped
234
+ // with their `queued` mark still set, so nothing could ever run them again. The first
235
+ // error is rethrown once the queue is drained.
236
+ let failed = false;
237
+ let first;
238
+ try {
239
+ while (G.queue.length) {
240
+ if (++guard > 100000)
241
+ throw new Error('[fluixi] effect flush did not settle (cycle?)');
242
+ const batchOf = G.queue;
243
+ G.queue = [];
244
+ for (const e of batchOf) {
245
+ e.queued = false;
246
+ if (e.disposed)
247
+ continue;
248
+ try {
249
+ e.run();
250
+ }
251
+ catch (error) {
252
+ if (!failed) {
253
+ failed = true;
254
+ first = error;
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+ finally {
261
+ G.flushing = false;
262
+ }
263
+ if (failed)
264
+ throw first;
265
+ }
266
+ /**
267
+ * Run pending effects after a write completes, but only at the top level (not
268
+ * inside a batch). A single write thus marks the whole graph dirty first, then
269
+ * flushes once (legacy-equivalent synchronous effects, glitch-free). The `G.flushing`
270
+ * guard makes a write inside an effect a no-op here (the running loop drains it).
271
+ */
272
+ export function afterWrite() {
273
+ if (G.batchDepth === 0)
274
+ flushSync();
275
+ }
276
+ /** Group writes so dependent effects flush once, at the end. */
277
+ export function batch(fn) {
278
+ G.batchDepth++;
279
+ try {
280
+ return fn();
281
+ }
282
+ finally {
283
+ if (--G.batchDepth === 0)
284
+ flushSync();
285
+ }
286
+ }
287
+ // ── effect(): built on Computed + Watcher + Owner ───────────────────────────
288
+ class EffectNode {
289
+ computed;
290
+ watcher;
291
+ owner;
292
+ queued = false;
293
+ disposed = false;
294
+ constructor(fn, report = false, name) {
295
+ this.owner = makeOwner(G.owner);
296
+ // Tie disposal to the creating scope, when the parent owner tears down it must
297
+ // fully dispose this effect (unwatch + body cleanups), not just run the body
298
+ // cleanups. Register dispose() as a parent cleanup rather than as an owned tree.
299
+ if (G.owner)
300
+ (G.owner.cleanups ??= []).push(() => this.dispose());
301
+ // The Computed runs the user fn inside a tracking scope + this effect's owner,
302
+ // so reads subscribe and onCleanup/nested roots attach here.
303
+ this.computed = new Computed(() => {
304
+ // teardown previous run's cleanups before re-running. not final: the owner is
305
+ // reused for this run, so it must stay alive (only its children are disposed).
306
+ disposeOwner(this.owner, false);
307
+ runWithOwner(this.owner, () => {
308
+ const r = fn();
309
+ if (typeof r === 'function')
310
+ (this.owner.cleanups ??= []).push(r);
311
+ });
312
+ });
313
+ // Before the first run, so a watcher counts that run like every later one. A render
314
+ // effect is reported too, marked as plumbing: it is not worth drawing, but a node made
315
+ // inside one has to be able to find its way back to the component that caused it.
316
+ reportReactiveNode({ kind: 'effect', node: this.computed, handle: this, internal: !report, ...(name ? { name } : {}) });
317
+ // The watcher only schedules; the recompute happens on flush.
318
+ this.watcher = new Watcher(() => enqueue(this));
319
+ this.run(); // initial synchronous run (Solid semantics)
320
+ this.watcher.watch(this.computed);
321
+ }
322
+ run() {
323
+ if (this.disposed)
324
+ return;
325
+ this.watcher.getPending(); // clear pending; reading re-arms the watcher
326
+ try {
327
+ this.computed.get(); // recompute if dirty (re-runs the effect body)
328
+ }
329
+ catch (e) {
330
+ // A suspended read (createResource while pending) throws its promise. Report
331
+ // it to the nearest Suspense boundary; the effect re-runs via normal
332
+ // reactivity once the resource's state signal changes. Other errors propagate.
333
+ if (e && typeof e.then === 'function' && G.onSuspend) {
334
+ G.onSuspend(e, this.owner);
335
+ return;
336
+ }
337
+ // Route a real error to the nearest error boundary; rethrow if none handled it.
338
+ if (G.onError && G.onError(e, this.owner))
339
+ return;
340
+ throw e;
341
+ }
342
+ }
343
+ dispose() {
344
+ if (this.disposed)
345
+ return;
346
+ this.disposed = true;
347
+ reportReactiveDispose(this.computed);
348
+ this.watcher.unwatch(this.computed);
349
+ disposeOwner(this.owner);
350
+ }
351
+ }
352
+ /** A re-running side effect. Runs once now, then again (G.scheduled) when deps change. */
353
+ export function effect(fn, report = false, name) {
354
+ // Reported only when the caller says so. Every DOM binding is a render effect through this
355
+ // same factory, and a graph holding one node per text node is not a graph anyone reads.
356
+ const node = new EffectNode(fn, report, name);
357
+ return () => node.dispose();
358
+ }