bireactive 0.2.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 (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/dist/animation/anim.d.ts +57 -0
  4. package/dist/animation/anim.js +318 -0
  5. package/dist/animation/combinators.d.ts +39 -0
  6. package/dist/animation/combinators.js +113 -0
  7. package/dist/animation/easings.d.ts +5 -0
  8. package/dist/animation/easings.js +5 -0
  9. package/dist/animation/index.d.ts +3 -0
  10. package/dist/animation/index.js +3 -0
  11. package/dist/assert/algebra.d.ts +20 -0
  12. package/dist/assert/algebra.js +79 -0
  13. package/dist/assert/claim.d.ts +40 -0
  14. package/dist/assert/claim.js +129 -0
  15. package/dist/assert/index.d.ts +7 -0
  16. package/dist/assert/index.js +19 -0
  17. package/dist/assert/predicates.d.ts +18 -0
  18. package/dist/assert/predicates.js +43 -0
  19. package/dist/assert/record.d.ts +20 -0
  20. package/dist/assert/record.js +78 -0
  21. package/dist/assert/scope.d.ts +42 -0
  22. package/dist/assert/scope.js +233 -0
  23. package/dist/assert/span.d.ts +37 -0
  24. package/dist/assert/span.js +68 -0
  25. package/dist/assert/tree.d.ts +22 -0
  26. package/dist/assert/tree.js +65 -0
  27. package/dist/code/code.d.ts +70 -0
  28. package/dist/code/code.js +361 -0
  29. package/dist/code/index.d.ts +2 -0
  30. package/dist/code/index.js +9 -0
  31. package/dist/code/morph.d.ts +5 -0
  32. package/dist/code/morph.js +194 -0
  33. package/dist/code/tokenize.d.ts +8 -0
  34. package/dist/code/tokenize.js +51 -0
  35. package/dist/constraints/cluster.d.ts +83 -0
  36. package/dist/constraints/cluster.js +213 -0
  37. package/dist/constraints/drivers.d.ts +15 -0
  38. package/dist/constraints/drivers.js +40 -0
  39. package/dist/constraints/factories.d.ts +73 -0
  40. package/dist/constraints/factories.js +248 -0
  41. package/dist/constraints/index.d.ts +11 -0
  42. package/dist/constraints/index.js +39 -0
  43. package/dist/constraints/interaction.d.ts +21 -0
  44. package/dist/constraints/interaction.js +148 -0
  45. package/dist/constraints/linalg.d.ts +18 -0
  46. package/dist/constraints/linalg.js +141 -0
  47. package/dist/constraints/phases.d.ts +21 -0
  48. package/dist/constraints/phases.js +60 -0
  49. package/dist/constraints/physics.d.ts +34 -0
  50. package/dist/constraints/physics.js +128 -0
  51. package/dist/constraints/rigid.d.ts +210 -0
  52. package/dist/constraints/rigid.js +835 -0
  53. package/dist/constraints/solver.d.ts +107 -0
  54. package/dist/constraints/solver.js +510 -0
  55. package/dist/constraints/term.d.ts +50 -0
  56. package/dist/constraints/term.js +80 -0
  57. package/dist/constraints/terms.d.ts +80 -0
  58. package/dist/constraints/terms.js +302 -0
  59. package/dist/constraints/world.d.ts +31 -0
  60. package/dist/constraints/world.js +245 -0
  61. package/dist/core/aggregates.d.ts +64 -0
  62. package/dist/core/aggregates.js +198 -0
  63. package/dist/core/anim.d.ts +84 -0
  64. package/dist/core/anim.js +301 -0
  65. package/dist/core/index.d.ts +38 -0
  66. package/dist/core/index.js +38 -0
  67. package/dist/core/introspect.d.ts +5 -0
  68. package/dist/core/introspect.js +31 -0
  69. package/dist/core/lenses/closed-form-policies.d.ts +64 -0
  70. package/dist/core/lenses/closed-form-policies.js +452 -0
  71. package/dist/core/lenses/domain-aggregates.d.ts +54 -0
  72. package/dist/core/lenses/domain-aggregates.js +259 -0
  73. package/dist/core/lenses/factor-lens.d.ts +42 -0
  74. package/dist/core/lenses/factor-lens.js +419 -0
  75. package/dist/core/lenses/index.d.ts +5 -0
  76. package/dist/core/lenses/index.js +16 -0
  77. package/dist/core/lenses/memory.d.ts +47 -0
  78. package/dist/core/lenses/memory.js +102 -0
  79. package/dist/core/lenses/typed-factor.d.ts +45 -0
  80. package/dist/core/lenses/typed-factor.js +376 -0
  81. package/dist/core/network-utils.d.ts +14 -0
  82. package/dist/core/network-utils.js +62 -0
  83. package/dist/core/new-primitives.d.ts +33 -0
  84. package/dist/core/new-primitives.js +113 -0
  85. package/dist/core/signal.d.ts +254 -0
  86. package/dist/core/signal.js +1349 -0
  87. package/dist/core/traits.d.ts +61 -0
  88. package/dist/core/traits.js +56 -0
  89. package/dist/core/tree.d.ts +23 -0
  90. package/dist/core/tree.js +62 -0
  91. package/dist/core/values/anchor.d.ts +23 -0
  92. package/dist/core/values/anchor.js +23 -0
  93. package/dist/core/values/audio.d.ts +33 -0
  94. package/dist/core/values/audio.js +107 -0
  95. package/dist/core/values/bool.d.ts +37 -0
  96. package/dist/core/values/bool.js +75 -0
  97. package/dist/core/values/box.d.ts +77 -0
  98. package/dist/core/values/box.js +211 -0
  99. package/dist/core/values/canvas.d.ts +71 -0
  100. package/dist/core/values/canvas.js +495 -0
  101. package/dist/core/values/color.d.ts +49 -0
  102. package/dist/core/values/color.js +106 -0
  103. package/dist/core/values/flags.d.ts +18 -0
  104. package/dist/core/values/flags.js +50 -0
  105. package/dist/core/values/gpu.d.ts +74 -0
  106. package/dist/core/values/gpu.js +426 -0
  107. package/dist/core/values/matrix.d.ts +53 -0
  108. package/dist/core/values/matrix.js +140 -0
  109. package/dist/core/values/num.d.ts +62 -0
  110. package/dist/core/values/num.js +166 -0
  111. package/dist/core/values/pose.d.ts +31 -0
  112. package/dist/core/values/pose.js +83 -0
  113. package/dist/core/values/range.d.ts +83 -0
  114. package/dist/core/values/range.js +167 -0
  115. package/dist/core/values/str.d.ts +76 -0
  116. package/dist/core/values/str.js +346 -0
  117. package/dist/core/values/template.d.ts +49 -0
  118. package/dist/core/values/template.js +148 -0
  119. package/dist/core/values/transform.d.ts +49 -0
  120. package/dist/core/values/transform.js +115 -0
  121. package/dist/core/values/tri.d.ts +31 -0
  122. package/dist/core/values/tri.js +95 -0
  123. package/dist/core/values/vec.d.ts +72 -0
  124. package/dist/core/values/vec.js +219 -0
  125. package/dist/core/writable.d.ts +15 -0
  126. package/dist/core/writable.js +29 -0
  127. package/dist/ext/events.d.ts +10 -0
  128. package/dist/ext/events.js +31 -0
  129. package/dist/ext/index.d.ts +4 -0
  130. package/dist/ext/index.js +4 -0
  131. package/dist/ext/snapshot.d.ts +8 -0
  132. package/dist/ext/snapshot.js +29 -0
  133. package/dist/ext/timeline.d.ts +56 -0
  134. package/dist/ext/timeline.js +94 -0
  135. package/dist/ext/waapi.d.ts +25 -0
  136. package/dist/ext/waapi.js +198 -0
  137. package/dist/index.d.ts +8 -0
  138. package/dist/index.js +10 -0
  139. package/dist/propagators/index.d.ts +6 -0
  140. package/dist/propagators/index.js +6 -0
  141. package/dist/propagators/layout.d.ts +68 -0
  142. package/dist/propagators/layout.js +336 -0
  143. package/dist/propagators/network.d.ts +52 -0
  144. package/dist/propagators/network.js +185 -0
  145. package/dist/propagators/propagator.d.ts +12 -0
  146. package/dist/propagators/propagator.js +16 -0
  147. package/dist/propagators/range.d.ts +45 -0
  148. package/dist/propagators/range.js +147 -0
  149. package/dist/propagators/relations.d.ts +60 -0
  150. package/dist/propagators/relations.js +343 -0
  151. package/dist/shapes/annular-sector.d.ts +15 -0
  152. package/dist/shapes/annular-sector.js +64 -0
  153. package/dist/shapes/button.d.ts +14 -0
  154. package/dist/shapes/button.js +31 -0
  155. package/dist/shapes/choreographers.d.ts +22 -0
  156. package/dist/shapes/choreographers.js +69 -0
  157. package/dist/shapes/circle.d.ts +17 -0
  158. package/dist/shapes/circle.js +57 -0
  159. package/dist/shapes/clip.d.ts +5 -0
  160. package/dist/shapes/clip.js +31 -0
  161. package/dist/shapes/connect.d.ts +16 -0
  162. package/dist/shapes/connect.js +70 -0
  163. package/dist/shapes/curve.d.ts +60 -0
  164. package/dist/shapes/curve.js +285 -0
  165. package/dist/shapes/dashed.d.ts +16 -0
  166. package/dist/shapes/dashed.js +142 -0
  167. package/dist/shapes/debug.d.ts +43 -0
  168. package/dist/shapes/debug.js +97 -0
  169. package/dist/shapes/group.d.ts +5 -0
  170. package/dist/shapes/group.js +10 -0
  171. package/dist/shapes/handle.d.ts +32 -0
  172. package/dist/shapes/handle.js +88 -0
  173. package/dist/shapes/index.d.ts +23 -0
  174. package/dist/shapes/index.js +23 -0
  175. package/dist/shapes/interaction.d.ts +32 -0
  176. package/dist/shapes/interaction.js +187 -0
  177. package/dist/shapes/label.d.ts +20 -0
  178. package/dist/shapes/label.js +42 -0
  179. package/dist/shapes/layout.d.ts +29 -0
  180. package/dist/shapes/layout.js +74 -0
  181. package/dist/shapes/line.d.ts +21 -0
  182. package/dist/shapes/line.js +79 -0
  183. package/dist/shapes/list.d.ts +18 -0
  184. package/dist/shapes/list.js +51 -0
  185. package/dist/shapes/mount.d.ts +7 -0
  186. package/dist/shapes/mount.js +10 -0
  187. package/dist/shapes/path.d.ts +77 -0
  188. package/dist/shapes/path.js +227 -0
  189. package/dist/shapes/rect.d.ts +30 -0
  190. package/dist/shapes/rect.js +131 -0
  191. package/dist/shapes/shape.d.ts +132 -0
  192. package/dist/shapes/shape.js +306 -0
  193. package/dist/shapes/text.d.ts +24 -0
  194. package/dist/shapes/text.js +53 -0
  195. package/dist/shapes/tokens.d.ts +28 -0
  196. package/dist/shapes/tokens.js +27 -0
  197. package/dist/shapes/transitions.d.ts +23 -0
  198. package/dist/shapes/transitions.js +62 -0
  199. package/dist/tex/decorations.d.ts +26 -0
  200. package/dist/tex/decorations.js +116 -0
  201. package/dist/tex/index.d.ts +5 -0
  202. package/dist/tex/index.js +5 -0
  203. package/dist/tex/marker.d.ts +17 -0
  204. package/dist/tex/marker.js +63 -0
  205. package/dist/tex/motion.d.ts +43 -0
  206. package/dist/tex/motion.js +290 -0
  207. package/dist/tex/parts.d.ts +65 -0
  208. package/dist/tex/parts.js +149 -0
  209. package/dist/tex/tex.d.ts +45 -0
  210. package/dist/tex/tex.js +244 -0
  211. package/dist/web/attr.d.ts +16 -0
  212. package/dist/web/attr.js +98 -0
  213. package/dist/web/diagram.d.ts +49 -0
  214. package/dist/web/diagram.js +260 -0
  215. package/dist/web/index.d.ts +6 -0
  216. package/dist/web/index.js +6 -0
  217. package/dist/web/md-marker.d.ts +6 -0
  218. package/dist/web/md-marker.js +39 -0
  219. package/dist/web/md-tex.d.ts +6 -0
  220. package/dist/web/md-tex.js +61 -0
  221. package/dist/web/raf.d.ts +6 -0
  222. package/dist/web/raf.js +24 -0
  223. package/dist/web/viewport.d.ts +7 -0
  224. package/dist/web/viewport.js +13 -0
  225. package/package.json +87 -0
@@ -0,0 +1,1349 @@
1
+ // signal.ts — symmetric bidirectional reactive engine.
2
+ //
3
+ // Forward propagation is alien-signals verbatim (link/propagate/
4
+ // checkDirty/shallowPropagate, Dirty/Pending/Recursed flags, lazy pull).
5
+ // Backward is not a second engine: a write "compiles" a view-edit into
6
+ // source-edits by walking up `_bwdParent`, applying each lens's `put` to
7
+ // compute what the source(s) must become, committing via the SAME
8
+ // forward write path. So views are never sticky (a view is always
9
+ // `get(source)`; lossy lenses snap), no-op deltas short-circuit for free
10
+ // via equality, and backward cost ≤ forward cost.
11
+ //
12
+ // Duals:
13
+ // * merge — N→1 backward aggregation (dual of computed's N→1 forward).
14
+ // Contributions land in a slot map keyed by contributor identity,
15
+ // fold via a user policy, reset per settle.
16
+ // * multi-parent lens — a write that SPLITS across N parents
17
+ // (`_put(target)` → per-parent update array, `propagateSplit`); the
18
+ // dual of a getter reading N parents. Covers coupled writables
19
+ // (N→M, e.g. mean/diff). Info the source can't hold lives in a
20
+ // stateful-lens complement, not a bespoke engine kind.
21
+ //
22
+ // Core asymmetry: forward deps are IMPLICIT (auto-tracked reads of
23
+ // `.value` under `activeSub`); backward targets are EXPLICIT (declared
24
+ // at construction in `_bwdParent`). Hence no `activeBwdWrite` global.
25
+ //
26
+ // Mode table — a cell's role is fully determined by which fields are set:
27
+ // source getter undefined (truth in currentValue)
28
+ // computed getter, no _bwd (read-only derived)
29
+ // lens 1→1 getter + _bwd{ put, parent: Cell }
30
+ // multi-out getter + _bwd{ put, parent: Cell[] } (1→N / N→M bwd)
31
+ // merge getter + _bwd{ merge } (N→1 backward fold)
32
+ // stateful getter + _bwd{ put, parent, stateful } (complement-carrying)
33
+ // A cell is writable iff `_bwd !== undefined` (the backward sidecar; see
34
+ // `BwdSpec`). `pendingValue` is dual-keyed: a staged forward write for a
35
+ // source, a deferred backward target for a getter cell (never both).
36
+ //
37
+ // Batching: outside a batch a write propagates backward eagerly and
38
+ // flushes (alien's synchronous per-write semantics). Inside batch/flush,
39
+ // lens writes deposit their latest value and queue (last-write-wins via
40
+ // `_queueIdx`) and merge folds defer until all contributors land; the
41
+ // flush loop alternates bwd-drain / effect-drain to a fixpoint.
42
+ // Flag bits (alien-signals v2).
43
+ const F = {
44
+ None: 0,
45
+ Mutable: 1,
46
+ Watching: 2,
47
+ RecursedCheck: 4,
48
+ Recursed: 8,
49
+ Dirty: 16,
50
+ Pending: 32,
51
+ /** Backward-only: cell has a pending backward contribution queued. */
52
+ BwdQueued: 64,
53
+ };
54
+ let cycle = 0;
55
+ let runDepth = 0;
56
+ let batchDepth = 0;
57
+ let notifyIndex = 0;
58
+ let queuedLength = 0;
59
+ let activeSub;
60
+ let flushing = false;
61
+ /** Network running its body, if any. Source writes self-exclude it so a
62
+ * network reading+writing a cell doesn't re-trigger itself. */
63
+ let activeNetwork;
64
+ const queued = [];
65
+ const EMPTY_DIRTY = new Set();
66
+ /** Backward worklist: lens cells with deferred writes, merge cells
67
+ * awaiting fold. Drained to a fixpoint with effects by flush. */
68
+ const bwdQueue = [];
69
+ // Fires on every SOURCE value-change (the one place truth mutates).
70
+ // Backward writes reach it via `_writeSource`, attributing lens edits to
71
+ // the source they resolve to.
72
+ let writeHook;
73
+ /** Install a hook fired on every source value-change; returns a restore fn. */
74
+ export function setCellWriteHook(fn) {
75
+ const prev = writeHook;
76
+ writeHook = fn;
77
+ return () => {
78
+ writeHook = prev;
79
+ };
80
+ }
81
+ // alien-signals algorithm (verbatim): link / unlink / propagate / checkDirty.
82
+ function link(dep, sub, version) {
83
+ const prevDep = sub.depsTail;
84
+ if (prevDep !== undefined && prevDep.dep === dep)
85
+ return;
86
+ const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps;
87
+ if (nextDep !== undefined && nextDep.dep === dep) {
88
+ nextDep.version = version;
89
+ sub.depsTail = nextDep;
90
+ return;
91
+ }
92
+ const prevSub = dep.subsTail;
93
+ if (prevSub !== undefined && prevSub.version === version && prevSub.sub === sub)
94
+ return;
95
+ const isFirstSub = dep.subs === undefined;
96
+ const newLink = (sub.depsTail =
97
+ dep.subsTail =
98
+ {
99
+ version,
100
+ dep,
101
+ sub,
102
+ prevDep,
103
+ nextDep,
104
+ prevSub,
105
+ nextSub: undefined,
106
+ });
107
+ if (nextDep !== undefined)
108
+ nextDep.prevDep = newLink;
109
+ if (prevDep !== undefined)
110
+ prevDep.nextDep = newLink;
111
+ else
112
+ sub.deps = newLink;
113
+ if (prevSub !== undefined)
114
+ prevSub.nextSub = newLink;
115
+ else
116
+ dep.subs = newLink;
117
+ // First-subscriber lifecycle hook (dual: last-sub in `_unwatched`).
118
+ if (isFirstSub && dep instanceof Cell) {
119
+ const hook = dep._watched;
120
+ if (hook !== undefined)
121
+ hook.call(dep);
122
+ }
123
+ }
124
+ function unlink(l, sub = l.sub) {
125
+ const { dep, prevDep, nextDep, nextSub, prevSub } = l;
126
+ if (nextDep !== undefined)
127
+ nextDep.prevDep = prevDep;
128
+ else
129
+ sub.depsTail = prevDep;
130
+ if (prevDep !== undefined)
131
+ prevDep.nextDep = nextDep;
132
+ else
133
+ sub.deps = nextDep;
134
+ if (nextSub !== undefined)
135
+ nextSub.prevSub = prevSub;
136
+ else
137
+ dep.subsTail = prevSub;
138
+ if (prevSub !== undefined)
139
+ prevSub.nextSub = nextSub;
140
+ else if ((dep.subs = nextSub) === undefined)
141
+ dep._unwatched();
142
+ return nextDep;
143
+ }
144
+ function propagate(start, innerWrite, excluding) {
145
+ let l = start;
146
+ let next = start.nextSub;
147
+ let stack;
148
+ top: do {
149
+ const sub = l.sub;
150
+ // `excluding` skips one subscriber (used by `network()` so a body
151
+ // writing a cell it subscribes to doesn't re-trigger itself).
152
+ if (sub !== excluding) {
153
+ let flags = sub.flags;
154
+ if (!(flags & (F.RecursedCheck | F.Recursed | F.Dirty | F.Pending))) {
155
+ sub.flags = flags | F.Pending;
156
+ if (innerWrite)
157
+ sub.flags |= F.Recursed;
158
+ }
159
+ else if (!(flags & (F.RecursedCheck | F.Recursed))) {
160
+ flags = F.None;
161
+ }
162
+ else if (!(flags & F.RecursedCheck)) {
163
+ sub.flags = (flags & ~F.Recursed) | F.Pending;
164
+ }
165
+ else if (!(flags & (F.Dirty | F.Pending)) && isValidLink(l, sub)) {
166
+ sub.flags = flags | (F.Recursed | F.Pending);
167
+ flags &= F.Mutable;
168
+ }
169
+ else {
170
+ flags = F.None;
171
+ }
172
+ if (flags & F.Watching)
173
+ sub._notify();
174
+ if (flags & F.Mutable) {
175
+ const subSubs = sub.subs;
176
+ if (subSubs !== undefined) {
177
+ const nextSub = (l = subSubs).nextSub;
178
+ if (nextSub !== undefined) {
179
+ stack = { value: next, prev: stack };
180
+ next = nextSub;
181
+ }
182
+ continue;
183
+ }
184
+ }
185
+ }
186
+ if ((l = next) !== undefined) {
187
+ next = l.nextSub;
188
+ continue;
189
+ }
190
+ while (stack !== undefined) {
191
+ l = stack.value;
192
+ stack = stack.prev;
193
+ if (l !== undefined) {
194
+ next = l.nextSub;
195
+ continue top;
196
+ }
197
+ }
198
+ break;
199
+ } while (true);
200
+ }
201
+ function checkDirty(startLink, startSub) {
202
+ let l = startLink, sub = startSub;
203
+ let stack;
204
+ let checkDepth = 0, dirty = false;
205
+ top: do {
206
+ const dep = l.dep;
207
+ const flags = dep.flags;
208
+ if (sub.flags & F.Dirty)
209
+ dirty = true;
210
+ else if ((flags & (F.Mutable | F.Dirty)) === (F.Mutable | F.Dirty)) {
211
+ const subs = dep.subs;
212
+ if (dep._update()) {
213
+ if (subs.nextSub !== undefined)
214
+ shallowPropagate(subs);
215
+ dirty = true;
216
+ }
217
+ }
218
+ else if ((flags & (F.Mutable | F.Pending)) === (F.Mutable | F.Pending)) {
219
+ stack = { value: l, prev: stack };
220
+ l = dep.deps;
221
+ sub = dep;
222
+ ++checkDepth;
223
+ continue;
224
+ }
225
+ if (!dirty) {
226
+ const nextDep = l.nextDep;
227
+ if (nextDep !== undefined) {
228
+ l = nextDep;
229
+ continue;
230
+ }
231
+ }
232
+ while (checkDepth--) {
233
+ l = stack.value;
234
+ stack = stack.prev;
235
+ if (dirty) {
236
+ const subs = sub.subs;
237
+ if (sub._update()) {
238
+ if (subs.nextSub !== undefined)
239
+ shallowPropagate(subs);
240
+ sub = l.sub;
241
+ continue;
242
+ }
243
+ dirty = false;
244
+ }
245
+ else {
246
+ sub.flags &= ~F.Pending;
247
+ }
248
+ sub = l.sub;
249
+ const nextDep = l.nextDep;
250
+ if (nextDep !== undefined) {
251
+ l = nextDep;
252
+ continue top;
253
+ }
254
+ }
255
+ return dirty && !!sub.flags;
256
+ } while (true);
257
+ }
258
+ function shallowPropagate(l) {
259
+ do {
260
+ const sub = l.sub;
261
+ const flags = sub.flags;
262
+ if ((flags & (F.Pending | F.Dirty)) === F.Pending) {
263
+ sub.flags = flags | F.Dirty;
264
+ if ((flags & (F.Watching | F.RecursedCheck)) === F.Watching)
265
+ sub._notify();
266
+ }
267
+ } while ((l = l.nextSub) !== undefined);
268
+ }
269
+ function isValidLink(checkLink, sub) {
270
+ let l = sub.depsTail;
271
+ while (l !== undefined) {
272
+ if (l === checkLink)
273
+ return true;
274
+ l = l.prevDep;
275
+ }
276
+ return false;
277
+ }
278
+ function purgeDeps(sub) {
279
+ const depsTail = sub.depsTail;
280
+ let dep = depsTail !== undefined ? depsTail.nextDep : sub.deps;
281
+ while (dep !== undefined)
282
+ dep = unlink(dep, sub);
283
+ }
284
+ function disposeAllDepsInReverse(sub) {
285
+ let l = sub.depsTail;
286
+ while (l !== undefined) {
287
+ const prev = l.prevDep;
288
+ unlink(l, sub);
289
+ l = prev;
290
+ }
291
+ }
292
+ export const DIRECT_SLOT = Symbol("merge:direct-slot");
293
+ class MergeNode {
294
+ parent;
295
+ policy;
296
+ slots = new Map();
297
+ hasIncrementalAcc;
298
+ acc;
299
+ constructor(parent, policy) {
300
+ this.parent = parent;
301
+ this.policy = policy;
302
+ this.hasIncrementalAcc = policy.remove !== undefined;
303
+ this.acc = policy.identity;
304
+ }
305
+ receive(slot, next) {
306
+ if (this.hasIncrementalAcc) {
307
+ const remove = this.policy.remove;
308
+ const prior = this.slots.get(slot);
309
+ if (prior === undefined)
310
+ this.acc = this.policy.combine(this.acc, next);
311
+ else
312
+ this.acc = this.policy.combine(remove(this.acc, prior), next);
313
+ }
314
+ this.slots.set(slot, next);
315
+ }
316
+ fold() {
317
+ if (this.hasIncrementalAcc)
318
+ return this.acc;
319
+ let acc = this.policy.identity;
320
+ for (const v of this.slots.values())
321
+ acc = this.policy.combine(acc, v);
322
+ return acc;
323
+ }
324
+ reset() {
325
+ this.slots.clear();
326
+ this.acc = this.policy.identity;
327
+ }
328
+ }
329
+ // BwdSpec — the backward sidecar.
330
+ //
331
+ // Every cell carries the forward fields (links, getter, value cache).
332
+ // Only a WRITABLE derived cell — 1→1 lens, multi-output lens, merge,
333
+ // stateful lens, or `pin` — needs a backward target and the closures to
334
+ // drive it. Those fields live here, off a single `_bwd` pointer, rather
335
+ // than inline on `Cell`, so a source/computed stays lean: the forward hot
336
+ // path never touches them, and a plain node drops ~64 B. A cell is
337
+ // writable iff `_bwd !== undefined`.
338
+ //
339
+ // Two mode payloads hang off named fields rather than one union, so each
340
+ // stays distinctly typed: `merge` (the N→1 fold node) and `stateful` (the
341
+ // complement machinery of a complement-carrying lens). Both are rare, so
342
+ // a plain 1→1 / multi-out lens leaves them `undefined` and pays only
343
+ // `parent` + `put` + `queueIdx`.
344
+ class BwdSpec {
345
+ /** Backward target(s): one `Cell` (1→1 / merge) or `Cell[]` (multi-out). */
346
+ parent = undefined;
347
+ /** Lens `put` — backward derivation (dual of `getter`). Always called by
348
+ * the engine in 1-arg form `put(target)`; a source-reading lens bakes
349
+ * `settled(parent)` into this closure at build time. Multi-output:
350
+ * returns a per-parent update array. Stateful: the spec's `bwd`. */
351
+ // biome-ignore lint/suspicious/noExplicitAny: put fn is opaque shape
352
+ put = undefined;
353
+ /** Backward aggregation node; presence IS the merge-mode discriminant. */
354
+ merge = undefined;
355
+ /** Complement machinery; presence IS the stateful-mode discriminant. */
356
+ stateful = undefined;
357
+ /** Index in `bwdQueue` of this cell's latest push; the drain skips stale
358
+ * entries so each cell propagates backward once per flush, last-write. */
359
+ queueIdx = -1;
360
+ }
361
+ /** Runtime state of a stateful (complement-carrying) lens — the rare
362
+ * backward mode, kept off `BwdSpec` so plain lenses don't carry its slots.
363
+ * `put` (the spec's `bwd`) and `parent` stay on `BwdSpec`; this holds the
364
+ * complement and the closures that project from / advance it. */
365
+ class StatefulCore {
366
+ /** Engine-owned memory the view discards. */
367
+ complement;
368
+ /** Forward projection `fwd(sources, complement) → view`. */
369
+ // biome-ignore lint/suspicious/noExplicitAny: opaque fwd shape
370
+ fwd;
371
+ /** Advance the complement: `step(sources, complement, external)`. */
372
+ // biome-ignore lint/suspicious/noExplicitAny: opaque step shape
373
+ step;
374
+ /** Source values last written back (own-vs-external test); `undefined`
375
+ * until the first back-write. */
376
+ lastBwd = undefined;
377
+ constructor(complement,
378
+ // biome-ignore lint/suspicious/noExplicitAny: opaque fwd shape
379
+ fwd,
380
+ // biome-ignore lint/suspicious/noExplicitAny: opaque step shape
381
+ step) {
382
+ this.complement = complement;
383
+ this.fwd = fwd;
384
+ this.step = step;
385
+ }
386
+ }
387
+ /** Snapshot a `Val<T>` to plain `T` (one-shot, no tracking). */
388
+ export function readNow(v) {
389
+ if (v instanceof Cell)
390
+ return v.value;
391
+ return v;
392
+ }
393
+ /** Resolve a `Val<T>` to a `() => T` closure that unwraps on each call. */
394
+ export function reader(v) {
395
+ if (v instanceof Cell)
396
+ return () => v.value;
397
+ return () => v;
398
+ }
399
+ /** Lazy getter: computes once, installs a non-enumerable own prop under
400
+ * `key` that shadows this getter on later reads. */
401
+ export function lazy(self, key, make) {
402
+ const v = make();
403
+ Object.defineProperty(self, key, {
404
+ value: v,
405
+ writable: false,
406
+ configurable: false,
407
+ enumerable: false,
408
+ });
409
+ return v;
410
+ }
411
+ export const isCell = (v) => v instanceof Cell;
412
+ /** Lens mode: a derived cell that can be written back (has a backward sidecar). */
413
+ export const isLens = (v) => v instanceof Cell && v.getter !== undefined && v._bwd !== undefined;
414
+ /** Computed mode: derived + read-only (no backward path). */
415
+ export const isComputed = (v) => v instanceof Cell && v.getter !== undefined && v._bwd === undefined;
416
+ export class Cell {
417
+ flags = F.Mutable;
418
+ subs;
419
+ subsTail;
420
+ deps;
421
+ depsTail;
422
+ /** Forward derivation (computed/lens/merge). `undefined` ⇒ source. */
423
+ getter;
424
+ /** Per-instance equality, always defined (defaults to `Object.is` at
425
+ * construction) so hot paths call it without an `undefined` branch. */
426
+ _equals;
427
+ /** First-subscriber / last-subscriber lifecycle hooks. */
428
+ _watched;
429
+ _unwatchedHook;
430
+ /** Source: `currentValue` = committed, `pendingValue` = staged write.
431
+ * Getter cell: `currentValue` = last derived cache, `pendingValue`
432
+ * reused as the deferred backward target (see `set value`). The two
433
+ * roles never coexist, so two fields suffice for four. */
434
+ currentValue;
435
+ pendingValue;
436
+ /** Backward sidecar: target(s) + lens closures + queue slot, or
437
+ * `undefined` for a read-only cell (source or computed). Allocated only
438
+ * for writable derived cells, keeping the common node lean. Writability
439
+ * is exactly `_bwd !== undefined`. See `BwdSpec`. */
440
+ _bwd;
441
+ constructor(initial, opts) {
442
+ this.currentValue = initial;
443
+ this.pendingValue = initial;
444
+ // Pre-init every optional slot for a stable V8 hidden class across variants.
445
+ this.subs = undefined;
446
+ this.subsTail = undefined;
447
+ this.deps = undefined;
448
+ this.depsTail = undefined;
449
+ this.getter = undefined;
450
+ this._equals = Object.is;
451
+ this._watched = undefined;
452
+ this._unwatchedHook = undefined;
453
+ this._bwd = undefined;
454
+ if (opts !== undefined) {
455
+ if (opts.equals !== undefined)
456
+ this._equals = opts.equals;
457
+ if (opts.watched !== undefined)
458
+ this._watched = opts.watched;
459
+ if (opts.unwatched !== undefined)
460
+ this._unwatchedHook = opts.unwatched;
461
+ }
462
+ }
463
+ _enqueueBwd() {
464
+ this.flags |= F.BwdQueued;
465
+ this._bwd.queueIdx = bwdQueue.length;
466
+ bwdQueue.push(this);
467
+ }
468
+ /** Source write (alien's signal setter). Self-excludes the active
469
+ * network so a body writing its own dep doesn't re-trigger itself. */
470
+ _writeSource(next) {
471
+ const prev = this.pendingValue;
472
+ this.pendingValue = next;
473
+ if (!this._equals(prev, next)) {
474
+ this.flags = F.Mutable | F.Dirty;
475
+ if (writeHook !== undefined)
476
+ writeHook(this);
477
+ const subs = this.subs;
478
+ if (subs !== undefined)
479
+ propagate(subs, runDepth > 0, activeNetwork);
480
+ if (batchDepth === 0 && !flushing && subs !== undefined)
481
+ flush();
482
+ }
483
+ }
484
+ _update() {
485
+ if (this.getter !== undefined) {
486
+ // Computed/lens/merge: re-run the forward derivation.
487
+ this.depsTail = undefined;
488
+ this.flags = F.Mutable | F.RecursedCheck;
489
+ const prev = activeSub;
490
+ activeSub = this;
491
+ let threw = true;
492
+ try {
493
+ ++cycle;
494
+ const old = this.currentValue;
495
+ const next = (this.currentValue = this.getter());
496
+ threw = false;
497
+ return !this._equals(old, next);
498
+ }
499
+ finally {
500
+ activeSub = prev;
501
+ this.flags = threw ? F.Mutable | F.Dirty : this.flags & ~F.RecursedCheck;
502
+ purgeDeps(this);
503
+ }
504
+ }
505
+ this.flags = F.Mutable;
506
+ const prevV = this.currentValue;
507
+ this.currentValue = this.pendingValue;
508
+ return !this._equals(prevV, this.currentValue);
509
+ }
510
+ _notify() { }
511
+ _unwatched() {
512
+ if (this.getter !== undefined && this.depsTail !== undefined) {
513
+ this.flags = F.Mutable | F.Dirty;
514
+ disposeAllDepsInReverse(this);
515
+ return;
516
+ }
517
+ if (this._unwatchedHook !== undefined)
518
+ this._unwatchedHook();
519
+ }
520
+ peek() {
521
+ const prev = activeSub;
522
+ activeSub = undefined;
523
+ try {
524
+ return this.value;
525
+ }
526
+ finally {
527
+ activeSub = prev;
528
+ }
529
+ }
530
+ /** Guard: silent coercion to string/number is almost always a bug. */
531
+ [Symbol.toPrimitive](hint) {
532
+ throw new TypeError(`Cell cannot be coerced to ${hint} — use \`.value\``);
533
+ }
534
+ // Construction helpers build via `new this()` so a subclass static
535
+ // (`Vec.lens(...)`) yields a `Vec` with its constructor-set equality.
536
+ // Every lens has a structural backward target (`_bwd.parent`), which is
537
+ // what makes the backward pass well-defined.
538
+ /** Endomorphic lens. A 2-arg `bwd(view, current)` consults the current
539
+ * source; a 1-arg `bwd(view)` reconstructs it from the view alone. */
540
+ lens(fwd, bwd) {
541
+ return buildLens1(this.constructor, this, fwd, bwd, bwd.length >= 2);
542
+ }
543
+ /** Backward-aggregating node — bwd dual of computed. Forward, the
544
+ * identity view of its parent; backward, folds contributions from
545
+ * upstream lenses (slot-keyed) and direct writes (DIRECT_SLOT). */
546
+ merge(policy) {
547
+ if (this.getter !== undefined && this._bwd === undefined) {
548
+ throw new TypeError("merge: receiver is read-only");
549
+ }
550
+ const parent = this;
551
+ const cell = new this.constructor();
552
+ cell.flags = F.Mutable | F.Dirty;
553
+ cell.getter = () => parent.value;
554
+ const b = (cell._bwd = new BwdSpec());
555
+ b.parent = parent;
556
+ b.merge = new MergeNode(parent, policy);
557
+ return cell;
558
+ }
559
+ // biome-ignore lint/suspicious/noExplicitAny: dispatch
560
+ static derive(...args) {
561
+ if (args.length === 1)
562
+ return buildComputed(this, args[0]);
563
+ const [parent, fn] = args;
564
+ if (Array.isArray(parent))
565
+ return buildLensN(this, parent, fn, undefined, false);
566
+ return buildComputed(this, () => fn(parent.value));
567
+ }
568
+ // biome-ignore lint/suspicious/noExplicitAny: dispatch
569
+ static lens(...args) {
570
+ const [parent, a, b] = args;
571
+ if (args.length === 2)
572
+ return buildStateful(this, Array.isArray(parent) ? parent : [parent], a);
573
+ const readsSource = b.length >= 2;
574
+ if (Array.isArray(parent))
575
+ return buildLensN(this, parent, a, b, readsSource);
576
+ return buildLens1(this, parent, a, b, readsSource);
577
+ }
578
+ /** Type predicate against this class: `Vec.is(x)` narrows `x` to `Vec`.
579
+ * Inherited static; works for any subclass via polymorphic `this`. */
580
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
581
+ static is(v) {
582
+ return v instanceof this;
583
+ }
584
+ /** Lift `Val<Inner<Cls>>` → `Cls`: instance → identity, RO cell →
585
+ * tracked `derive`, literal → fresh seed. */
586
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
587
+ static from(v) {
588
+ if (v instanceof this)
589
+ return v;
590
+ if (v instanceof Cell) {
591
+ // biome-ignore lint/suspicious/noExplicitAny: dispatch
592
+ return this.derive(() => readNow(v));
593
+ }
594
+ return new this(v);
595
+ }
596
+ /** Writable-shaped constant: always reads `v`, absorbs writes
597
+ * (parentless sink lens), for APIs demanding bidirectionality. */
598
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
599
+ static pin(v) {
600
+ const cell = new this();
601
+ cell.flags = F.Mutable | F.Dirty;
602
+ cell.getter = () => v;
603
+ const b = (cell._bwd = new BwdSpec());
604
+ b.put = () => undefined; // absorb (no parent → sink)
605
+ return cell;
606
+ }
607
+ /** Typed field lens onto `parent.value[key]`. A read-only computed
608
+ * parent yields a RO derive view; any writable parent yields a
609
+ * bidirectional field lens with spread-replace `put`. */
610
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
611
+ static fieldOf(
612
+ // biome-ignore lint/suspicious/noExplicitAny: parent is contravariant on put
613
+ parent, key, Cls) {
614
+ const ctor = Cls;
615
+ const get = (s) => s[key];
616
+ // Read-only ⇔ computed: a getter with no backward sidecar.
617
+ const ro = parent.getter !== undefined && parent._bwd === undefined;
618
+ if (ro) {
619
+ return buildComputed(ctor, () => get(parent.value));
620
+ }
621
+ // Spread-replace reads the current source ⇒ source-reading (lens) form.
622
+ return buildLens1(ctor, parent, get, (v, s) => ({ ...s, [key]: v }), true);
623
+ }
624
+ }
625
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
626
+ function buildComputed(Cls, getter) {
627
+ const cell = new Cls();
628
+ cell.getter = getter;
629
+ cell.flags = F.Mutable | F.Dirty;
630
+ return cell;
631
+ }
632
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
633
+ function buildLens1(Cls, parent, fwd, bwd, readsSource) {
634
+ const cell = new Cls();
635
+ cell.flags = F.Mutable | F.Dirty;
636
+ cell.getter = (() => fwd(parent.value));
637
+ const b = (cell._bwd = new BwdSpec());
638
+ // Source-reading lenses bake the (non-committing) current source into the
639
+ // closure so the engine always calls the 1-arg form (no arity branch).
640
+ b.put = readsSource ? (t) => bwd(t, settled(parent)) : bwd;
641
+ b.parent = parent;
642
+ return cell;
643
+ }
644
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
645
+ function buildLensN(Cls, parents, fwd, bwd, readsSource) {
646
+ const n = parents.length;
647
+ const vals = new Array(n);
648
+ const cell = new Cls();
649
+ cell.flags = F.Mutable | F.Dirty;
650
+ cell.getter = (() => {
651
+ for (let i = 0; i < n; i++)
652
+ vals[i] = parents[i].value;
653
+ return fwd(vals);
654
+ });
655
+ if (bwd === undefined)
656
+ return cell; // read-only derive-N
657
+ const b = (cell._bwd = new BwdSpec());
658
+ b.parent = parents;
659
+ b.put = readsSource
660
+ ? (target) => {
661
+ for (let i = 0; i < n; i++)
662
+ vals[i] = parents[i].peek();
663
+ return bwd(target, vals);
664
+ }
665
+ : (target) => bwd(target);
666
+ return cell;
667
+ }
668
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
669
+ function buildStateful(Cls, parents,
670
+ // biome-ignore lint/suspicious/noExplicitAny: opaque spec
671
+ spec) {
672
+ const n = parents.length;
673
+ const vals = new Array(n);
674
+ const cell = new Cls();
675
+ cell.flags = F.Mutable | F.Dirty;
676
+ const b = (cell._bwd = new BwdSpec());
677
+ const seed = new Array(n);
678
+ for (let i = 0; i < n; i++)
679
+ seed[i] = parents[i].peek();
680
+ const sc = (b.stateful = new StatefulCore(spec.init(seed), spec.fwd, spec.step));
681
+ b.put = spec.bwd;
682
+ b.parent = parents;
683
+ cell.getter = (() => {
684
+ for (let i = 0; i < n; i++)
685
+ vals[i] = parents[i].value;
686
+ // External unless the live sources still equal this lens's own last
687
+ // back-write.
688
+ let external = true;
689
+ const lb = sc.lastBwd;
690
+ if (lb !== undefined) {
691
+ external = false;
692
+ for (let i = 0; i < n; i++) {
693
+ if (vals[i] !== lb[i]) {
694
+ external = true;
695
+ break;
696
+ }
697
+ }
698
+ }
699
+ sc.complement = sc.step(vals, sc.complement, external);
700
+ return sc.fwd(vals, sc.complement);
701
+ });
702
+ return cell;
703
+ }
704
+ // Install `value` on the prototype (V8 JITs it better than a class get/set).
705
+ Object.defineProperty(Cell.prototype, "value", {
706
+ get() {
707
+ const flags = this.flags;
708
+ if (this.getter !== undefined) {
709
+ if (flags & F.RecursedCheck) {
710
+ throw new RangeError(`Cyclic computed: ${this.constructor.name ?? "?"} read its own value`);
711
+ }
712
+ if (flags & F.Dirty ||
713
+ (flags & F.Pending &&
714
+ (checkDirty(this.deps, this) || ((this.flags = flags & ~F.Pending), false)))) {
715
+ if (this._update()) {
716
+ const subs = this.subs;
717
+ if (subs !== undefined)
718
+ shallowPropagate(subs);
719
+ }
720
+ }
721
+ else if (!flags) {
722
+ // First read: lazy init.
723
+ this.flags = F.Mutable | F.RecursedCheck;
724
+ const prev = activeSub;
725
+ activeSub = this;
726
+ let threw = true;
727
+ try {
728
+ this.currentValue = this.getter();
729
+ threw = false;
730
+ }
731
+ finally {
732
+ activeSub = prev;
733
+ this.flags = threw ? F.Mutable | F.Dirty : this.flags & ~F.RecursedCheck;
734
+ }
735
+ }
736
+ if (activeSub !== undefined)
737
+ link(this, activeSub, cycle);
738
+ return this.currentValue;
739
+ }
740
+ // Cell path.
741
+ if (flags & F.Dirty) {
742
+ this.flags = F.Mutable;
743
+ const prevV = this.currentValue;
744
+ this.currentValue = this.pendingValue;
745
+ if (!this._equals(prevV, this.currentValue)) {
746
+ const subs = this.subs;
747
+ if (subs !== undefined)
748
+ shallowPropagate(subs);
749
+ }
750
+ }
751
+ if (activeSub !== undefined)
752
+ link(this, activeSub, cycle);
753
+ return this.currentValue;
754
+ },
755
+ set(next) {
756
+ if (this.getter === undefined) {
757
+ this._writeSource(next);
758
+ return;
759
+ }
760
+ // Backward write. Deferred while batching/flushing so repeated writes
761
+ // coalesce (last-write-wins) and merge folds wait for all
762
+ // contributors; eager + synchronous otherwise. Exception: inside a
763
+ // network body writes are eager so the body's fixpoint loop observes
764
+ // its own edits via `peek()` between steps.
765
+ const b = this._bwd;
766
+ if (b === undefined) {
767
+ throw new TypeError("Cannot write to a computed");
768
+ }
769
+ const deferred = (batchDepth > 0 || flushing) && activeNetwork === undefined;
770
+ if (b.merge !== undefined) {
771
+ b.merge.receive(DIRECT_SLOT, next);
772
+ if (deferred)
773
+ this._enqueueBwd();
774
+ else
775
+ bwdUntracked(this, undefined, false);
776
+ return;
777
+ }
778
+ if (deferred && (Array.isArray(b.parent) || b.stateful !== undefined)) {
779
+ // Multi-parent / stateful: defer to flush so a split coalesces, a
780
+ // merge folds after all contributors land, and a complement steps
781
+ // once. Reuse `pendingValue` (unused by a getter's forward path) as
782
+ // the deferred backward target; drained by flush. Entry no-op vs the
783
+ // current view (GetPut) skips the walk.
784
+ if (this._equals(next, this.peek()))
785
+ return;
786
+ this.pendingValue = next;
787
+ this._enqueueBwd();
788
+ return;
789
+ }
790
+ // Single-parent lens: run the walk now. Inside a batch `_writeSource`
791
+ // stages the source (Dirty + pending) and defers only the flush, so
792
+ // the view reads back consistently and a later write supersedes via
793
+ // the source's pending value — last-write-wins, no queue, no lost
794
+ // revert. We must NOT peek the view here when batching: that would
795
+ // commit the source's pending value and break net-zero revert
796
+ // coalescing — `propagateBwd`'s `settled` no-op stop prunes a true
797
+ // no-op without committing. Outside a batch, the O(1) GetPut check is
798
+ // safe (no source is staged) and worth keeping.
799
+ if (!deferred && this._equals(next, this.peek()))
800
+ return;
801
+ bwdUntracked(this, next, deferred);
802
+ },
803
+ enumerable: false,
804
+ configurable: false,
805
+ });
806
+ // Backward pass (propagateBwd).
807
+ //
808
+ // Walk up `_bwdParent`, applying `put` at each lens / folding at each
809
+ // merge, until a source is committed (via the forward write path) or a
810
+ // parent merge is reached. `deferred` (inside batch/flush) stops at a
811
+ // parent merge after depositing so it folds once all contributors land;
812
+ // eager folds merges inline. Not a second engine: every path terminates
813
+ // in `_writeSource`.
814
+ // Backward evaluation runs UNTRACKED so `bwd`/`step`/`fwd` reads don't
815
+ // establish forward deps on whatever `activeSub` is writing (e.g. an
816
+ // effect that writes a lens). All backward entry points route through here.
817
+ function bwdUntracked(cell, target, deferred) {
818
+ const prev = activeSub;
819
+ activeSub = undefined;
820
+ try {
821
+ propagateBwd(cell, target, deferred);
822
+ }
823
+ finally {
824
+ activeSub = prev;
825
+ }
826
+ }
827
+ /** A cell's current value for the backward pass's internal no-op checks,
828
+ * WITHOUT side effects. A source staged earlier in this batch reads its
829
+ * pending value directly; reading it via `peek` would COMMIT the pending
830
+ * value (`_update`: currentValue = pendingValue), so a later net-zero
831
+ * revert would look like a real change and over-fire downstream. A
832
+ * non-source (lens/computed) has no such hazard and recomputes via peek. */
833
+ function settled(cell) {
834
+ return cell.getter === undefined && (cell.flags & F.Dirty) !== 0
835
+ ? cell.pendingValue
836
+ : cell.peek();
837
+ }
838
+ function propagateBwd(start, target, deferred) {
839
+ let cell = start;
840
+ let v = target;
841
+ while (true) {
842
+ // Multi-parent lens: SPLIT the write into each parent (dual of a
843
+ // getter reading N parents — the `put` yields N upstream values).
844
+ const cb = cell._bwd;
845
+ const parent = cb.parent;
846
+ if (Array.isArray(parent)) {
847
+ propagateSplit(cell, v, deferred);
848
+ return;
849
+ }
850
+ let push;
851
+ if (cb.merge !== undefined) {
852
+ const node = cb.merge;
853
+ push = node.fold();
854
+ node.reset();
855
+ }
856
+ else {
857
+ // Single-arg always: a source-reading lens baked `settled(parent)`
858
+ // into `put` at build time (see `buildLens1`); `pin` ignores `v`.
859
+ push = cb.put(v);
860
+ }
861
+ // Parentless lens (e.g. `pin`): no upstream, write absorbed. Sink.
862
+ if (parent === undefined)
863
+ return;
864
+ // Concrete no-op stop: if the parent already holds `push`, committing
865
+ // changes nothing upstream, so the walk stops. Sound for ANY topology
866
+ // (no speculation). A lossy lens hides an off-grid edit by returning
867
+ // the current source from `put`. Merge parents fold instead. `settled`
868
+ // reads a batched source's pending value WITHOUT committing it, so a
869
+ // net-zero revert leaves the source unchanged and downstream un-fired.
870
+ const pb = parent._bwd;
871
+ const parentMerge = pb !== undefined ? pb.merge : undefined;
872
+ if (parentMerge === undefined && parent._equals(push, settled(parent)))
873
+ return;
874
+ if (parentMerge !== undefined) {
875
+ parentMerge.receive(cell, push);
876
+ if (deferred) {
877
+ if (!(parent.flags & F.BwdQueued))
878
+ parent._enqueueBwd();
879
+ return;
880
+ }
881
+ cell = parent;
882
+ continue;
883
+ }
884
+ if (parent.getter === undefined) {
885
+ // Source: commit + forward-propagate (the forward write).
886
+ parent._writeSource(push);
887
+ return;
888
+ }
889
+ // Parent is a lens: keep walking, carrying its new view value.
890
+ cell = parent;
891
+ v = push;
892
+ }
893
+ }
894
+ /** Split a multi-parent cell's write across its N parents. `_put(target)`
895
+ * returns the per-parent update array (`undefined` ⇒ leave parent);
896
+ * each defined update recurses via `propagateBwd`. Eager splits coalesce
897
+ * under one flush (same guarantee as `batch()`); the coalescing lives
898
+ * here since such a cell may be reached as a write start or mid-chain. */
899
+ function propagateSplit(cell, target, deferred) {
900
+ const b = cell._bwd;
901
+ const parents = b.parent;
902
+ const n = parents.length;
903
+ // STATEFUL lens: `bwd` reads the complement and returns per-parent
904
+ // updates plus the post-write complement. We commit the stepped
905
+ // complement and fork the source updates; absorption is the lens's job
906
+ // (its `bwd` returns `undefined` updates, forked as no-ops).
907
+ const sc = b.stateful;
908
+ if (sc !== undefined) {
909
+ // Bring the complement current with the sources before the back-write
910
+ // (a source may have changed without the view being read, leaving
911
+ // `step` un-run). Untracked, so reading `.value` adds no dependency.
912
+ void cell.value;
913
+ const vals = new Array(n);
914
+ for (let i = 0; i < n; i++)
915
+ vals[i] = parents[i].peek();
916
+ const res = b.put(target, vals, sc.complement);
917
+ const updates = res.updates;
918
+ const cand = new Array(n);
919
+ let anyWrite = false;
920
+ for (let i = 0; i < n; i++) {
921
+ const u = updates[i];
922
+ if (u === undefined) {
923
+ cand[i] = vals[i];
924
+ }
925
+ else {
926
+ cand[i] = u;
927
+ anyWrite = true;
928
+ }
929
+ }
930
+ sc.complement = sc.step(cand, res.complement, false);
931
+ if (!anyWrite) {
932
+ // Complement-only change (no source moves): mark dirty for a correct next read.
933
+ cell.flags = F.Mutable | F.Dirty;
934
+ return;
935
+ }
936
+ sc.lastBwd = cand;
937
+ if (deferred) {
938
+ forkInto(parents, updates, n);
939
+ return;
940
+ }
941
+ ++batchDepth;
942
+ try {
943
+ forkInto(parents, updates, n);
944
+ }
945
+ finally {
946
+ if (!--batchDepth)
947
+ flush();
948
+ }
949
+ return;
950
+ }
951
+ const updates = b.put(target);
952
+ // No speculation: each defined update forks to its parent, where
953
+ // `_writeSource`'s equality check prunes no-op sources and the forward
954
+ // pass prunes unchanged views. Absorption ⇒ `undefined` updates.
955
+ if (deferred) {
956
+ forkInto(parents, updates, n);
957
+ return;
958
+ }
959
+ ++batchDepth;
960
+ try {
961
+ forkInto(parents, updates, n);
962
+ }
963
+ finally {
964
+ if (!--batchDepth)
965
+ flush();
966
+ }
967
+ }
968
+ /** Route each defined update to its parent: a source commits directly, a
969
+ * lens/multi-parent/merge re-enters the backward pass. Always called
970
+ * under a bumped `batchDepth` so commits coalesce into one flush. */
971
+ function forkInto(parents, updates, n) {
972
+ for (let i = 0; i < n; i++) {
973
+ const u = updates[i];
974
+ if (u === undefined)
975
+ continue;
976
+ const parent = parents[i];
977
+ if (parent.getter === undefined)
978
+ parent._writeSource(u);
979
+ else
980
+ propagateBwd(parent, u, true);
981
+ }
982
+ }
983
+ /** Writable source; passes an existing `Writable` through (idempotent). */
984
+ export function cell(initial, opts) {
985
+ if (initial instanceof Cell)
986
+ return initial;
987
+ return new Cell(initial, opts);
988
+ }
989
+ export function computed(fn) {
990
+ const cell = new Cell(undefined);
991
+ cell.flags = F.Mutable | F.Dirty;
992
+ cell.getter = fn;
993
+ return cell;
994
+ }
995
+ // Bare (untyped) factories. Construct a plain `Cell`, inferring `R`
996
+ // from the closures (the polymorphic-`this` statics are for typed
997
+ // subclasses like `Vec.lens`).
998
+ const CELL_CTOR = Cell;
999
+ // biome-ignore lint/suspicious/noExplicitAny: dispatch
1000
+ export function derive(...args) {
1001
+ if (args.length === 1)
1002
+ return buildComputed(CELL_CTOR, args[0]);
1003
+ const [parent, fn] = args;
1004
+ if (Array.isArray(parent))
1005
+ return buildLensN(CELL_CTOR, parent, fn, undefined, false);
1006
+ return buildComputed(CELL_CTOR, () => fn(parent.value));
1007
+ }
1008
+ // biome-ignore lint/suspicious/noExplicitAny: dispatch
1009
+ export function lens(...args) {
1010
+ const [parent, a, b] = args;
1011
+ if (args.length === 2) {
1012
+ return buildStateful(CELL_CTOR, Array.isArray(parent) ? parent : [parent], a);
1013
+ }
1014
+ const readsSource = b.length >= 2;
1015
+ if (Array.isArray(parent))
1016
+ return buildLensN(CELL_CTOR, parent, a, b, readsSource);
1017
+ return buildLens1(CELL_CTOR, parent, a, b, readsSource);
1018
+ }
1019
+ // Effect — alien-signals verbatim.
1020
+ class Effect {
1021
+ flags = F.Watching | F.RecursedCheck;
1022
+ subs = undefined;
1023
+ subsTail = undefined;
1024
+ deps = undefined;
1025
+ depsTail = undefined;
1026
+ fn;
1027
+ cleanup = undefined;
1028
+ constructor(fn) {
1029
+ this.fn = fn;
1030
+ const prev = activeSub;
1031
+ activeSub = this;
1032
+ try {
1033
+ ++runDepth;
1034
+ const ret = fn();
1035
+ this.cleanup = typeof ret === "function" ? ret : undefined;
1036
+ }
1037
+ finally {
1038
+ --runDepth;
1039
+ activeSub = prev;
1040
+ this.flags &= ~F.RecursedCheck;
1041
+ }
1042
+ }
1043
+ _update() {
1044
+ this.flags = F.Mutable;
1045
+ return true;
1046
+ }
1047
+ _notify() {
1048
+ let e = this;
1049
+ let insertIndex = queuedLength;
1050
+ const firstInsertedIndex = insertIndex;
1051
+ do {
1052
+ queued[insertIndex++] = e;
1053
+ e.flags &= ~F.Watching;
1054
+ const next = e.subs?.sub;
1055
+ if (next === undefined || !(next.flags & F.Watching))
1056
+ break;
1057
+ e = next;
1058
+ } while (true);
1059
+ queuedLength = insertIndex;
1060
+ let idx = insertIndex, firstIdx = firstInsertedIndex;
1061
+ while (firstIdx < --idx) {
1062
+ const left = queued[firstIdx];
1063
+ queued[firstIdx++] = queued[idx];
1064
+ queued[idx] = left;
1065
+ }
1066
+ }
1067
+ _unwatched() {
1068
+ this.flags = F.None;
1069
+ disposeAllDepsInReverse(this);
1070
+ const sub = this.subs;
1071
+ if (sub !== undefined)
1072
+ unlink(sub);
1073
+ if (this.cleanup)
1074
+ this._runCleanup();
1075
+ }
1076
+ _run() {
1077
+ const flags = this.flags;
1078
+ if (flags & F.Dirty || (flags & F.Pending && checkDirty(this.deps, this))) {
1079
+ if (this.cleanup) {
1080
+ this._runCleanup();
1081
+ if (!this.flags)
1082
+ return;
1083
+ }
1084
+ this.depsTail = undefined;
1085
+ this.flags = F.Watching | F.RecursedCheck;
1086
+ const prev = activeSub;
1087
+ activeSub = this;
1088
+ try {
1089
+ ++cycle;
1090
+ ++runDepth;
1091
+ const ret = this.fn();
1092
+ this.cleanup = typeof ret === "function" ? ret : undefined;
1093
+ }
1094
+ finally {
1095
+ --runDepth;
1096
+ activeSub = prev;
1097
+ this.flags &= ~F.RecursedCheck;
1098
+ purgeDeps(this);
1099
+ }
1100
+ }
1101
+ else if (this.deps !== undefined) {
1102
+ this.flags = F.Watching;
1103
+ }
1104
+ }
1105
+ _runCleanup() {
1106
+ const c = this.cleanup;
1107
+ this.cleanup = undefined;
1108
+ const prev = activeSub;
1109
+ activeSub = undefined;
1110
+ try {
1111
+ c();
1112
+ }
1113
+ finally {
1114
+ activeSub = prev;
1115
+ }
1116
+ }
1117
+ }
1118
+ export function effect(fn) {
1119
+ const e = new Effect(fn);
1120
+ return () => e._unwatched();
1121
+ }
1122
+ // Alternates backward-drain and effect-drain to a fixpoint: backward
1123
+ // commits sources (queuing effects); effects may write (more effects /
1124
+ // more bwd entries). Loops until both queues are exhausted.
1125
+ function flush() {
1126
+ if (flushing)
1127
+ return;
1128
+ flushing = true;
1129
+ let bwdIndex = 0;
1130
+ try {
1131
+ // Head-checked so the common already-drained case skips the body.
1132
+ while (bwdIndex < bwdQueue.length || notifyIndex < queuedLength) {
1133
+ while (bwdIndex < bwdQueue.length) {
1134
+ const cell = bwdQueue[bwdIndex];
1135
+ const cb = cell._bwd;
1136
+ if (cb.queueIdx !== bwdIndex || !(cell.flags & F.BwdQueued)) {
1137
+ bwdIndex++;
1138
+ continue;
1139
+ }
1140
+ bwdIndex++;
1141
+ cell.flags &= ~F.BwdQueued;
1142
+ if (cb.merge !== undefined) {
1143
+ bwdUntracked(cell, undefined, true);
1144
+ }
1145
+ else {
1146
+ bwdUntracked(cell, cell.pendingValue, true);
1147
+ }
1148
+ }
1149
+ while (notifyIndex < queuedLength) {
1150
+ const e = queued[notifyIndex];
1151
+ queued[notifyIndex++] = undefined;
1152
+ e._run();
1153
+ }
1154
+ }
1155
+ }
1156
+ finally {
1157
+ bwdQueue.length = 0;
1158
+ notifyIndex = 0;
1159
+ queuedLength = 0;
1160
+ flushing = false;
1161
+ }
1162
+ }
1163
+ export function batch(fn) {
1164
+ ++batchDepth;
1165
+ try {
1166
+ return fn();
1167
+ }
1168
+ finally {
1169
+ if (!--batchDepth)
1170
+ flush();
1171
+ }
1172
+ }
1173
+ export function untracked(fn) {
1174
+ const prev = activeSub;
1175
+ activeSub = undefined;
1176
+ try {
1177
+ return fn();
1178
+ }
1179
+ finally {
1180
+ activeSub = prev;
1181
+ }
1182
+ }
1183
+ class _NetworkNode {
1184
+ subs = undefined;
1185
+ subsTail = undefined;
1186
+ deps = undefined;
1187
+ depsTail = undefined;
1188
+ flags = F.Watching | F.RecursedCheck;
1189
+ body;
1190
+ manual;
1191
+ /** Per-instance last-seen dep values; used to compute `dirty`. */
1192
+ lastValues = new Map();
1193
+ pending = false;
1194
+ disposed = false;
1195
+ _ownCycle = 0;
1196
+ _depsSet = new Set();
1197
+ _handle;
1198
+ constructor(body, manual) {
1199
+ this.body = body;
1200
+ this.manual = manual;
1201
+ }
1202
+ /** Two-phase init so the body sees its own handle on the first fire. */
1203
+ _initWithHandle(handle, initialDeps) {
1204
+ this._handle = handle;
1205
+ this._linkBatch(initialDeps);
1206
+ this._runBody(EMPTY_DIRTY);
1207
+ }
1208
+ _update() {
1209
+ this.flags = F.Mutable;
1210
+ return true;
1211
+ }
1212
+ _notify() {
1213
+ if (this.manual) {
1214
+ this.pending = true;
1215
+ this.flags |= F.Watching;
1216
+ return;
1217
+ }
1218
+ queued[queuedLength++] = this;
1219
+ this.flags &= ~F.Watching;
1220
+ }
1221
+ _unwatched() {
1222
+ this.disposed = true;
1223
+ this.flags = F.None;
1224
+ disposeAllDepsInReverse(this);
1225
+ const sub = this.subs;
1226
+ if (sub !== undefined)
1227
+ unlink(sub);
1228
+ this.lastValues.clear();
1229
+ }
1230
+ _run() {
1231
+ if (this.disposed)
1232
+ return;
1233
+ const flags = this.flags;
1234
+ if (flags & F.Dirty || (flags & F.Pending && checkDirty(this.deps, this))) {
1235
+ this._runBody(this._computeDirty());
1236
+ }
1237
+ else if (this.deps !== undefined) {
1238
+ this.flags = F.Watching;
1239
+ }
1240
+ }
1241
+ _computeDirty() {
1242
+ let dirty;
1243
+ for (const [sig, lastVal] of this.lastValues) {
1244
+ if (sig.peek() !== lastVal) {
1245
+ if (dirty === undefined)
1246
+ dirty = new Set();
1247
+ dirty.add(sig);
1248
+ }
1249
+ }
1250
+ return dirty ?? EMPTY_DIRTY;
1251
+ }
1252
+ _runBody(dirty) {
1253
+ // RecursedCheck doubles as the "body running" guard (see flush()).
1254
+ this.flags = F.Watching | F.RecursedCheck;
1255
+ const prevSettler = activeNetwork;
1256
+ activeNetwork = this;
1257
+ try {
1258
+ ++cycle;
1259
+ ++runDepth;
1260
+ ++batchDepth;
1261
+ try {
1262
+ this.body(dirty, this._handle);
1263
+ }
1264
+ finally {
1265
+ if (!--batchDepth)
1266
+ flush();
1267
+ }
1268
+ }
1269
+ finally {
1270
+ --runDepth;
1271
+ activeNetwork = prevSettler;
1272
+ this.flags &= ~F.RecursedCheck;
1273
+ this.lastValues.clear();
1274
+ let l = this.deps;
1275
+ while (l !== undefined) {
1276
+ const sig = l.dep;
1277
+ this.lastValues.set(sig, sig.peek());
1278
+ l = l.nextDep;
1279
+ }
1280
+ }
1281
+ this.pending = false;
1282
+ }
1283
+ flush() {
1284
+ if (this.disposed)
1285
+ return;
1286
+ if (this.flags & F.RecursedCheck) {
1287
+ throw new Error("network: flush() called from inside body — would recurse infinitely. " +
1288
+ "Return from the body and let the next dep change drive the next fire.");
1289
+ }
1290
+ this._runBody(this._computeDirty());
1291
+ }
1292
+ subscribe(sigs) {
1293
+ if (this.disposed)
1294
+ return;
1295
+ this._linkBatch(sigs);
1296
+ }
1297
+ unsubscribe(sigs) {
1298
+ if (this.disposed)
1299
+ return;
1300
+ const set = this._depsSet;
1301
+ for (const s of sigs) {
1302
+ if (!set.has(s))
1303
+ continue;
1304
+ set.delete(s);
1305
+ let l = this.deps;
1306
+ while (l !== undefined) {
1307
+ if (l.dep === s) {
1308
+ unlink(l, this);
1309
+ break;
1310
+ }
1311
+ l = l.nextDep;
1312
+ }
1313
+ }
1314
+ }
1315
+ _linkBatch(sigs) {
1316
+ const set = this._depsSet;
1317
+ let tail = this.deps;
1318
+ if (tail !== undefined) {
1319
+ while (tail.nextDep !== undefined)
1320
+ tail = tail.nextDep;
1321
+ }
1322
+ this.depsTail = tail;
1323
+ for (const s of sigs) {
1324
+ if (set.has(s))
1325
+ continue;
1326
+ set.add(s);
1327
+ link(s, this, ++this._ownCycle);
1328
+ }
1329
+ }
1330
+ }
1331
+ /** Build a reactive sub-DAG node with explicit topology. The body fires
1332
+ * when any subscribed dep changes (`dirty` = the changed subset), runs
1333
+ * inside `batch()`, and self-excludes its own writes. Topology is the
1334
+ * deps array + later subscribe/unsubscribe (body reads add no deps).
1335
+ * `flush()` from inside the body throws; `manual: true` defers
1336
+ * auto-firing so only `flush()` advances. */
1337
+ export function network(
1338
+ // biome-ignore lint/suspicious/noExplicitAny: deps come in many flavours
1339
+ deps, body, opts) {
1340
+ const node = new _NetworkNode(body, opts?.manual ?? false);
1341
+ const handle = {
1342
+ dispose: () => node._unwatched(),
1343
+ flush: () => node.flush(),
1344
+ subscribe: (...sigs) => node.subscribe(sigs),
1345
+ unsubscribe: (...sigs) => node.unsubscribe(sigs),
1346
+ };
1347
+ node._initWithHandle(handle, deps);
1348
+ return handle;
1349
+ }