@solidjs/signals 2.0.0-rc.6 → 2.0.0-rc.8

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 (131) hide show
  1. package/dist/dev-shared.js +5243 -0
  2. package/dist/dev.attribution.js +2333 -0
  3. package/dist/dev.js +1758 -6621
  4. package/dist/observe/affects.js +127 -0
  5. package/dist/observe/attribution.js +1 -0
  6. package/dist/observe/boundaries.js +586 -0
  7. package/dist/observe/core/action.js +167 -0
  8. package/dist/observe/core/async.js +736 -0
  9. package/dist/observe/core/attribution-hooks.js +60 -0
  10. package/dist/observe/core/attribution.js +2318 -0
  11. package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +135 -71
  12. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  13. package/dist/observe/core/core.js +1289 -0
  14. package/dist/observe/core/dev.js +232 -0
  15. package/dist/observe/core/effect.js +179 -0
  16. package/dist/observe/core/error.js +80 -0
  17. package/dist/observe/core/external.js +98 -0
  18. package/dist/observe/core/graph.js +155 -0
  19. package/dist/observe/core/heap.js +147 -0
  20. package/dist/observe/core/invariants.js +43 -0
  21. package/dist/observe/core/lanes.js +168 -0
  22. package/dist/observe/core/optimistic.js +438 -0
  23. package/dist/observe/core/owner.js +308 -0
  24. package/dist/observe/core/scheduler.js +1190 -0
  25. package/dist/observe/core/verdict.js +551 -0
  26. package/dist/observe/index.js +55 -0
  27. package/dist/observe/map.js +482 -0
  28. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +305 -317
  29. package/dist/observe/store/index.js +38 -0
  30. package/dist/observe/store/next/optimistic.js +651 -0
  31. package/dist/observe/store/next/projection.js +242 -0
  32. package/dist/observe/store/next/reconcile.js +373 -0
  33. package/dist/observe/store/next/store.js +2213 -0
  34. package/dist/observe/store/next/target.js +51 -0
  35. package/dist/observe/store/store.js +323 -0
  36. package/dist/observe/store/storePath.js +103 -0
  37. package/dist/observe/store/utils.js +210 -0
  38. package/dist/prod/attribution.js +32 -0
  39. package/dist/prod/boundaries.js +3 -1
  40. package/dist/prod/core/action.js +52 -32
  41. package/dist/prod/core/async.js +177 -128
  42. package/dist/prod/core/attribution-hooks.js +3 -0
  43. package/dist/prod/core/constants.js +32 -1
  44. package/dist/prod/core/context.js +10 -16
  45. package/dist/prod/core/core.js +505 -286
  46. package/dist/prod/core/dev.js +2 -0
  47. package/dist/prod/core/effect.js +72 -43
  48. package/dist/prod/core/external.js +2 -2
  49. package/dist/prod/core/graph.js +40 -36
  50. package/dist/prod/core/heap.js +50 -51
  51. package/dist/prod/core/lanes.js +43 -22
  52. package/dist/prod/core/optimistic.js +224 -80
  53. package/dist/prod/core/owner.js +43 -39
  54. package/dist/prod/core/scheduler.js +347 -232
  55. package/dist/prod/core/verdict.js +71 -74
  56. package/dist/prod/index.js +9 -5
  57. package/dist/prod/map.js +311 -150
  58. package/dist/prod/signals.js +52 -38
  59. package/dist/prod/store/next/optimistic.js +127 -131
  60. package/dist/prod/store/next/projection.js +3 -3
  61. package/dist/prod/store/next/reconcile.js +149 -294
  62. package/dist/prod/store/next/store.js +694 -380
  63. package/dist/prod/store/next/target.js +32 -10
  64. package/dist/prod/store/store.js +12 -14
  65. package/dist/prod/store/utils.js +36 -27
  66. package/dist/types/attribution.d.ts +15 -0
  67. package/dist/types/attribution.prod.d.ts +13 -0
  68. package/dist/types/core/async.d.ts +1 -1
  69. package/dist/types/core/attribution-hooks.d.ts +183 -6
  70. package/dist/types/core/attribution.d.ts +435 -24
  71. package/dist/types/core/constants.d.ts +31 -0
  72. package/dist/types/core/core.d.ts +19 -3
  73. package/dist/types/core/dev.d.ts +165 -26
  74. package/dist/types/core/effect.d.ts +8 -1
  75. package/dist/types/core/heap.d.ts +5 -3
  76. package/dist/types/core/index.d.ts +2 -1
  77. package/dist/types/core/invariants.d.ts +1 -1
  78. package/dist/types/core/lanes.d.ts +17 -0
  79. package/dist/types/core/scheduler.d.ts +69 -4
  80. package/dist/types/core/types.d.ts +29 -9
  81. package/dist/types/index.d.ts +9 -2
  82. package/dist/types/map.d.ts +2 -0
  83. package/dist/types/signals.d.ts +10 -0
  84. package/dist/types/store/index.d.ts +4 -6
  85. package/dist/types/store/next/optimistic.d.ts +4 -2
  86. package/dist/types/store/next/reconcile.d.ts +5 -12
  87. package/dist/types/store/next/store.d.ts +8 -9
  88. package/dist/types/store/next/target.d.ts +56 -55
  89. package/dist/types/store/store.d.ts +14 -9
  90. package/dist/types/store/utils.d.ts +7 -0
  91. package/package.json +21 -16
  92. package/dist/node.cjs +0 -11121
  93. package/dist/prod/store/next/patch-hooks.js +0 -13
  94. package/dist/prod/store/next/patch.js +0 -614
  95. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  96. package/dist/types/store/next/patch.d.ts +0 -91
  97. package/dist/types-cjs/affects.d.cts +0 -47
  98. package/dist/types-cjs/boundaries.d.cts +0 -175
  99. package/dist/types-cjs/core/action.d.cts +0 -64
  100. package/dist/types-cjs/core/async.d.cts +0 -23
  101. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -63
  102. package/dist/types-cjs/core/attribution.d.cts +0 -239
  103. package/dist/types-cjs/core/core.d.cts +0 -176
  104. package/dist/types-cjs/core/dev.d.cts +0 -95
  105. package/dist/types-cjs/core/effect.d.cts +0 -30
  106. package/dist/types-cjs/core/error.d.cts +0 -56
  107. package/dist/types-cjs/core/external.d.cts +0 -15
  108. package/dist/types-cjs/core/graph.d.cts +0 -28
  109. package/dist/types-cjs/core/heap.d.cts +0 -22
  110. package/dist/types-cjs/core/index.d.cts +0 -13
  111. package/dist/types-cjs/core/invariants.d.cts +0 -59
  112. package/dist/types-cjs/core/lanes.d.cts +0 -44
  113. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  114. package/dist/types-cjs/core/owner.d.cts +0 -124
  115. package/dist/types-cjs/core/scheduler.d.cts +0 -231
  116. package/dist/types-cjs/core/types.d.cts +0 -204
  117. package/dist/types-cjs/core/verdict.d.cts +0 -2
  118. package/dist/types-cjs/index.d.cts +0 -10
  119. package/dist/types-cjs/map.d.cts +0 -69
  120. package/dist/types-cjs/package.json +0 -3
  121. package/dist/types-cjs/store/index.d.cts +0 -21
  122. package/dist/types-cjs/store/next/optimistic.d.cts +0 -23
  123. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  124. package/dist/types-cjs/store/next/patch.d.cts +0 -91
  125. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  126. package/dist/types-cjs/store/next/reconcile.d.cts +0 -17
  127. package/dist/types-cjs/store/next/store.d.cts +0 -127
  128. package/dist/types-cjs/store/next/target.d.cts +0 -192
  129. package/dist/types-cjs/store/store.d.cts +0 -138
  130. package/dist/types-cjs/store/storePath.d.cts +0 -58
  131. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,11 +1,33 @@
1
1
  /**
2
- * Ownership (first cut, decision 2026-08-16d): one WeakSet of store-owned
3
- * backings serving both the production identity-skip guard and the false
4
- * no-mutation oracle.
2
+ * Ownership stamp (#3360): every backing the store ALLOCATES (CoW clones,
3
+ * privatized committed backings) carries its owning target under this
4
+ * enumerable symbol. One property write replaces the two weak-collection
5
+ * registrations (ownership set + raw→target map) a fresh object used to pay
6
+ * per draft — V8's identity-hash + ephemeron cost dominated the one-key
7
+ * write floor. Enumerable so a spread copy (the plain-data clone path) stays
8
+ * on the fast path and carries the stamp along.
9
+ *
10
+ * Owned backings are never user-reachable (`snapshot` copies them, the traps
11
+ * hide the key), so every raw key walk in the store must skip `$OWNER`, and
12
+ * ownership is answered by `isOwned` — a user object never carries it.
13
+ * Overlay drafts (`Object.create(v)` over an owned `v`) inherit the stamp.
5
14
  */
6
- const ownedRaw = new WeakSet;
15
+ const $OWNER = Symbol(0);
7
16
 
8
- /** raw → target. The only raw-keyed lookup; boundary mechanism (O8). */ const storeNextLookup = new WeakMap;
17
+ /** raw → target for UNOWNED backings (user-ingested, adopted); owned
18
+ * backings resolve through their `$OWNER` stamp. Boundary mechanism (O8). */ const storeNextLookup = new WeakMap;
19
+
20
+ /** A backing the store allocated and may mutate in place. */ function isOwned(e) {
21
+ return e[$OWNER] !== undefined;
22
+ }
23
+
24
+ /** raw → target within a family (`null` = plain stores / the global map).
25
+ * The stamp answers for backings owned by a target OF THAT FAMILY; anything
26
+ * else (user objects, adoptees, another family's backings the family
27
+ * re-registered for its own wrapper) resolves through the family's map. */ function lookupTarget(e, o) {
28
+ const t = e[$OWNER];
29
+ return t !== undefined && t.fam === o ? t : (o?.map ?? storeNextLookup).get(e);
30
+ }
9
31
 
10
32
  function devAssertNeverUserMutation(e) {
11
33
  return;
@@ -19,11 +41,11 @@ function setOptHooks(e) {
19
41
 
20
42
  /** Sticky descendants flag walk (§6d): reconcile's keyed pruning descends
21
43
  * only where subscriptions exist at/below. Nodes AND patches count. */ function markDescendants(e) {
22
- let t = e;
23
- while (t && !t.d) {
24
- t.d = true;
25
- t = t.u;
44
+ let o = e;
45
+ while (o && !o.d) {
46
+ o.d = true;
47
+ o = o.u;
26
48
  }
27
49
  }
28
50
 
29
- export { devAssertNeverUserMutation, markDescendants, optHooks, ownedRaw, setOptHooks, storeNextLookup };
51
+ export { $OWNER, devAssertNeverUserMutation, isOwned, lookupTarget, markDescendants, optHooks, setOptHooks, storeNextLookup };
@@ -8,7 +8,7 @@ import "../core/verdict.js";
8
8
 
9
9
  import "../core/effect.js";
10
10
 
11
- import { storeNextLookup } from "./next/target.js";
11
+ import { $OWNER, lookupTarget as lookupTarget$1 } from "./next/target.js";
12
12
 
13
13
  /**
14
14
  * Brand symbols used internally by the store proxy / projection plumbing.
@@ -24,13 +24,9 @@ $AFFECTS = Symbol(0);
24
24
  const STORE_VALUE = "v", STORE_NODE = "n", STORE_HAS = "h";
25
25
 
26
26
  function lookupTarget(e, t) {
27
- // Family maps (projections/optimistic) map raw -> target; the global next
28
- // lookup maps raw -> target too. Proxies resolve through $TARGET directly.
29
- if (t !== undefined) {
30
- const o = t.get(e);
31
- if (o !== undefined) return o[$TARGET] ?? o;
32
- }
33
- return storeNextLookup.get(e);
27
+ // Family registrations (projections/optimistic) first, then the global
28
+ // next lookup. Proxies resolve through $TARGET directly.
29
+ return (t ? lookupTarget$1(e, t) : undefined) ?? lookupTarget$1(e, null);
34
30
  }
35
31
 
36
32
  // Values marked raw never acquire a proxy identity: wrap() serves them as-is
@@ -202,9 +198,9 @@ s) {
202
198
  // scope must mark them like any property node.
203
199
  if (f.k) o.push(f.k);
204
200
  if (f.dk) o.push(f.dk);
205
- // Carry the effective lookup into untouched descendants (family maps for
206
- // projections/optimistic stores; the global next lookup otherwise).
207
- r = f.fam?.map ?? r ?? storeNextLookup;
201
+ // Carry the effective family into untouched descendants (projections/
202
+ // optimistic stores register children under their family).
203
+ r = f.fam ?? r;
208
204
  }
209
205
  // Overlays are gone (next has no layer): raw enumeration; the optimistic
210
206
  // view composition above already folded armed-node membership/values in.
@@ -214,6 +210,7 @@ s) {
214
210
  }
215
211
  const e = Object.getOwnPropertySymbols(n);
216
212
  for (let f = 0, c = e.length; f < c; f++) {
213
+ if (e[f] === $OWNER) continue;
217
214
  const c = Object.getOwnPropertyDescriptor(n, e[f]);
218
215
  if (!c || c.get) continue;
219
216
  walkAffectsScope(c.value, t, o, r, s);
@@ -221,6 +218,7 @@ s) {
221
218
  } else {
222
219
  const e = Reflect.ownKeys(n);
223
220
  for (let f = 0, c = e.length; f < c; f++) {
221
+ if (e[f] === $OWNER) continue;
224
222
  const c = Object.getOwnPropertyDescriptor(n, e[f]);
225
223
  if (!c || c.get) continue;
226
224
  walkAffectsScope(c.value, t, o, r, s);
@@ -252,7 +250,7 @@ s) {
252
250
  // Callers guard on `pendingCheckActive`, which only flips inside
253
251
  // isPending() — the verdict layer is loaded and its hook installed.
254
252
  const o = e[STORE_NODE]?.[$AFFECTS];
255
- if (o?.o?.t) GlobalQueue.Wt(o);
253
+ if (o?.o?.t) GlobalQueue.ri(o);
256
254
  if (affectsScopes.size) {
257
255
  // Chained backings (§7b): a wrapper's STORE_VALUE can be another store's
258
256
  // proxy — marks cover by identity of the BASE raw, so resolve the chain
@@ -263,7 +261,7 @@ s) {
263
261
  let t = r;
264
262
  for (;;) {
265
263
  if (s.scope.has(t)) {
266
- GlobalQueue.Wt(e);
264
+ GlobalQueue.ri(e);
267
265
  break;
268
266
  }
269
267
  const o = t?.[$TARGET];
@@ -302,7 +300,7 @@ s) {
302
300
  inherited: []
303
301
  });
304
302
  const r = [ t ];
305
- walkAffectsScope(e[$PROXY], o, r, e.fam?.map, new Set);
303
+ walkAffectsScope(e[$PROXY], o, r, e.fam, new Set);
306
304
  return r;
307
305
  }
308
306
  const o = e.n?.[t] ?? nextAffectsNodeResolver(e, t);
@@ -51,6 +51,15 @@ function resolveSource(e) {
51
51
 
52
52
  const $SOURCES = Symbol(0);
53
53
 
54
+ /** @internal The flattened sources behind a `merge()` PROXY, or undefined.
55
+ * Only the proxy form: its writes are no-ops, so the sources are the whole
56
+ * truth. merge()'s plain-object form also records `$SOURCES` (so nested
57
+ * merges flatten), but it is a real object callers may mutate afterwards
58
+ * (html's tagged templates assign props after spreading) — those own writes
59
+ * live on the object, not in the sources, so it must be read directly. */ function mergeSources(e) {
60
+ return e != null && e[$PROXY] === e ? e[$SOURCES] : undefined;
61
+ }
62
+
54
63
  /**
55
64
  * Merges multiple props-like objects into a single proxy that *preserves
56
65
  * reactivity*. Reads are forwarded to the right-most source that defines the
@@ -77,9 +86,9 @@ const $SOURCES = Symbol(0);
77
86
  for (let n = 0; n < e.length; n++) {
78
87
  const o = e[n];
79
88
  r = r || !!o && $PROXY in o;
80
- const s = !!o && o[$SOURCES];
81
- if (s) {
82
- for (let e = 0; e < s.length; e++) t.push(s[e]);
89
+ const u = !!o && o[$SOURCES];
90
+ if (u) {
91
+ for (let e = 0; e < u.length; e++) t.push(u[e]);
83
92
  } else t.push(typeof o === "function" ? (r = true, createMemo(o)) : o);
84
93
  }
85
94
  if (SUPPORTS_PROXY && r) {
@@ -109,21 +118,21 @@ const $SOURCES = Symbol(0);
109
118
  }
110
119
  const n = Object.create(null);
111
120
  let o = false;
112
- let s = t.length - 1;
113
- for (let e = s; e >= 0; e--) {
121
+ let u = t.length - 1;
122
+ for (let e = u; e >= 0; e--) {
114
123
  const r = t[e];
115
124
  if (!r) {
116
- e === s && s--;
125
+ e === u && u--;
117
126
  continue;
118
127
  }
119
- const u = Object.getOwnPropertyNames(r);
120
- for (let t = u.length - 1; t >= 0; t--) {
121
- const c = u[t];
122
- if (c === "__proto__" || c === "constructor") continue;
123
- if (!n[c]) {
124
- o = o || e !== s;
125
- const t = Object.getOwnPropertyDescriptor(r, c);
126
- n[c] = t.get ? {
128
+ const c = Object.getOwnPropertyNames(r);
129
+ for (let t = c.length - 1; t >= 0; t--) {
130
+ const s = c[t];
131
+ if (s === "__proto__" || s === "constructor") continue;
132
+ if (!n[s]) {
133
+ o = o || e !== u;
134
+ const t = Object.getOwnPropertyDescriptor(r, s);
135
+ n[s] = t.get ? {
127
136
  enumerable: true,
128
137
  configurable: true,
129
138
  get: t.get.bind(r)
@@ -131,15 +140,15 @@ const $SOURCES = Symbol(0);
131
140
  }
132
141
  }
133
142
  }
134
- if (!o) return t[s];
135
- const u = {};
136
- const c = Object.keys(n);
137
- for (let e = c.length - 1; e >= 0; e--) {
138
- const r = c[e], t = n[r];
139
- if (t.get) Object.defineProperty(u, r, t); else u[r] = t.value;
143
+ if (!o) return t[u];
144
+ const c = {};
145
+ const s = Object.keys(n);
146
+ for (let e = s.length - 1; e >= 0; e--) {
147
+ const r = s[e], t = n[r];
148
+ if (t.get) Object.defineProperty(c, r, t); else c[r] = t.value;
140
149
  }
141
- u[$SOURCES] = t;
142
- return u;
150
+ c[$SOURCES] = t;
151
+ return c;
143
152
  }
144
153
 
145
154
  /**
@@ -189,13 +198,13 @@ const $SOURCES = Symbol(0);
189
198
  const t = {};
190
199
  const n = Object.getOwnPropertyNames(e);
191
200
  const o = r.length > 4 && n.length > r.length ? new Set(r) : undefined;
192
- for (const s of n) {
193
- if (o ? !o.has(s) : !r.includes(s)) {
194
- const r = Object.getOwnPropertyDescriptor(e, s);
195
- !r.get && !r.set && r.enumerable && r.writable && r.configurable ? t[s] = r.value : Object.defineProperty(t, s, r);
201
+ for (const u of n) {
202
+ if (o ? !o.has(u) : !r.includes(u)) {
203
+ const r = Object.getOwnPropertyDescriptor(e, u);
204
+ !r.get && !r.set && r.enumerable && r.writable && r.configurable ? t[u] = r.value : Object.defineProperty(t, u, r);
196
205
  }
197
206
  }
198
207
  return t;
199
208
  }
200
209
 
201
- export { merge, omit };
210
+ export { merge, mergeSources, omit };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@solidjs/signals/attribution` — the "why did this run" engine.
3
+ *
4
+ * A separate entry on purpose: the core ships only the hook slot
5
+ * (`OBSERVE.attribution.install`) and the interaction frame
6
+ * (`OBSERVE.attribution.withInteraction`); the engine that turns hook facts
7
+ * into re-run explanations, cost tables, holds and feedback lives here, so an
8
+ * observe build carries it only when something imports this module. The dev
9
+ * and observe tiers resolve to this file; the prod tier resolves to
10
+ * `attribution.prod.ts`, an inert engine with the same surface, so app code
11
+ * can import it unconditionally.
12
+ */
13
+ export { attribution } from "./core/attribution.js";
14
+ export type { Acknowledgement, Attribution, AttributionFeedbackTables, AttributionOptions, AttributionRecords, AttributionRecordType, ChangeKind, ChangeOrigin, ChangeRecord, FallbackStats, FeedbackInteraction, FeedbackNavigation, FeedbackSource, FlightLink, FlightStats, HeldWrite, HoldEvent, InteractionEvent, NavigationEvent, NavigationHop, RerunEvent, ScopeCost, WaterfallRecord, WriteCost } from "./core/attribution.js";
15
+ export type { InteractionRef, NavigationRef, OriginRef } from "./core/attribution-hooks.js";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@solidjs/signals/attribution` as the prod tier resolves it.
3
+ *
4
+ * A prod build has no hook sites — `__OBSERVE__` folded every one out — so an
5
+ * engine installed there would never hear a fact. Rather than make apps guard
6
+ * the import per tier, prod resolves this inert twin: the same `Attribution`
7
+ * surface, every query empty, `enable()` a no-op. Type-checked against the
8
+ * real engine's interface so the two cannot drift.
9
+ */
10
+ import type { Attribution } from "./core/attribution.js";
11
+ export declare const attribution: Attribution;
12
+ export type { Acknowledgement, Attribution, AttributionFeedbackTables, AttributionOptions, AttributionRecords, AttributionRecordType, ChangeKind, ChangeOrigin, ChangeRecord, FallbackStats, FeedbackInteraction, FeedbackNavigation, FeedbackSource, FlightLink, FlightStats, HeldWrite, HoldEvent, InteractionEvent, NavigationEvent, NavigationHop, RerunEvent, ScopeCost, WaterfallRecord, WriteCost } from "./core/attribution.js";
13
+ export type { InteractionRef, NavigationRef, OriginRef } from "./core/attribution-hooks.js";
@@ -14,7 +14,7 @@ export declare function setPendingError(el: Computed<any>, source?: Computed<any
14
14
  export declare function forEachDependent(el: Computed<any>, fn: (node: Computed<any>, link: Link) => void): void;
15
15
  export declare function releaseSettledDependents(el: Computed<any>): void;
16
16
  export declare function settleErroredDependents(el: Computed<any>, error: any): void;
17
- export declare function settlePendingSource(el: Computed<any>): void;
17
+ export declare function settlePendingSource(el: Computed<any>, source?: Computed<any>): void;
18
18
  export declare function isThenable<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
19
19
  /** Fire and clear a node's iterator-flight cancellation hook (#3122). */
20
20
  export declare function releaseFlightTeardown(el: Computed<any>): void;
@@ -1,21 +1,47 @@
1
+ import type { Transition } from "./scheduler.js";
1
2
  import type { Computed, Signal } from "./types.js";
2
3
  /**
3
- * Dev-only observability hook points for the reactive core.
4
+ * Observe-tier hook points for the reactive core.
4
5
  *
5
6
  * Core's obligation is to call these with true facts at the moments they
6
7
  * happen; ALL attribution semantics (stamps, cause chains, timings, warnings)
7
- * live in the engine that installs them (attribution.ts same pattern as the
8
- * GlobalQueue._* feature slots). `attrHooks` is null unless an engine is
9
- * installed, so the disabled cost is one null check per site, and prod builds
10
- * fold every site out behind __DEV__.
8
+ * live in the engine that installs them — `@solidjs/signals/attribution`, a
9
+ * separate entry so an observe build that never enables it never ships it
10
+ * (same pattern as the GlobalQueue._* feature slots). `attrHooks` is null
11
+ * unless an engine is installed, so the disabled cost is one null check per
12
+ * site, and prod builds fold every site out behind __OBSERVE__.
11
13
  *
12
14
  * IMPORTANT for implementers of call sites: a hook call must never sit inside
13
15
  * a `try` block — rollup's tryCatchDeoptimization retains functions referenced
14
- * inside `try` even behind a folded __DEV__ guard, which re-couples the dev
16
+ * inside `try` even behind a folded __OBSERVE__ guard, which re-couples the
15
17
  * engine into prod bundles (#2883 harness). Set a local flag inside the try
16
18
  * and call the hook after the catch.
17
19
  */
18
20
  export interface AttributionHooks {
21
+ /**
22
+ * `withInteraction` opened a user-interaction frame: root writes until the
23
+ * matching `interactionEnd` were performed by the handler of `ref`. Frames
24
+ * nest strictly (synchronous dispatch), so the engine keeps a stack.
25
+ */
26
+ interactionStart(ref: InteractionRef): void;
27
+ interactionEnd(): void;
28
+ /**
29
+ * `withOrigin` opened a declared-origin frame: root writes until the
30
+ * matching `originEnd` are the unit of work `ref` describes (a router's
31
+ * navigation). Nests inside an interaction frame — a link click that
32
+ * navigates — or stands alone (a redirect from an action, a programmatic
33
+ * `navigate()`). Frames nest strictly, so the engine keeps a stack.
34
+ */
35
+ originStart(ref: OriginRef): void;
36
+ originEnd(): void;
37
+ /**
38
+ * A `flush()` drain finished: every batch it processed either committed
39
+ * (its effects have run) or was parked in a held transition (`holdStart`
40
+ * fired for it). Fires once per drain, after the loop — not per batch, and
41
+ * not for a `flush()` call that found nothing to do. Gives the engine the
42
+ * "committed, screen updated" instant for writes no transition ever held.
43
+ */
44
+ flushEnd(): void;
19
45
  /**
20
46
  * A recompute is starting; `el._deps` still holds the previous run's links.
21
47
  * Always paired with `recomputeEnd` (recompute has no early returns).
@@ -58,6 +84,157 @@ export interface AttributionHooks {
58
84
  * from the node's state against its asyncStart snapshot.
59
85
  */
60
86
  asyncEnd(el: Computed<any>, prev: unknown, value: unknown, direct: boolean): void;
87
+ /**
88
+ * An effect's imperative half (its effect callback) is about to run /
89
+ * has run. Both fire outside the run's try; `effectRunEnd` fires whether
90
+ * or not the callback threw. Writes between the two are the effect's.
91
+ */
92
+ effectRunStart(el: Computed<any>): void;
93
+ effectRunEnd(el: Computed<any>): void;
94
+ /**
95
+ * One synchronous step of an `action()` generator is about to run / has
96
+ * run (`it.next()`/`it.throw()` up to the next yield). `it` is the
97
+ * invocation's iterator — stable identity across its steps; `name` the
98
+ * generator function's name. Writes between the two are the action's.
99
+ */
100
+ actionStepStart(it: object, name: string | undefined): void;
101
+ actionStepEnd(it: object): void;
102
+ /**
103
+ * A flush found `t` incomplete (transitionComplete's false verdict): its
104
+ * writes stay staged and its queues are about to be parked. Fired BEFORE
105
+ * this flush's lane effects (the visible acknowledgers — isPending
106
+ * companions, optimistic values) run; `holdEnd` fires from the root
107
+ * stashQueues call after them, so effect runs between the two are runs that
108
+ * painted *during* the hold.
109
+ */
110
+ holdStart(t: Transition): void;
111
+ holdEnd(): void;
112
+ /**
113
+ * `t` was judged complete (transitionComplete's true verdict, before `_done`
114
+ * flips). Fired before its held writes commit, so `t._pendingNodes` still
115
+ * lists what was staged.
116
+ */
117
+ transitionSettled(t: Transition): void;
118
+ /** `outgoing` was folded into `target` (`outgoing._done = target`). */
119
+ transitionMerged(target: Transition, outgoing: Transition): void;
120
+ /**
121
+ * A store setter batch replaced the container at `path` (e.g. `store.user`)
122
+ * with a different one (both non-null, same array-ness, not the same
123
+ * logical slot), and this is the leaf census of the new container against
124
+ * the old: `total` leaves (own keys, or items) in the new one, `unchanged`
125
+ * of which are the same value as before (identity, judged on unwrapped
126
+ * values — object keys compared by key, array items by membership), and
127
+ * `prevTotal` leaves in the old one. Containers above 64 leaves are not
128
+ * announced. Fired per written key from the write channel's notify. The
129
+ * engine decides whether the replacement was a spread-copy worth a
130
+ * diagnostic.
131
+ */
132
+ storeReplaced(path: string, isArray: boolean, total: number, unchanged: number, prevTotal: number): void;
133
+ /**
134
+ * A `mapArray` update both disposed and created rows: `removed` are the
135
+ * items whose rows were disposed, `created` the items that got new rows,
136
+ * `newLen` the list's new length, `keyed` whether a key function is in use
137
+ * (false = identity or by-index). Fired after commit. The engine judges
138
+ * whether the churn replaced equivalent records (unstable identity).
139
+ */
140
+ listChurn(el: Computed<any>, removed: unknown[], created: unknown[], newLen: number, keyed: boolean): void;
141
+ /**
142
+ * A loading boundary started (`shown` true) or stopped showing its
143
+ * fallback. `boundary` is the boundary's queue (stable identity); `tree`
144
+ * its bound subtree computed when already constructed — the first show can
145
+ * fire while the subtree is still being built — whose owner chain names
146
+ * the boundary. Fired at the source-set transitions (first pending source
147
+ * registers / last one clears), not per flush.
148
+ */
149
+ boundaryFallback(boundary: object, tree: Computed<any> | undefined, shown: boolean): void;
150
+ }
151
+ /** A user interaction, as a rendering runtime describes it to `withInteraction`. */
152
+ export interface InteractionRef {
153
+ /** Event type — `click`, `keydown`, `input`… */
154
+ type: string;
155
+ /** The element hit, e.g. `button#next "Next →"`. */
156
+ target?: string;
157
+ /** Dispatch time on the `performance.now()` clock; defaults to now. */
158
+ at?: number;
159
+ }
160
+ /**
161
+ * A navigation, as a router describes it to `withOrigin` around the location
162
+ * write it is about to perform. Match eagerly and describe before writing:
163
+ * the engine keys the work the write causes — the hold behind route data,
164
+ * the re-runs, the verdicts — to this record, and names it by the
165
+ * parametrized route so occurrences fold together.
166
+ *
167
+ * The engine keeps the object and reads `name`, `to` and `params` again when
168
+ * the navigation settles (and when a hold on it is judged), so a router whose
169
+ * match is not final at write time — a lazy route subtree that resolves inside
170
+ * the hold — may describe coarsely (`/admin/*`) and assign the exact pattern
171
+ * and params onto the same object once it knows them. `from` and `at` are
172
+ * read once, when the frame opens.
173
+ */
174
+ export interface NavigationRef {
175
+ kind: "navigation";
176
+ /** The matched route pattern — `/users/:id`. The name every consumer groups by. */
177
+ name?: string;
178
+ /** Concrete destination path. */
179
+ to?: string;
180
+ /** Concrete path being left. */
181
+ from?: string;
182
+ /** Route params the pattern bound — `{ id: "42" }` (optional params unbound: `undefined`). */
183
+ params?: Readonly<Record<string, string | undefined>>;
184
+ /**
185
+ * When the navigation was requested on the `performance.now()` clock;
186
+ * defaults to now. A router whose request predates the write (loaders
187
+ * awaited before the location moves) passes its own start here.
188
+ */
189
+ at?: number;
190
+ /**
191
+ * `>= 1`: this frame is the Nth redirect hop of the navigation still
192
+ * pending — a guard or loader sent it elsewhere before it landed — not a
193
+ * new navigation. The engine folds it onto that pending record: the record
194
+ * keeps the user's request time and interaction, its destination becomes
195
+ * this one, and the abandoned destination is kept in `redirects`. Without
196
+ * a pending navigation to fold onto it opens a navigation of its own.
197
+ */
198
+ redirect?: number;
61
199
  }
200
+ /**
201
+ * What `withOrigin` accepts: a declared unit of work whose writes the engine
202
+ * should attribute as a whole. A discriminated union so kinds can be added
203
+ * (a form submission, a tab switch) without the seam changing shape; the
204
+ * engine knows `navigation` today.
205
+ */
206
+ export type OriginRef = NavigationRef;
62
207
  export declare let attrHooks: AttributionHooks | null;
63
208
  export declare function setAttributionHooks(hooks: AttributionHooks | null): void;
209
+ /**
210
+ * Run `fn` as the handler of a user interaction: every root write it performs
211
+ * (and every action step, effect or flight the write causes) is attributed to
212
+ * `ref` by whichever engine is installed. The web runtime wraps event
213
+ * dispatch in this; custom renderers and test harnesses call it themselves.
214
+ * With no engine installed it is `fn()` — the wiring, not the engine, so it
215
+ * lives in core and is reachable as `OBSERVE.attribution.withInteraction`.
216
+ *
217
+ * The `finally` is deliberate and safe under the try rule above: this
218
+ * function is referenced only from the `OBSERVE` object, which prod builds
219
+ * fold to `undefined`, so nothing retains it there.
220
+ */
221
+ export declare function withInteraction<T>(ref: InteractionRef, fn: () => T): T;
222
+ /**
223
+ * Run `fn` as a declared unit of work — a router's navigation: every root
224
+ * write it performs is attributed to `ref` (and, through it, to the enclosing
225
+ * interaction when there is one), so the hold those writes wait in, the
226
+ * re-runs they cause and the verdicts on them all carry the route's name
227
+ * instead of a bare signal's. Same contract as `withInteraction`: the wiring,
228
+ * not the engine; `fn()` with no engine installed. Reachable as
229
+ * `OBSERVE.attribution.withOrigin`.
230
+ *
231
+ * ```ts
232
+ * OBSERVE
233
+ * ? OBSERVE.attribution.withOrigin(
234
+ * { kind: "navigation", name: match.pattern, to, from, params: match.params },
235
+ * () => setLocation(to)
236
+ * )
237
+ * : setLocation(to);
238
+ * ```
239
+ */
240
+ export declare function withOrigin<T>(ref: OriginRef, fn: () => T): T;