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

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 (90) hide show
  1. package/dist/dev-shared.js +1399 -285
  2. package/dist/dev.attribution.js +459 -307
  3. package/dist/dev.js +1864 -435
  4. package/dist/observe/affects.js +3 -1
  5. package/dist/observe/attribution.js +7 -1
  6. package/dist/observe/boundaries.js +209 -154
  7. package/dist/observe/core/action.js +18 -8
  8. package/dist/observe/core/async.js +220 -110
  9. package/dist/observe/core/attribution-costs.js +66 -0
  10. package/dist/observe/core/attribution-feedback.js +282 -0
  11. package/dist/observe/core/attribution-hooks.js +23 -1
  12. package/dist/observe/core/attribution-queries.js +28 -0
  13. package/dist/observe/core/attribution.js +262 -485
  14. package/dist/observe/core/constants.js +34 -1
  15. package/dist/observe/core/context.js +3 -3
  16. package/dist/observe/core/core.js +867 -367
  17. package/dist/observe/core/dev.js +78 -17
  18. package/dist/observe/core/effect.js +67 -51
  19. package/dist/observe/core/error-hooks.js +71 -0
  20. package/dist/observe/core/external.js +4 -4
  21. package/dist/observe/core/graph.js +37 -37
  22. package/dist/observe/core/heap.js +45 -45
  23. package/dist/observe/core/invariants.js +2 -0
  24. package/dist/observe/core/lanes.js +86 -49
  25. package/dist/observe/core/optimistic.js +242 -95
  26. package/dist/observe/core/owner.js +98 -84
  27. package/dist/observe/core/scheduler.js +543 -305
  28. package/dist/observe/core/verdict.js +247 -129
  29. package/dist/observe/index.js +7 -3
  30. package/dist/observe/map.js +126 -124
  31. package/dist/observe/signals.js +33 -17
  32. package/dist/observe/store/index.js +2 -0
  33. package/dist/observe/store/next/optimistic.js +141 -132
  34. package/dist/observe/store/next/projection.js +34 -21
  35. package/dist/observe/store/next/reconcile.js +58 -56
  36. package/dist/observe/store/next/store.js +260 -146
  37. package/dist/observe/store/store.js +5 -3
  38. package/dist/observe/store/utils.js +948 -135
  39. package/dist/prod/attribution.js +26 -17
  40. package/dist/prod/boundaries.js +128 -76
  41. package/dist/prod/core/action.js +16 -8
  42. package/dist/prod/core/async.js +251 -143
  43. package/dist/prod/core/constants.js +34 -1
  44. package/dist/prod/core/context.js +3 -3
  45. package/dist/prod/core/core.js +843 -347
  46. package/dist/prod/core/dev.js +17 -1
  47. package/dist/prod/core/effect.js +48 -34
  48. package/dist/prod/core/error-hooks.js +71 -0
  49. package/dist/prod/core/external.js +4 -4
  50. package/dist/prod/core/graph.js +37 -37
  51. package/dist/prod/core/heap.js +45 -45
  52. package/dist/prod/core/lanes.js +90 -53
  53. package/dist/prod/core/optimistic.js +247 -100
  54. package/dist/prod/core/owner.js +57 -45
  55. package/dist/prod/core/scheduler.js +543 -305
  56. package/dist/prod/core/verdict.js +245 -127
  57. package/dist/prod/index.js +7 -3
  58. package/dist/prod/map.js +112 -112
  59. package/dist/prod/signals.js +28 -12
  60. package/dist/prod/store/next/optimistic.js +135 -128
  61. package/dist/prod/store/next/projection.js +31 -20
  62. package/dist/prod/store/next/store.js +325 -252
  63. package/dist/prod/store/store.js +2 -2
  64. package/dist/prod/store/utils.js +946 -135
  65. package/dist/types/attribution.d.ts +7 -2
  66. package/dist/types/attribution.prod.d.ts +10 -1
  67. package/dist/types/boundaries.d.ts +10 -1
  68. package/dist/types/core/action.d.ts +12 -5
  69. package/dist/types/core/attribution-costs.d.ts +35 -0
  70. package/dist/types/core/attribution-feedback.d.ts +133 -0
  71. package/dist/types/core/attribution-hooks.d.ts +28 -3
  72. package/dist/types/core/attribution-queries.d.ts +10 -0
  73. package/dist/types/core/attribution.d.ts +51 -172
  74. package/dist/types/core/constants.d.ts +33 -0
  75. package/dist/types/core/core.d.ts +186 -5
  76. package/dist/types/core/dev.d.ts +129 -9
  77. package/dist/types/core/error-hooks.d.ts +71 -0
  78. package/dist/types/core/index.d.ts +3 -1
  79. package/dist/types/core/invariants.d.ts +4 -0
  80. package/dist/types/core/lanes.d.ts +31 -4
  81. package/dist/types/core/scheduler.d.ts +95 -2
  82. package/dist/types/core/types.d.ts +3 -0
  83. package/dist/types/index.d.ts +2 -2
  84. package/dist/types/signals.d.ts +8 -0
  85. package/dist/types/store/index.d.ts +2 -1
  86. package/dist/types/store/next/optimistic.d.ts +1 -1
  87. package/dist/types/store/next/store.d.ts +6 -5
  88. package/dist/types/store/next/target.d.ts +1 -1
  89. package/dist/types/store/utils.d.ts +177 -6
  90. package/package.json +1 -1
@@ -0,0 +1,66 @@
1
+ import { registerFold, rootsOf } from "./attribution.js";
2
+
3
+ /**
4
+ * `costs()` — the cost tables: scopes ranked by the self-time they burn,
5
+ * root writes ranked by the downstream re-run time they cause. Folded from
6
+ * every `RerunEvent` as the engine records it.
7
+ *
8
+ * Its own module on purpose: the tables register with the engine's fold seam
9
+ * when this module is evaluated, so an observe-tier consumer that only
10
+ * subscribes to records (an APM adapter) never ships the accumulation or the
11
+ * tables; a consumer that imports `costs` gets both. Reset with the engine on
12
+ * `enable()`/`disable()`.
13
+ */ const scopeCosts = new Map;
14
+
15
+ const writeCosts = new Map;
16
+
17
+ function recordCosts(s, o) {
18
+ let t = scopeCosts.get(s);
19
+ if (t === undefined) {
20
+ t = {
21
+ name: o.nodeName,
22
+ kind: o.nodeKind,
23
+ runs: 0,
24
+ selfMs: 0,
25
+ wastedMs: 0,
26
+ overlayMs: 0
27
+ };
28
+ scopeCosts.set(s, t);
29
+ }
30
+ t.runs++;
31
+ t.selfMs += o.selfMs;
32
+ if (o.phase !== "plain") t.overlayMs += o.selfMs; else if (!o.changed && !o.held) t.wastedMs += o.selfMs;
33
+ const e = new Set;
34
+ rootsOf(o.causes, e);
35
+ for (const s of e) {
36
+ let t = writeCosts.get(s);
37
+ if (t === undefined) writeCosts.set(s, t = {
38
+ name: s,
39
+ runs: 0,
40
+ downstreamMs: 0
41
+ });
42
+ t.runs++;
43
+ t.downstreamMs += o.selfMs;
44
+ }
45
+ }
46
+
47
+ registerFold({
48
+ rerun: recordCosts,
49
+ reset() {
50
+ scopeCosts.clear();
51
+ writeCosts.clear();
52
+ }
53
+ });
54
+
55
+ /**
56
+ * Aggregated cost tables since `enable()`: `scopes` ranked by self-time
57
+ * (with `wastedMs` = time spent on unchanged-value runs), `writes` ranked by
58
+ * total downstream re-run time each root write caused.
59
+ */ function costs() {
60
+ return {
61
+ scopes: [ ...scopeCosts.values() ].sort((s, o) => o.selfMs - s.selfMs),
62
+ writes: [ ...writeCosts.values() ].sort((s, o) => o.downstreamMs - s.downstreamMs)
63
+ };
64
+ }
65
+
66
+ export { costs };
@@ -0,0 +1,282 @@
1
+ import { registerFold, now, isSilentHold, isLongHold, formatOrigin, nodeName } from "./attribution.js";
2
+
3
+ import { ownerPath } from "./dev.js";
4
+
5
+ /**
6
+ * `feedback()` — what the user waited on, as ranked tables. Folded from the
7
+ * records the engine already keeps (holds, the interaction on each re-run,
8
+ * navigations) and two censuses the engine reports as they happen (flights
9
+ * started/landed/abandoned per async source; fallback shows per boundary).
10
+ *
11
+ * Its own module on purpose: the tables register with the engine's fold seam
12
+ * when this module is evaluated, so an observe-tier consumer that only
13
+ * subscribes to records never ships them. Reset with the engine on
14
+ * `enable()`/`disable()`.
15
+ */ const feedbackSources = new Map;
16
+
17
+ const feedbackInteractions = new Map;
18
+
19
+ const feedbackNavigations = new Map;
20
+
21
+ const flightStats = new Map;
22
+
23
+ const fallbackStats = new Map;
24
+
25
+ const FALLBACK_FLASH_MS = 150;
26
+
27
+ function flightBucket(e) {
28
+ let n = flightStats.get(e);
29
+ if (n === undefined) {
30
+ n = {
31
+ source: nodeName(e),
32
+ flights: 0,
33
+ landed: 0,
34
+ abandoned: 0,
35
+ landedMs: 0,
36
+ worstMs: 0
37
+ };
38
+ flightStats.set(e, n);
39
+ }
40
+ return n;
41
+ }
42
+
43
+ function trackFallback(e, n, t) {
44
+ let i = fallbackStats.get(e);
45
+ if (i === undefined) {
46
+ i = {
47
+ row: {
48
+ boundary: "boundary",
49
+ shows: 0,
50
+ shownMs: 0,
51
+ worstMs: 0,
52
+ flashes: 0
53
+ },
54
+ shownAt: null
55
+ };
56
+ fallbackStats.set(e, i);
57
+ }
58
+ // The first show can fire before the subtree exists; name on first sight.
59
+ if (i.row.boundary === "boundary" && n !== undefined) {
60
+ const e = ownerPath(n);
61
+ if (e !== undefined) i.row.boundary = e.join(" › ");
62
+ }
63
+ if (t) {
64
+ if (i.shownAt === null) {
65
+ i.shownAt = now();
66
+ i.row.shows++;
67
+ }
68
+ return;
69
+ }
70
+ if (i.shownAt === null) return;
71
+ const s = now() - i.shownAt;
72
+ i.shownAt = null;
73
+ i.row.shownMs += s;
74
+ if (s > i.row.worstMs) i.row.worstMs = s;
75
+ if (s < FALLBACK_FLASH_MS) i.row.flashes++;
76
+ }
77
+
78
+ function interactionBucket(e) {
79
+ const n = formatOrigin(e);
80
+ let t = feedbackInteractions.get(n);
81
+ if (t === undefined) {
82
+ t = {
83
+ row: {
84
+ interaction: n,
85
+ dispatches: 0,
86
+ runs: 0,
87
+ selfMs: 0,
88
+ worstDispatchMs: 0,
89
+ holds: 0,
90
+ heldMs: 0,
91
+ silentMs: 0,
92
+ worstHoldMs: 0
93
+ },
94
+ dispatches: new Map
95
+ };
96
+ feedbackInteractions.set(n, t);
97
+ }
98
+ const i = e.at ?? 0;
99
+ if (!t.dispatches.has(i)) {
100
+ t.dispatches.set(i, 0);
101
+ t.row.dispatches++;
102
+ }
103
+ return t;
104
+ }
105
+
106
+ function recordFeedbackRun(e) {
107
+ if (e.interaction === undefined) return;
108
+ const n = interactionBucket(e.interaction);
109
+ n.row.runs++;
110
+ n.row.selfMs += e.selfMs;
111
+ const t = e.interaction.at ?? 0;
112
+ const i = n.dispatches.get(t) + e.selfMs;
113
+ n.dispatches.set(t, i);
114
+ if (i > n.row.worstDispatchMs) n.row.worstDispatchMs = i;
115
+ }
116
+
117
+ function recordFeedbackHold(e) {
118
+ const n = [ ...e.blockers ].sort();
119
+ const t = n.join("\0");
120
+ let i = feedbackSources.get(t);
121
+ if (i === undefined) {
122
+ i = {
123
+ row: {
124
+ sources: n,
125
+ holds: 0,
126
+ heldMs: 0,
127
+ worstMs: 0,
128
+ silent: 0,
129
+ silentMs: 0,
130
+ latestOnly: 0,
131
+ long: 0,
132
+ longMs: 0,
133
+ acknowledgedBy: [],
134
+ interactions: [],
135
+ writes: [],
136
+ actions: 0
137
+ },
138
+ acks: new Map,
139
+ interactions: new Map,
140
+ writes: new Set
141
+ };
142
+ feedbackSources.set(t, i);
143
+ }
144
+ const s = i.row;
145
+ const o = isSilentHold(e);
146
+ s.holds++;
147
+ s.heldMs += e.holdMs;
148
+ if (e.holdMs > s.worstMs) s.worstMs = e.holdMs;
149
+ if (o) {
150
+ s.silent++;
151
+ s.silentMs += e.holdMs;
152
+ } else if (e.acknowledgements.length > 0 && e.acknowledgements.every(e => e.kind === "latest")) s.latestOnly++;
153
+ if (isLongHold(e)) {
154
+ s.long++;
155
+ s.longMs += e.tailMs;
156
+ }
157
+ if (e.action) s.actions++;
158
+ for (const n of e.acknowledgements) {
159
+ const e = `${n.kind}:${n.source}`;
160
+ i.acks.set(e, (i.acks.get(e) ?? 0) + 1);
161
+ }
162
+ for (const n of e.heldWrites) i.writes.add(n.name);
163
+ if (e.interaction !== undefined) {
164
+ const n = formatOrigin(e.interaction);
165
+ i.interactions.set(n, (i.interactions.get(n) ?? 0) + 1);
166
+ const t = interactionBucket(e.interaction);
167
+ t.row.holds++;
168
+ t.row.heldMs += e.holdMs;
169
+ if (o) t.row.silentMs += e.holdMs;
170
+ if (e.holdMs > t.row.worstHoldMs) t.row.worstHoldMs = e.holdMs;
171
+ }
172
+ }
173
+
174
+ function recordFeedbackNavigation(e) {
175
+ const n = e.name ?? e.to;
176
+ if (n === undefined) return;
177
+ let t = feedbackNavigations.get(n);
178
+ if (t === undefined) {
179
+ t = {
180
+ name: n,
181
+ navigations: 0,
182
+ settledMs: 0,
183
+ worstMs: 0,
184
+ held: 0,
185
+ heldMs: 0,
186
+ silent: 0,
187
+ superseded: 0,
188
+ redirected: 0
189
+ };
190
+ feedbackNavigations.set(n, t);
191
+ }
192
+ t.navigations++;
193
+ if (e.redirects !== undefined) t.redirected++;
194
+ if (e.outcome === "superseded") {
195
+ t.superseded++;
196
+ return;
197
+ }
198
+ const i = e.settledMs;
199
+ t.settledMs += i;
200
+ if (i > t.worstMs) t.worstMs = i;
201
+ if (e.outcome === "held") {
202
+ t.held++;
203
+ t.heldMs += e.hold?.holdMs ?? i;
204
+ if (e.hold !== undefined && isSilentHold(e.hold)) t.silent++;
205
+ }
206
+ }
207
+
208
+ function rankedCounts(e, n) {
209
+ return [ ...e ].sort((e, n) => n[1] - e[1]).map(([e, t]) => ({
210
+ [n]: e,
211
+ holds: t
212
+ }));
213
+ }
214
+
215
+ registerFold({
216
+ rerun: (e, n) => recordFeedbackRun(n),
217
+ hold: recordFeedbackHold,
218
+ navigation: recordFeedbackNavigation,
219
+ flightStart(e, n) {
220
+ const t = flightBucket(e);
221
+ t.flights++;
222
+ if (n) t.abandoned++;
223
+ },
224
+ flightLanded(e, n) {
225
+ const t = flightBucket(e);
226
+ t.landed++;
227
+ t.landedMs += n;
228
+ if (n > t.worstMs) t.worstMs = n;
229
+ },
230
+ fallback: trackFallback,
231
+ reset() {
232
+ feedbackSources.clear();
233
+ feedbackInteractions.clear();
234
+ feedbackNavigations.clear();
235
+ flightStats.clear();
236
+ fallbackStats.clear();
237
+ }
238
+ });
239
+
240
+ /**
241
+ * What the user waited on, folded from holds and the interaction on each
242
+ * re-run: `sources` ranks async sources by the silent time writes spent held
243
+ * behind them (with which affordances answered, how often, and which
244
+ * interactions were held); `interactions` ranks user events by the total time
245
+ * they cost — re-run work caused (long-flush hazard) beside time held
246
+ * (silent-hold hazard). Facts at every duration; SILENT_HOLD is the
247
+ * thresholded verdict. Three more tables round out the picture: `navigations`
248
+ * ranks routes by the time spent held navigating to them (folded from settled
249
+ * navigations), `flights` counts each async source's flights and how many
250
+ * were abandoned before landing (the re-ask storm), and `fallbacks` measures
251
+ * how long each loading boundary showed its fallback and how often that was a
252
+ * flash.
253
+ */ function feedback() {
254
+ const e = [ ...feedbackNavigations.values() ].map(e => ({
255
+ ...e
256
+ })).sort((e, n) => n.heldMs - e.heldMs || n.settledMs - e.settledMs);
257
+ const n = [ ...flightStats.values() ].map(e => ({
258
+ ...e
259
+ })).sort((e, n) => n.abandoned - e.abandoned || n.flights - e.flights);
260
+ const t = [ ...fallbackStats.values() ].map(e => ({
261
+ ...e.row
262
+ })).sort((e, n) => n.flashes - e.flashes || n.shownMs - e.shownMs);
263
+ const i = [ ...feedbackSources.values() ].map(e => ({
264
+ ...e.row,
265
+ acknowledgedBy: rankedCounts(e.acks, "by"),
266
+ interactions: rankedCounts(e.interactions, "interaction"),
267
+ writes: [ ...e.writes ]
268
+ })).sort((e, n) => n.silentMs - e.silentMs || n.heldMs - e.heldMs);
269
+ // Ranked by the total time the user spent on it: held plus synchronous work.
270
+ const s = [ ...feedbackInteractions.values() ].map(e => ({
271
+ ...e.row
272
+ })).sort((e, n) => n.heldMs + n.selfMs - (e.heldMs + e.selfMs));
273
+ return {
274
+ sources: i,
275
+ interactions: s,
276
+ navigations: e,
277
+ flights: n,
278
+ fallbacks: t
279
+ };
280
+ }
281
+
282
+ export { feedback };
@@ -1,7 +1,17 @@
1
1
  let attrHooks = null;
2
2
 
3
+ /**
4
+ * The installed engine, registered on `globalThis` as well (the records
5
+ * channel's reason, see `Records`): a wire layer bundled without a framework
6
+ * import — `@solidjs/web`'s server-function client — stamps the records it
7
+ * emits through the engine's `currentOrigin`, and this is its reach. The
8
+ * module binding stays the core's own read (one null check per hook site);
9
+ * the registration mirrors it.
10
+ */ const INSTALLED = Symbol.for("@solidjs/signals/observe/attribution");
11
+
3
12
  function setAttributionHooks(t) {
4
13
  attrHooks = t;
14
+ globalThis[INSTALLED] = t ?? undefined;
5
15
  }
6
16
 
7
17
  /**
@@ -57,4 +67,16 @@ function setAttributionHooks(t) {
57
67
  }
58
68
  }
59
69
 
60
- export { attrHooks, setAttributionHooks, withInteraction, withOrigin };
70
+ /**
71
+ * The provenance a root write performed now would carry, as the installed
72
+ * engine sees it (`AttributionHooks.currentOrigin`); `undefined` with no
73
+ * engine, or when nothing is in effect. Reachable as
74
+ * `OBSERVE.attribution.currentOrigin` — how a runtime stamps a fact of its
75
+ * own (a server-function call) with the interaction or navigation it ran
76
+ * for, so an observer joins the two by identity.
77
+ */ function currentOrigin() {
78
+ const t = attrHooks;
79
+ return t === null ? undefined : t.currentOrigin();
80
+ }
81
+
82
+ export { attrHooks, currentOrigin, setAttributionHooks, withInteraction, withOrigin };
@@ -0,0 +1,28 @@
1
+ import { nodeName, attribution } from "./attribution.js";
2
+
3
+ import { $REFRESH } from "./constants.js";
4
+
5
+ import { subjectOf } from "./dev.js";
6
+
7
+ /**
8
+ * Point queries over the engine's live state — the devtools/console view of
9
+ * one scope. Their own module so a records-only consumer never ships them;
10
+ * they read the engine's ring buffer and the graph, and register nothing.
11
+ */
12
+ /** The node behind a memo/effect accessor, or the raw node passed through. */ function nodeOf(t) {
13
+ return t?.[$REFRESH] ?? t;
14
+ }
15
+
16
+ /** Re-run history for one node — pass a memo/effect accessor or raw node. */ function why(t) {
17
+ const n = nodeOf(t);
18
+ return attribution.history().filter(t => subjectOf(t) === n);
19
+ }
20
+
21
+ /** Current dependency names of one scope — the devtools subscription view. */ function subscriptions(t) {
22
+ const n = nodeOf(t);
23
+ const o = [];
24
+ for (let t = n?.ee ?? null; t !== null; t = t.te) o.push(nodeName(t.re));
25
+ return o;
26
+ }
27
+
28
+ export { subscriptions, why };