@solidjs/signals 2.0.0-rc.14 → 2.0.0-rc.15
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.
- package/dist/dev-shared.js +248 -189
- package/dist/dev.attribution.js +124 -41
- package/dist/dev.js +553 -322
- package/dist/observe/affects.js +34 -31
- package/dist/observe/boundaries.js +166 -132
- package/dist/observe/core/async.js +90 -83
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +281 -197
- package/dist/observe/core/constants.js +7 -2
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +277 -245
- package/dist/observe/core/dev.js +3 -3
- package/dist/observe/core/effect.js +21 -21
- package/dist/observe/core/external.js +2 -2
- package/dist/observe/core/graph.js +25 -25
- package/dist/observe/core/heap.js +9 -9
- package/dist/observe/core/lanes.js +206 -160
- package/dist/observe/core/owner.js +27 -27
- package/dist/observe/core/scheduler.js +92 -86
- package/dist/observe/core/verdict.js +66 -54
- package/dist/observe/map.js +35 -35
- package/dist/observe/reveal.js +24 -24
- package/dist/observe/signals.js +38 -28
- package/dist/observe/store/affects.js +75 -68
- package/dist/observe/store/hooks.js +13 -0
- package/dist/observe/store/index.js +0 -2
- package/dist/observe/store/optimistic.js +67 -34
- package/dist/observe/store/projection.js +37 -31
- package/dist/observe/store/reconcile.js +32 -30
- package/dist/observe/store/store.js +128 -85
- package/dist/prod/affects.js +34 -31
- package/dist/prod/boundaries.js +143 -109
- package/dist/prod/core/async.js +64 -67
- package/dist/prod/core/constants.js +7 -2
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +302 -270
- package/dist/prod/core/effect.js +38 -38
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +27 -27
- package/dist/prod/core/heap.js +30 -30
- package/dist/prod/core/lanes.js +257 -211
- package/dist/prod/core/owner.js +39 -39
- package/dist/prod/core/scheduler.js +125 -119
- package/dist/prod/core/verdict.js +70 -58
- package/dist/prod/map.js +73 -73
- package/dist/prod/reveal.js +38 -38
- package/dist/prod/signals.js +30 -20
- package/dist/prod/store/affects.js +75 -68
- package/dist/prod/store/hooks.js +13 -0
- package/dist/prod/store/index.js +0 -2
- package/dist/prod/store/optimistic.js +114 -81
- package/dist/prod/store/projection.js +30 -24
- package/dist/prod/store/reconcile.js +32 -30
- package/dist/prod/store/store.js +136 -93
- package/dist/types/boundaries.d.ts +4 -0
- package/dist/types/core/attribution.d.ts +6 -2
- package/dist/types/core/constants.d.ts +5 -0
- package/dist/types/core/core.d.ts +33 -3
- package/dist/types/core/lanes.d.ts +10 -0
- package/dist/types/core/scheduler.d.ts +12 -6
- package/dist/types/core/types.d.ts +0 -6
- package/dist/types/store/affects.d.ts +11 -0
- package/dist/types/store/hooks.d.ts +48 -0
- package/dist/types/store/index.d.ts +0 -1
- package/dist/types/store/store.d.ts +3 -38
- package/package.json +1 -1
package/dist/dev.attribution.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
bv as OBSERVE,
|
|
3
|
+
bF as setAttributionHooks,
|
|
4
|
+
bG as liveRootOwners,
|
|
5
5
|
N as NOT_PENDING,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
ap as emitDiagnostic,
|
|
7
|
+
bH as ownerPath,
|
|
8
|
+
aD as reportDiagnostic,
|
|
9
|
+
bI as isSuppressed,
|
|
10
|
+
bJ as blockersOf,
|
|
11
|
+
bK as CONFIG_PLUMBING,
|
|
12
|
+
bL as anyExcluded,
|
|
13
|
+
bM as isExcluded,
|
|
14
|
+
bN as CONFIG_WIDE,
|
|
15
15
|
a as CONFIG_OVERRIDE,
|
|
16
16
|
C as CONFIG_GUESS,
|
|
17
17
|
G as GlobalQueue,
|
|
18
18
|
b as CONFIG_VERDICT,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
bO as GRAPH_SIZE_WARN_AT,
|
|
20
|
+
bP as noteFanOut,
|
|
21
|
+
bQ as CONFIG_SUPERSEDES,
|
|
22
|
+
aV as $REFRESH
|
|
23
23
|
} from "./dev-shared.js";
|
|
24
24
|
|
|
25
25
|
/** A fallback shown for less than this is a flash: feedback for a wait too short to need it. */
|
|
@@ -1913,8 +1913,17 @@ function checkOptimisticRevert(el, shown, truth, how) {
|
|
|
1913
1913
|
);
|
|
1914
1914
|
}
|
|
1915
1915
|
// --- Graph growth -----------------------------------------------------------------
|
|
1916
|
-
/**
|
|
1916
|
+
/**
|
|
1917
|
+
* Per route: at its last `visits` compared settles, oldest first, the
|
|
1918
|
+
* graph's size and the part of it that was already alive when that
|
|
1919
|
+
* navigation opened.
|
|
1920
|
+
*/
|
|
1917
1921
|
const routeCounts = new Map();
|
|
1922
|
+
/**
|
|
1923
|
+
* The route the app last arrived on: a navigation that stays on it (only
|
|
1924
|
+
* its params or query change) continues the visit and is not compared.
|
|
1925
|
+
*/
|
|
1926
|
+
let visitRoute;
|
|
1918
1927
|
/**
|
|
1919
1928
|
* The live graph's size — a walk, not a counter: nothing is charged at node
|
|
1920
1929
|
* creation, disposal, write or re-run; the engine asks at a navigation's
|
|
@@ -1929,26 +1938,42 @@ const routeCounts = new Map();
|
|
|
1929
1938
|
* counted unless a subscription still reaches them.
|
|
1930
1939
|
*/
|
|
1931
1940
|
function graphSize() {
|
|
1941
|
+
return walkGraph();
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* The walk behind `graphSize()`. Given `before` alone, it adds every node it
|
|
1945
|
+
* meets to that set: what is alive as a navigation opens. Given `before` and
|
|
1946
|
+
* `survived`, it also counts into `survived` the nodes it meets that are in
|
|
1947
|
+
* the set, and an edge when both its ends are: what outlived the navigation.
|
|
1948
|
+
* Nothing is retained — the set is weak.
|
|
1949
|
+
*/
|
|
1950
|
+
function walkGraph(before, survived) {
|
|
1932
1951
|
const roots = liveRootOwners();
|
|
1933
|
-
const size = { roots:
|
|
1952
|
+
const size = { roots: 0, owners: 0, computations: 0, signals: 0, edges: 0 };
|
|
1934
1953
|
const seen = new Set();
|
|
1935
1954
|
// A worklist, not recursion: a subscriber chain can be thousands deep.
|
|
1936
1955
|
const work = [];
|
|
1937
1956
|
const stack = [];
|
|
1957
|
+
const count = (node, key) => {
|
|
1958
|
+
size[key]++;
|
|
1959
|
+
if (survived !== undefined) {
|
|
1960
|
+
if (before.has(node)) survived[key]++;
|
|
1961
|
+
} else if (before !== undefined) before.add(node);
|
|
1962
|
+
};
|
|
1938
1963
|
const meet = node => {
|
|
1939
1964
|
if (seen.has(node)) return;
|
|
1940
1965
|
seen.add(node);
|
|
1941
|
-
|
|
1942
|
-
else size.signals++;
|
|
1966
|
+
count(node, typeof node._fn === "function" ? "computations" : "signals");
|
|
1943
1967
|
work.push(node);
|
|
1944
1968
|
};
|
|
1945
1969
|
for (const root of roots) {
|
|
1946
|
-
|
|
1970
|
+
count(root, "roots");
|
|
1971
|
+
count(root, "owners");
|
|
1947
1972
|
// Iterative: a deep tree must not grow the call stack.
|
|
1948
1973
|
let child = root._firstChild;
|
|
1949
1974
|
for (;;) {
|
|
1950
1975
|
while (child !== null) {
|
|
1951
|
-
|
|
1976
|
+
count(child, "owners");
|
|
1952
1977
|
if (typeof child._fn === "function") meet(child);
|
|
1953
1978
|
if (child._firstChild !== null) stack.push(child);
|
|
1954
1979
|
child = child._nextSibling;
|
|
@@ -1967,10 +1992,20 @@ function graphSize() {
|
|
|
1967
1992
|
for (let link = node._deps; link !== null; link = link._nextDep) {
|
|
1968
1993
|
size.edges++;
|
|
1969
1994
|
meet(link._dep);
|
|
1995
|
+
if (survived !== undefined && before.has(node) && before.has(link._dep)) survived.edges++;
|
|
1970
1996
|
}
|
|
1971
1997
|
}
|
|
1972
1998
|
return size;
|
|
1973
1999
|
}
|
|
2000
|
+
/**
|
|
2001
|
+
* openNavigation, while the growth check is on: what is alive as a
|
|
2002
|
+
* navigation opens, for its settle to tell apart from what it created.
|
|
2003
|
+
*/
|
|
2004
|
+
function snapshotGraph() {
|
|
2005
|
+
const before = new WeakSet();
|
|
2006
|
+
walkGraph(before);
|
|
2007
|
+
return before;
|
|
2008
|
+
}
|
|
1974
2009
|
/**
|
|
1975
2010
|
* settleNavigation: the app has finished moving to a route — count the live
|
|
1976
2011
|
* graph. One record per settle for a listener; the growth check compares
|
|
@@ -1979,46 +2014,77 @@ function graphSize() {
|
|
|
1979
2014
|
* `createRoot` in an effect with no dispose, a subscription a component
|
|
1980
2015
|
* registered outside its owner. Nothing here runs per node; the walk is
|
|
1981
2016
|
* the cost, at navigation cadence.
|
|
2017
|
+
*
|
|
2018
|
+
* A count taken at an arrival also holds everything the visit just created,
|
|
2019
|
+
* so a page whose correctly owned content is bigger each visit climbs too.
|
|
2020
|
+
* What tells the two apart is what outlived the navigation: the nodes alive
|
|
2021
|
+
* when it opened (`NavState.before`) that are still alive at its settle. A
|
|
2022
|
+
* visit's own content is not among them; what an earlier visit left behind
|
|
2023
|
+
* is, whatever the content does. A series reports only when both climb.
|
|
1982
2024
|
*/
|
|
1983
2025
|
const GRAPH_SERIES = ["owners", "computations", "signals", "edges"];
|
|
1984
|
-
function trackGraph(
|
|
2026
|
+
function trackGraph(state) {
|
|
2027
|
+
const navigation = state.event;
|
|
2028
|
+
const before = state.before;
|
|
2029
|
+
state.before = undefined;
|
|
1985
2030
|
const cfg = options.graphGrowth;
|
|
1986
2031
|
if (cfg === false && !records.observed("graph")) return;
|
|
1987
|
-
const
|
|
2032
|
+
const survived = before && {
|
|
2033
|
+
roots: 0,
|
|
2034
|
+
owners: 0,
|
|
2035
|
+
computations: 0,
|
|
2036
|
+
signals: 0,
|
|
2037
|
+
edges: 0
|
|
2038
|
+
};
|
|
2039
|
+
const size = walkGraph(before, survived);
|
|
1988
2040
|
const route = navigation.name ?? navigation.to;
|
|
1989
2041
|
const event = { at: now(), ...size, navigation };
|
|
1990
2042
|
if (route !== undefined) event.route = route;
|
|
1991
2043
|
records.emit("graph", event, undefined);
|
|
2044
|
+
if (cfg === false || route === undefined) return;
|
|
2045
|
+
// A navigation that stays on the route pattern continues the visit: the
|
|
2046
|
+
// page it kept outlived the move by design, so comparing it would read the
|
|
2047
|
+
// page itself as growth. Only arrivals that enter the route are compared —
|
|
2048
|
+
// a leak made during a visit is still alive when the next one starts. A
|
|
2049
|
+
// superseded navigation never showed, so the app is still where it was.
|
|
2050
|
+
const left = visitRoute;
|
|
2051
|
+
if (navigation.outcome !== "superseded") visitRoute = route;
|
|
1992
2052
|
// The initial declaration is not a visit (it settles before route content
|
|
1993
2053
|
// mounts — the same gate the feedback folds apply as `event.initial !== true`);
|
|
1994
2054
|
// `initial` is `true | undefined`, set only by openNavigation, so truthiness is exact.
|
|
1995
|
-
|
|
2055
|
+
// A navigation opened before the check was on has no snapshot to compare.
|
|
2056
|
+
if (navigation.initial || route === left || survived === undefined) return;
|
|
1996
2057
|
let history = routeCounts.get(route);
|
|
1997
2058
|
if (history === undefined) routeCounts.set(route, (history = []));
|
|
1998
|
-
history.push(size);
|
|
2059
|
+
history.push({ size, survived });
|
|
1999
2060
|
if (history.length > cfg.visits) history.shift();
|
|
2000
2061
|
if (history.length < cfg.visits) return;
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2062
|
+
const totals = history.map(h => h.size);
|
|
2063
|
+
const kept = history.map(h => h.survived);
|
|
2064
|
+
const climbs = (series, key) => {
|
|
2065
|
+
for (let i = 1; i < series.length; i++) if (series[i][key] <= series[i - 1][key]) return false;
|
|
2066
|
+
return series[series.length - 1][key] >= series[0][key] * cfg.ratio;
|
|
2067
|
+
};
|
|
2068
|
+
// Any series climbing on every visit to the ratio — in the totals and in
|
|
2069
|
+
// what outlived each navigation — is growth; which ones did says what
|
|
2070
|
+
// leaked — computations with owners flat is an ownerless effect per visit,
|
|
2071
|
+
// edges alone is a subscription per visit to something long-lived, owners
|
|
2072
|
+
// is an undisposed root.
|
|
2073
|
+
const grew = GRAPH_SERIES.filter(key => climbs(totals, key) && climbs(kept, key));
|
|
2010
2074
|
if (grew.length === 0) return;
|
|
2011
2075
|
// The count is the whole graph's, so a leak shows at every route's settle;
|
|
2012
2076
|
// the first route to complete its climb reports, naming the others seen.
|
|
2013
2077
|
const routes = [...routeCounts.keys()];
|
|
2014
|
-
const
|
|
2078
|
+
const join = (series, key) => series.map(h => h[key]).join(" → ");
|
|
2079
|
+
const series = grew.map(key => `${key} ${join(kept, key)} (of ${join(totals, key)})`).join("; ");
|
|
2015
2080
|
const shape = grew.includes("owners")
|
|
2016
2081
|
? "a createRoot() in an effect or handler with no dispose, or a Portal or panel mounted per visit"
|
|
2017
2082
|
: grew.includes("computations")
|
|
2018
2083
|
? "an effect or memo created with no owner (a module-level or callback createEffect) that only its sources keep alive"
|
|
2019
2084
|
: "a subscription per visit to something long-lived — a global store or signal read by a computation that outlives the visit";
|
|
2020
2085
|
const message =
|
|
2021
|
-
`[GRAPH_GROWTH] the live graph grew on ${cfg.visits} consecutive visits to ${route}
|
|
2086
|
+
`[GRAPH_GROWTH] the live graph grew on ${cfg.visits} consecutive visits to ${route}, and so did ` +
|
|
2087
|
+
`the part that outlived each navigation there: ${series} ` +
|
|
2022
2088
|
`(${size.roots} roots)${routes.length > 1 ? `, across visits to ${routes.join(", ")}` : ""}. Each ` +
|
|
2023
2089
|
`visit left something behind that the next did not reclaim — the shape says ${shape}. Dispose ` +
|
|
2024
2090
|
`what a visit creates (onCleanup, or return the disposer from onSettled) and own it under the ` +
|
|
@@ -2026,7 +2092,8 @@ function trackGraph(navigation) {
|
|
|
2026
2092
|
const data = {
|
|
2027
2093
|
route,
|
|
2028
2094
|
grew,
|
|
2029
|
-
history:
|
|
2095
|
+
history: totals,
|
|
2096
|
+
survived: kept,
|
|
2030
2097
|
roots: size.roots,
|
|
2031
2098
|
routes
|
|
2032
2099
|
};
|
|
@@ -2159,6 +2226,14 @@ const openNavs = new Set();
|
|
|
2159
2226
|
let navigationLog = [];
|
|
2160
2227
|
/** Drains completed since enable() — the clock `writeDrain` reads. */
|
|
2161
2228
|
let drainSeq = 0;
|
|
2229
|
+
/**
|
|
2230
|
+
* Navigations settled in a landing, waiting for their graph count: the hook
|
|
2231
|
+
* fires before the landing's commits dispose the frame parked for the page
|
|
2232
|
+
* left, which the walk would still reach through the sources both pages read
|
|
2233
|
+
* (#3989). `land()` runs only inside `flush()`'s drain, so `flushEnd` counts
|
|
2234
|
+
* them, after those commits — the instant a one-drain navigation is counted.
|
|
2235
|
+
*/
|
|
2236
|
+
const landedGraphs = [];
|
|
2162
2237
|
/**
|
|
2163
2238
|
* Copy what the router currently says onto the frame (what writes stamped —
|
|
2164
2239
|
* `formatOrigin` reads it) and the event. Called when the frame opens, when a
|
|
@@ -2193,6 +2268,8 @@ function openNavigation(frame, ref) {
|
|
|
2193
2268
|
held: false,
|
|
2194
2269
|
writeDrain: drainSeq
|
|
2195
2270
|
};
|
|
2271
|
+
// The initial declaration is never compared (see `trackGraph`).
|
|
2272
|
+
if (options.graphGrowth !== false && ref.initial !== true) state.before = snapshotGraph();
|
|
2196
2273
|
syncNavigation(state);
|
|
2197
2274
|
navStates.set(frame, state);
|
|
2198
2275
|
openNavs.add(state);
|
|
@@ -2268,7 +2345,7 @@ function settleNavigations(t, hold) {
|
|
|
2268
2345
|
if (state === undefined || !openNavs.has(state)) continue;
|
|
2269
2346
|
// A navigation whose frame is still open when its transition commits
|
|
2270
2347
|
// (`until()` inside the frame) settles here too: its writes are through.
|
|
2271
|
-
settleNavigation(state, state.held ? "held" : "committed", hold);
|
|
2348
|
+
settleNavigation(state, state.held ? "held" : "committed", hold, true);
|
|
2272
2349
|
}
|
|
2273
2350
|
}
|
|
2274
2351
|
let openFlush = null;
|
|
@@ -2384,11 +2461,14 @@ function trackFlushEnd() {
|
|
|
2384
2461
|
drainSeq++;
|
|
2385
2462
|
// The swaps this drain committed have rendered.
|
|
2386
2463
|
displayFallbacks();
|
|
2464
|
+
// Landed first: they settled before anything this drain settles below.
|
|
2465
|
+
for (let i = 0; i < landedGraphs.length; i++) trackGraph(landedGraphs[i]);
|
|
2466
|
+
landedGraphs.length = 0;
|
|
2387
2467
|
for (const state of openNavs)
|
|
2388
2468
|
if (state.open === 0 && !state.held) settleNavigation(state, "committed");
|
|
2389
2469
|
for (const state of openInteractions) maybeSettleInteraction(state);
|
|
2390
2470
|
}
|
|
2391
|
-
function settleNavigation(state, outcome, hold) {
|
|
2471
|
+
function settleNavigation(state, outcome, hold, landed) {
|
|
2392
2472
|
if (!openNavs.has(state)) return;
|
|
2393
2473
|
openNavs.delete(state);
|
|
2394
2474
|
syncNavigation(state);
|
|
@@ -2401,7 +2481,8 @@ function settleNavigation(state, outcome, hold) {
|
|
|
2401
2481
|
// route's responsiveness in the feedback tables. Consumers get the record.
|
|
2402
2482
|
if (event.initial !== true) for (const f of folds) f.navigation?.(event);
|
|
2403
2483
|
records.emit("navigation", event, undefined);
|
|
2404
|
-
|
|
2484
|
+
if (landed) landedGraphs.push(state);
|
|
2485
|
+
else trackGraph(state);
|
|
2405
2486
|
// The interaction that performed it may have been waiting only on this.
|
|
2406
2487
|
const under = openInteractionOf(event.interaction);
|
|
2407
2488
|
if (under !== undefined) maybeSettleInteraction(under);
|
|
@@ -2885,8 +2966,10 @@ function resetTracking() {
|
|
|
2885
2966
|
stagedFallbacks.delete(DRAIN);
|
|
2886
2967
|
trackingGen++;
|
|
2887
2968
|
openNavs.clear();
|
|
2969
|
+
landedGraphs.length = 0;
|
|
2888
2970
|
openInteractions.clear();
|
|
2889
2971
|
routeCounts.clear();
|
|
2972
|
+
visitRoute = undefined;
|
|
2890
2973
|
drainSeq = 0;
|
|
2891
2974
|
originFrames.length = 0;
|
|
2892
2975
|
effectStack.length = 0;
|