@solidjs/signals 2.0.0-rc.15 → 2.0.0-rc.16
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 +785 -367
- package/dist/dev.attribution.js +75 -52
- package/dist/dev.js +1120 -567
- package/dist/observe/affects-marks.js +86 -0
- package/dist/observe/affects.js +22 -73
- package/dist/observe/boundaries.js +191 -189
- package/dist/observe/core/async.js +175 -131
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +176 -159
- package/dist/observe/core/constants.js +17 -3
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +503 -363
- package/dist/observe/core/dev.js +66 -38
- package/dist/observe/core/effect.js +48 -48
- package/dist/observe/core/error.js +5 -3
- package/dist/observe/core/external.js +5 -6
- package/dist/observe/core/graph.js +57 -43
- package/dist/observe/core/heap.js +33 -33
- package/dist/observe/core/identities.js +62 -0
- package/dist/observe/core/lanes.js +226 -259
- package/dist/observe/core/owner.js +62 -50
- package/dist/observe/core/scheduler.js +319 -171
- package/dist/observe/core/truth.js +45 -0
- package/dist/observe/core/verdict.js +88 -50
- package/dist/observe/deferred.js +184 -0
- package/dist/observe/index.js +7 -1
- package/dist/observe/map.js +345 -260
- package/dist/observe/reveal.js +76 -76
- package/dist/observe/signals.js +41 -30
- package/dist/observe/store/affects.js +6 -6
- package/dist/observe/store/edges.js +39 -0
- package/dist/observe/store/optimistic.js +41 -36
- package/dist/observe/store/projection.js +68 -55
- package/dist/observe/store/store.js +157 -104
- package/dist/prod/affects-marks.js +86 -0
- package/dist/prod/affects.js +23 -72
- package/dist/prod/boundaries.js +155 -153
- package/dist/prod/core/async.js +171 -127
- package/dist/prod/core/constants.js +17 -3
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +478 -342
- package/dist/prod/core/effect.js +68 -68
- package/dist/prod/core/error.js +5 -3
- package/dist/prod/core/external.js +5 -6
- package/dist/prod/core/graph.js +57 -43
- package/dist/prod/core/heap.js +38 -38
- package/dist/prod/core/identities.js +62 -0
- package/dist/prod/core/lanes.js +217 -250
- package/dist/prod/core/owner.js +62 -51
- package/dist/prod/core/scheduler.js +310 -162
- package/dist/prod/core/truth.js +45 -0
- package/dist/prod/core/verdict.js +87 -49
- package/dist/prod/deferred.js +182 -0
- package/dist/prod/index.js +7 -1
- package/dist/prod/map.js +360 -275
- package/dist/prod/reveal.js +76 -76
- package/dist/prod/signals.js +41 -30
- package/dist/prod/store/affects.js +6 -6
- package/dist/prod/store/edges.js +39 -0
- package/dist/prod/store/optimistic.js +37 -33
- package/dist/prod/store/projection.js +25 -19
- package/dist/prod/store/store.js +142 -96
- package/dist/types/affects-marks.d.ts +14 -0
- package/dist/types/boundaries.d.ts +1 -1
- package/dist/types/core/async.d.ts +10 -0
- package/dist/types/core/constants.d.ts +13 -1
- package/dist/types/core/core.d.ts +44 -2
- package/dist/types/core/dev.d.ts +43 -13
- package/dist/types/core/graph.d.ts +11 -0
- package/dist/types/core/identities.d.ts +17 -0
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/lanes.d.ts +2 -14
- package/dist/types/core/owner.d.ts +5 -3
- package/dist/types/core/scheduler.d.ts +47 -28
- package/dist/types/core/truth.d.ts +16 -0
- package/dist/types/core/types.d.ts +21 -0
- package/dist/types/core/verdict.d.ts +32 -0
- package/dist/types/deferred.d.ts +28 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/map.d.ts +2 -2
- package/dist/types/signals.d.ts +28 -7
- package/dist/types/store/edges.d.ts +9 -0
- package/dist/types/store/index.d.ts +10 -3
- package/dist/types/store/optimistic.d.ts +3 -2
- package/dist/types/store/projection.d.ts +4 -3
- package/dist/types/store/store.d.ts +1 -1
- package/dist/types/store/target.d.ts +16 -8
- package/dist/types/store/types.d.ts +14 -0
- package/package.json +1 -1
package/dist/dev.js
CHANGED
|
@@ -1,167 +1,228 @@
|
|
|
1
1
|
import {
|
|
2
2
|
C as CONFIG_GUESS,
|
|
3
|
-
s as schedule,
|
|
4
3
|
N as NOT_PENDING,
|
|
5
|
-
a as
|
|
4
|
+
a as NotReadyError,
|
|
5
|
+
b as CONFIG_AUTHORITATIVE,
|
|
6
|
+
e as enqueueSub,
|
|
7
|
+
c as CONFIG_OVERRIDE,
|
|
8
|
+
s as schedule,
|
|
6
9
|
q as question,
|
|
7
10
|
n as nextQuestion,
|
|
11
|
+
i as installLaneHooks,
|
|
8
12
|
G as GlobalQueue,
|
|
9
|
-
R as REACTIVE_DISPOSED,
|
|
10
|
-
b as CONFIG_VERDICT,
|
|
11
|
-
c as REACTIVE_PROBE_UNANSWERED,
|
|
12
|
-
d as blocked,
|
|
13
|
-
e as REACTIVE_LANE_DIRTY,
|
|
14
|
-
f as enqueueSub,
|
|
15
13
|
r as resolveTx,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
j as sameLane,
|
|
14
|
+
d as newTransaction,
|
|
15
|
+
o as ownFlights,
|
|
16
|
+
R as REACTIVE_DISPOSED,
|
|
17
|
+
f as CONFIG_VERDICT,
|
|
18
|
+
g as REACTIVE_PROBE_UNANSWERED,
|
|
19
|
+
h as blocked,
|
|
20
|
+
j as REACTIVE_LANE_DIRTY,
|
|
24
21
|
k as CONFIG_HELD,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
22
|
+
t as txOf,
|
|
23
|
+
l as clock,
|
|
24
|
+
m as insertSubs,
|
|
25
|
+
p as laneDirty,
|
|
26
|
+
u as REACTIVE_LANE_READ,
|
|
27
|
+
v as ext,
|
|
28
|
+
S as STATUS_UNINITIALIZED,
|
|
29
|
+
w as REACTIVE_RECOMPUTING_DEPS,
|
|
30
|
+
x as queuePendingNode,
|
|
31
|
+
y as list,
|
|
32
|
+
z as STATUS_ERROR,
|
|
33
|
+
A as tracking,
|
|
34
|
+
B as CONFIG_CHILDREN_FORBIDDEN,
|
|
35
|
+
D as passLane,
|
|
36
|
+
E as STATUS_PENDING,
|
|
37
|
+
F as EFFECT_RENDER,
|
|
38
|
+
H as sameLane,
|
|
39
|
+
I as flushTransaction,
|
|
40
|
+
J as REACTIVE_SCREEN_READ,
|
|
41
|
+
K as REACTIVE_FRAME_READ,
|
|
42
|
+
L as attrHooks,
|
|
43
|
+
M as commitPendingNode,
|
|
44
|
+
O as REACTIVE_IN_HEAP,
|
|
45
|
+
P as REACTIVE_DIRTY,
|
|
46
|
+
Q as REACTIVE_CHECK,
|
|
47
|
+
T as CONFIG_INPUTS_PUBLISHED,
|
|
48
|
+
U as releaseQueues,
|
|
49
|
+
V as CONFIG_SLOT_NODE,
|
|
50
50
|
W as holdNode,
|
|
51
51
|
X as joinFuture,
|
|
52
|
-
Y as
|
|
53
|
-
Z as
|
|
54
|
-
_ as
|
|
55
|
-
$ as
|
|
56
|
-
a0 as
|
|
57
|
-
a1 as
|
|
58
|
-
a2 as
|
|
59
|
-
a3 as
|
|
60
|
-
a4 as
|
|
61
|
-
a5 as
|
|
62
|
-
a6 as
|
|
52
|
+
Y as reruns,
|
|
53
|
+
Z as CONFIG_STAGED,
|
|
54
|
+
_ as merge$1,
|
|
55
|
+
$ as setPassLane,
|
|
56
|
+
a0 as REACTIVE_JOINED,
|
|
57
|
+
a1 as REACTIVE_STAGED_READ,
|
|
58
|
+
a2 as stagedRead,
|
|
59
|
+
a3 as setVerdict,
|
|
60
|
+
a4 as context,
|
|
61
|
+
a5 as installVerdictHooks,
|
|
62
|
+
a6 as REACTIVE_PROBED,
|
|
63
63
|
a7 as verdict,
|
|
64
64
|
a8 as link,
|
|
65
65
|
a9 as pullComputed,
|
|
66
66
|
aa as strictRead,
|
|
67
67
|
ab as warnStrictReadUntracked,
|
|
68
|
-
ac as
|
|
69
|
-
ad as
|
|
70
|
-
ae as
|
|
71
|
-
af as
|
|
72
|
-
ag as
|
|
73
|
-
ah as
|
|
74
|
-
ai as
|
|
75
|
-
aj as
|
|
76
|
-
ak as
|
|
77
|
-
al as
|
|
78
|
-
am as
|
|
79
|
-
an as
|
|
80
|
-
ao as
|
|
81
|
-
ap as
|
|
82
|
-
aq as
|
|
83
|
-
ar as
|
|
84
|
-
as as
|
|
85
|
-
at as
|
|
86
|
-
au as
|
|
87
|
-
av as
|
|
88
|
-
aw as
|
|
89
|
-
ax as
|
|
90
|
-
ay as
|
|
91
|
-
az as
|
|
92
|
-
aA as
|
|
93
|
-
aB as
|
|
94
|
-
aC as
|
|
95
|
-
aD as
|
|
96
|
-
aE as
|
|
97
|
-
aF as
|
|
98
|
-
aG as
|
|
99
|
-
aH as
|
|
100
|
-
aI as
|
|
101
|
-
aJ as
|
|
102
|
-
aK as
|
|
103
|
-
aL as
|
|
104
|
-
aM as
|
|
105
|
-
aN as
|
|
106
|
-
aO as
|
|
107
|
-
aP as
|
|
108
|
-
aQ as
|
|
109
|
-
aR as
|
|
110
|
-
aS as
|
|
111
|
-
aT as
|
|
112
|
-
aU as
|
|
113
|
-
aV as
|
|
114
|
-
aW as
|
|
115
|
-
aX as
|
|
116
|
-
aY as
|
|
117
|
-
aZ as
|
|
118
|
-
a_ as
|
|
119
|
-
a$ as
|
|
120
|
-
b0 as
|
|
121
|
-
b1 as
|
|
122
|
-
b2 as
|
|
123
|
-
b3 as
|
|
124
|
-
b4 as
|
|
125
|
-
b5 as
|
|
126
|
-
b6 as
|
|
127
|
-
b7 as
|
|
128
|
-
b8 as
|
|
129
|
-
b9 as
|
|
130
|
-
ba as
|
|
131
|
-
bb as
|
|
132
|
-
bc as
|
|
133
|
-
bd as
|
|
134
|
-
be as
|
|
135
|
-
bf as
|
|
136
|
-
bg as
|
|
137
|
-
bh as
|
|
138
|
-
bi as
|
|
139
|
-
bj as
|
|
140
|
-
bk as
|
|
141
|
-
bl as
|
|
142
|
-
bm as
|
|
143
|
-
bn as
|
|
144
|
-
bo as
|
|
145
|
-
bp as
|
|
146
|
-
bq as
|
|
147
|
-
br as
|
|
148
|
-
bs as
|
|
149
|
-
bt as
|
|
150
|
-
bu as
|
|
151
|
-
bv as
|
|
68
|
+
ac as staleReader,
|
|
69
|
+
ad as globalQueue,
|
|
70
|
+
ae as joinPass,
|
|
71
|
+
af as markLateLinker,
|
|
72
|
+
ag as spectating,
|
|
73
|
+
ah as unflushedValue,
|
|
74
|
+
ai as CONFIG_STAGED_ERROR,
|
|
75
|
+
aj as stagedReaders,
|
|
76
|
+
ak as bornInto,
|
|
77
|
+
al as enterAction,
|
|
78
|
+
am as setEffectCallback,
|
|
79
|
+
an as exitAction,
|
|
80
|
+
ao as isThenable,
|
|
81
|
+
ap as actionDepth,
|
|
82
|
+
aq as flush,
|
|
83
|
+
ar as setQuestion,
|
|
84
|
+
as as emitDiagnostic,
|
|
85
|
+
at as enterCallback,
|
|
86
|
+
au as exitCallback,
|
|
87
|
+
av as getOwner,
|
|
88
|
+
aw as inEffectCallback,
|
|
89
|
+
ax as setExternalHooks,
|
|
90
|
+
ay as signal,
|
|
91
|
+
az as setSignal,
|
|
92
|
+
aA as cleanup,
|
|
93
|
+
aB as read,
|
|
94
|
+
aC as DEV$1,
|
|
95
|
+
aD as CONFIG_TRANSPARENT,
|
|
96
|
+
aE as NoOwnerError,
|
|
97
|
+
aF as ContextNotFoundError,
|
|
98
|
+
aG as createEffectNode,
|
|
99
|
+
aH as recompute,
|
|
100
|
+
aI as EFFECT_USER,
|
|
101
|
+
aJ as reportDiagnostic,
|
|
102
|
+
aK as computed,
|
|
103
|
+
aL as staleValues,
|
|
104
|
+
aM as CONFIG_AUTO_DISPOSE,
|
|
105
|
+
aN as EFFECT_TRACKED,
|
|
106
|
+
aO as heldRunHook,
|
|
107
|
+
aP as unwrapStatusError,
|
|
108
|
+
aQ as haltReactivity,
|
|
109
|
+
aR as setStrictRead,
|
|
110
|
+
aS as StatusError,
|
|
111
|
+
aT as trimStaleDeps,
|
|
112
|
+
aU as setTrackedQueueCallback,
|
|
113
|
+
aV as setEffectStatusNotify,
|
|
114
|
+
aW as _hitUnhandledAsync,
|
|
115
|
+
aX as resetUnhandledAsync,
|
|
116
|
+
aY as ROOT_ERROR_HOOK,
|
|
117
|
+
aZ as registerGraph,
|
|
118
|
+
a_ as runWithOwner,
|
|
119
|
+
a$ as setMemo,
|
|
120
|
+
b0 as $REFRESH,
|
|
121
|
+
b1 as markRefresh,
|
|
122
|
+
b2 as createRoot,
|
|
123
|
+
b3 as getObserver,
|
|
124
|
+
b4 as dispose,
|
|
125
|
+
b5 as TimeoutError,
|
|
126
|
+
b6 as untrack,
|
|
127
|
+
b7 as dirtyQueue,
|
|
128
|
+
b8 as CONFIG_REDERIVE,
|
|
129
|
+
b9 as passTx,
|
|
130
|
+
ba as CONFIG_FRESH_READ,
|
|
131
|
+
bb as createOwner,
|
|
132
|
+
bc as writeHook,
|
|
133
|
+
bd as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
|
|
134
|
+
be as ownedScopeWriteMessage,
|
|
135
|
+
bf as isEqual,
|
|
136
|
+
bg as CONFIG_MANUAL_WRITE,
|
|
137
|
+
bh as snapshotCaptureActive,
|
|
138
|
+
bi as captureWriteSnapshot,
|
|
139
|
+
bj as notePromotedWrite,
|
|
140
|
+
bk as slotSignal,
|
|
141
|
+
bl as stagedScreen,
|
|
142
|
+
bm as CONFIG_PROMOTED,
|
|
143
|
+
bn as setSlotUnobserved,
|
|
144
|
+
bo as installStoreHooks,
|
|
145
|
+
bp as joinPassTx,
|
|
146
|
+
bq as asyncTailFlights,
|
|
147
|
+
br as untrackDepth,
|
|
148
|
+
bs as checkPostAwaitRead,
|
|
149
|
+
bt as disposalDepth,
|
|
150
|
+
bu as insertIntoHeap,
|
|
151
|
+
bv as isDisposed,
|
|
152
|
+
bw as handleAsync,
|
|
153
|
+
bx as addPendingSource,
|
|
154
|
+
by as propagateStatus,
|
|
155
|
+
bz as forEachDependent,
|
|
156
|
+
bA as settlePendingSource,
|
|
157
|
+
bB as notifyEpoch,
|
|
158
|
+
bC as SUPPORTS_PROXY,
|
|
159
|
+
bD as releaseMarksHook,
|
|
160
|
+
bE as installMarkHooks,
|
|
161
|
+
bF as CONFIG_LAGS,
|
|
162
|
+
bG as REACTIVE_REASK,
|
|
163
|
+
bH as heldUnder,
|
|
164
|
+
bI as unhold,
|
|
165
|
+
bJ as laneStageHook,
|
|
166
|
+
bK as REACTIVE_ZOMBIE,
|
|
167
|
+
bL as installBoundaryHooks,
|
|
168
|
+
bM as CAUGHT_REARMED,
|
|
169
|
+
bN as OBSERVE$1
|
|
152
170
|
} from "./dev-shared.js";
|
|
153
171
|
export {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
172
|
+
bO as clearSnapshots,
|
|
173
|
+
bP as enforceLoadingBoundary,
|
|
174
|
+
bQ as getNextChildId,
|
|
175
|
+
bR as markSnapshotScope,
|
|
176
|
+
bS as peekNextChildId,
|
|
177
|
+
bT as releaseSnapshotScope,
|
|
178
|
+
bU as resetErrorHalt,
|
|
179
|
+
bV as setConsoleFooter,
|
|
180
|
+
bW as setSnapshotCapture,
|
|
181
|
+
bX as setWriteHook
|
|
163
182
|
} from "./dev-shared.js";
|
|
164
183
|
|
|
184
|
+
// Truth reads (`until`, `refresh`; CONFIG_AUTHORITATIVE) over lane nodes.
|
|
185
|
+
// Side-effect free and apart from lanes.ts so a truth reader alone does not
|
|
186
|
+
// retain the lane engine: its reads reach these only through `laneRead`,
|
|
187
|
+
// which exists once something installs lanes (`createOptimistic`, verdicts).
|
|
188
|
+
/** A lane node's current value: its guess, or the latest it derived — the
|
|
189
|
+
* staging a shown lane holds for its next reveal, else its slot; the
|
|
190
|
+
* committed value while it has none (its first lane pass in flight). */
|
|
191
|
+
function laneValueOf(el) {
|
|
192
|
+
if (!(el._config & CONFIG_GUESS) && el._pendingValue !== NOT_PENDING) return el._pendingValue;
|
|
193
|
+
const v = el._x._lane;
|
|
194
|
+
return v !== NOT_PENDING ? v : el._value;
|
|
195
|
+
}
|
|
196
|
+
/** Truth readers (`until`, `refresh`; CONFIG_AUTHORITATIVE) — pay-for-use:
|
|
197
|
+
* the first one installs these on GlobalQueue (signals.ts `watch`).
|
|
198
|
+
*
|
|
199
|
+
* A truth reader's read of a lane node: the lane's value when it is the
|
|
200
|
+
* truth (#3943), else the truth staged beneath a guess, else the committed
|
|
201
|
+
* value. */
|
|
202
|
+
function truthRead(el) {
|
|
203
|
+
if (truth(el)) return laneValueOf(el);
|
|
204
|
+
if (!(el._config & CONFIG_GUESS)) throw new NotReadyError(null);
|
|
205
|
+
return el._pendingValue !== NOT_PENDING ? el._pendingValue : el._value;
|
|
206
|
+
}
|
|
207
|
+
/** A guess confirmed: its lane's truth readers re-read — of the lane's
|
|
208
|
+
* derivations too, which may be the truth now (#3943). */
|
|
209
|
+
function truthConfirmed(l) {
|
|
210
|
+
for (let i = 0; i < l._nodes.length; i++)
|
|
211
|
+
for (let s = l._nodes[i]._subs; s !== null; s = s._nextSub)
|
|
212
|
+
if (s._sub._config & CONFIG_AUTHORITATIVE) enqueueSub(s._sub);
|
|
213
|
+
}
|
|
214
|
+
/** A lane node's value is the truth when every guess beneath it is
|
|
215
|
+
* confirmed — its source staged a value equal to the guess — and nothing
|
|
216
|
+
* beneath it is held in the parent: same inputs, same value. */
|
|
217
|
+
function truth(n) {
|
|
218
|
+
if (n._config & CONFIG_GUESS)
|
|
219
|
+
return n._pendingValue !== NOT_PENDING && n._equals && n._equals(n._x._lane, n._pendingValue);
|
|
220
|
+
if (n._pendingValue !== NOT_PENDING) return;
|
|
221
|
+
if (n._config & CONFIG_OVERRIDE)
|
|
222
|
+
for (let d = n._deps; d; d = d._nextDep) if (!truth(d._dep)) return;
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
165
226
|
// Lanes (plan sec. 28 replay, 2026-10-02; maintainer rulings 2026-10-01/02). A lane
|
|
166
227
|
// is "a new base of a transition": the sub-frame an optimistic write opens
|
|
167
228
|
// in the transition of the frame it is made in. It sees the screen plus its
|
|
@@ -201,14 +262,6 @@ function newLane(parent) {
|
|
|
201
262
|
lanes.push(l);
|
|
202
263
|
return l;
|
|
203
264
|
}
|
|
204
|
-
/** A lane node's current value: its guess, or the latest it derived — the
|
|
205
|
-
* staging a shown lane holds for its next reveal, else its slot; the
|
|
206
|
-
* committed value while it has none (its first lane pass in flight). */
|
|
207
|
-
function laneValueOf(el) {
|
|
208
|
-
if (!(el._config & CONFIG_GUESS) && el._pendingValue !== NOT_PENDING) return el._pendingValue;
|
|
209
|
-
const v = el._x._lane;
|
|
210
|
-
return v !== NOT_PENDING ? v : el._value;
|
|
211
|
-
}
|
|
212
265
|
/** What the screen shows for a lane's node: the lane's value once the lane
|
|
213
266
|
* has revealed, the committed truth before. */
|
|
214
267
|
function display(el) {
|
|
@@ -273,23 +326,28 @@ function applyGuesses(parent) {
|
|
|
273
326
|
const n = pendingGuesses[i];
|
|
274
327
|
const v = pendingGuesses[i + 1];
|
|
275
328
|
let changed = true;
|
|
329
|
+
// The frame already holding the slot: a guess over it is a second
|
|
330
|
+
// suggestion for the slot, and two cannot finish apart (A34 (1)) — the
|
|
331
|
+
// frames entangle.
|
|
332
|
+
let holder = null;
|
|
276
333
|
if (n._config & CONFIG_OVERRIDE) {
|
|
277
334
|
// A lane's node already: a guess re-asked, or a derivation guessed
|
|
278
|
-
// over (its staging is void — the guess is the value now).
|
|
279
|
-
//
|
|
280
|
-
// entangle. The same value re-asked renews the question; nothing to
|
|
281
|
-
// notify.
|
|
335
|
+
// over (its staging is void — the guess is the value now). The same
|
|
336
|
+
// value re-asked renews the question; nothing to notify.
|
|
282
337
|
changed = !n._equals || !n._equals(laneValueOf(n), v);
|
|
283
338
|
if (!(n._config & CONFIG_GUESS)) {
|
|
284
339
|
n._pendingValue = NOT_PENDING;
|
|
285
340
|
n._config |= CONFIG_GUESS;
|
|
286
341
|
}
|
|
287
342
|
n._x._lane = v;
|
|
288
|
-
|
|
289
|
-
if (parent !== null && holder !== null) merge$1(parent, holder);
|
|
343
|
+
holder = txOf(n)._parent;
|
|
290
344
|
} else {
|
|
291
345
|
let l;
|
|
292
|
-
|
|
346
|
+
// A held node: a truth staged there, or what a corrected guess showed.
|
|
347
|
+
// The guess composes on it, so it ends with that hold — and with its
|
|
348
|
+
// own frame's (F-11): it lands only when both have (the board's rapid
|
|
349
|
+
// adds — a later add's row reverted with an earlier add's landing).
|
|
350
|
+
if (n._config & CONFIG_HELD) l = newLane((holder = txOf(n)));
|
|
293
351
|
else {
|
|
294
352
|
if (parent === null && inFlight$1(n)) parent = newTransaction(false);
|
|
295
353
|
if (parent === null) continue;
|
|
@@ -305,6 +363,7 @@ function applyGuesses(parent) {
|
|
|
305
363
|
n._config |= CONFIG_OVERRIDE | CONFIG_GUESS;
|
|
306
364
|
list(n, l);
|
|
307
365
|
}
|
|
366
|
+
if (parent !== null && holder !== null) merge$1(parent, holder);
|
|
308
367
|
ext(n)._q = pendingGuesses[i + 2];
|
|
309
368
|
if (changed) {
|
|
310
369
|
// The lane's members re-derive as its work (REACTIVE_LANE_DIRTY): a
|
|
@@ -504,37 +563,6 @@ function supersede(n, value, changed) {
|
|
|
504
563
|
n._pendingValue = value;
|
|
505
564
|
joinFuture(parent);
|
|
506
565
|
}
|
|
507
|
-
/** Truth readers (`until`, `refresh`; CONFIG_AUTHORITATIVE) — pay-for-use:
|
|
508
|
-
* the first one installs these on GlobalQueue (signals.ts `watch`).
|
|
509
|
-
*
|
|
510
|
-
* A truth reader's read of a lane node: the lane's value when it is the
|
|
511
|
-
* truth (#3943), else the truth staged beneath a guess, else the committed
|
|
512
|
-
* value. */
|
|
513
|
-
function truthRead(el) {
|
|
514
|
-
return truth(el)
|
|
515
|
-
? laneValueOf(el)
|
|
516
|
-
: el._config & CONFIG_GUESS && el._pendingValue !== NOT_PENDING
|
|
517
|
-
? el._pendingValue
|
|
518
|
-
: el._value;
|
|
519
|
-
}
|
|
520
|
-
/** A guess confirmed: its lane's truth readers re-read — of the lane's
|
|
521
|
-
* derivations too, which may be the truth now (#3943). */
|
|
522
|
-
function truthConfirmed(l) {
|
|
523
|
-
for (let i = 0; i < l._nodes.length; i++)
|
|
524
|
-
for (let s = l._nodes[i]._subs; s !== null; s = s._nextSub)
|
|
525
|
-
if (s._sub._config & CONFIG_AUTHORITATIVE) enqueueSub(s._sub);
|
|
526
|
-
}
|
|
527
|
-
/** A lane node's value is the truth when every guess beneath it is
|
|
528
|
-
* confirmed — its source staged a value equal to the guess — and nothing
|
|
529
|
-
* beneath it is held in the parent: same inputs, same value. */
|
|
530
|
-
function truth(n) {
|
|
531
|
-
if (n._config & CONFIG_GUESS)
|
|
532
|
-
return n._pendingValue !== NOT_PENDING && n._equals && n._equals(n._x._lane, n._pendingValue);
|
|
533
|
-
if (n._pendingValue !== NOT_PENDING) return;
|
|
534
|
-
if (n._config & CONFIG_OVERRIDE)
|
|
535
|
-
for (let d = n._deps; d; d = d._nextDep) if (!truth(d._dep)) return;
|
|
536
|
-
return 1;
|
|
537
|
-
}
|
|
538
566
|
/** A lane's end, two ways. `into === null`: the parent landed — the lane's
|
|
539
567
|
* values are the truth now: a guess lands the truth staged beneath it or
|
|
540
568
|
* reverts to the one it covered (`_value`, untouched by the guess); a
|
|
@@ -669,9 +697,11 @@ function dissolveLane(l, into, except) {
|
|
|
669
697
|
function laneRead(c, el) {
|
|
670
698
|
const guess = el._config & CONFIG_GUESS;
|
|
671
699
|
if (c !== null && c._config & CONFIG_AUTHORITATIVE) return GlobalQueue._truth(el);
|
|
700
|
+
const status = el._statusFlags;
|
|
701
|
+
// An errored member's lane value predates its error; the error is the answer (#4106).
|
|
702
|
+
if (!guess && status & STATUS_ERROR) return NOT_PENDING;
|
|
672
703
|
if (c === null) return laneValueOf(el);
|
|
673
704
|
const l = txOf(el);
|
|
674
|
-
const status = el._statusFlags;
|
|
675
705
|
if (!tracking || c._config & CONFIG_CHILDREN_FORBIDDEN) {
|
|
676
706
|
// An untracked read inside lane work derives from the lane like a
|
|
677
707
|
// tracked one, minus the subscription — `untrack` is about
|
|
@@ -898,20 +928,16 @@ function answered(el) {
|
|
|
898
928
|
}
|
|
899
929
|
return q;
|
|
900
930
|
}
|
|
901
|
-
GlobalQueue._laneRead = laneRead;
|
|
902
|
-
GlobalQueue._laneStage = laneStage;
|
|
903
|
-
GlobalQueue._laneOutcome = laneOutcome;
|
|
904
|
-
GlobalQueue._laneWrite = laneWrite;
|
|
905
931
|
/** The lanes alive before this seam's guesses opened theirs (a prefix of
|
|
906
932
|
* `lanes`; nothing dissolves one between here and the seam loop): a lane
|
|
907
933
|
* born at the seam has no work to judge yet (its passes run next round) —
|
|
908
934
|
* it is neither shown nor blocked until it has. */
|
|
909
935
|
let judged = 0;
|
|
910
|
-
|
|
936
|
+
const applyGuessesAt = parent => {
|
|
911
937
|
judged = lanes.length;
|
|
912
938
|
if (pendingGuesses.length !== 0) applyGuesses(parent);
|
|
913
939
|
};
|
|
914
|
-
|
|
940
|
+
const laneSeams = leaks => {
|
|
915
941
|
// Passes that read a write the parked frame holds as the screen
|
|
916
942
|
// (`stagedReaders`: lane work, a verdict reader before the verdict): they
|
|
917
943
|
// re-derive on the committed world next round (and register as stale
|
|
@@ -949,8 +975,17 @@ GlobalQueue._laneSeams = leaks => {
|
|
|
949
975
|
for (let k = Math.min(judged, lanes.length) - 1; k >= 0; k--)
|
|
950
976
|
laneSeam(lanes[k], leaked.indexOf(lanes[k]) !== -1);
|
|
951
977
|
};
|
|
952
|
-
|
|
953
|
-
|
|
978
|
+
installLaneHooks(
|
|
979
|
+
laneRead,
|
|
980
|
+
laneStage,
|
|
981
|
+
laneOutcome,
|
|
982
|
+
laneWrite,
|
|
983
|
+
applyGuessesAt,
|
|
984
|
+
laneSeams,
|
|
985
|
+
() => lanes.length !== 0 && laneCorrections(),
|
|
986
|
+
endLanes,
|
|
987
|
+
lanesBlocked
|
|
988
|
+
);
|
|
954
989
|
GlobalQueue._laneGuesses = t => {
|
|
955
990
|
const out = [];
|
|
956
991
|
for (let i = 0; i < lanes.length; i++) {
|
|
@@ -962,7 +997,6 @@ GlobalQueue._laneGuesses = t => {
|
|
|
962
997
|
}
|
|
963
998
|
return out;
|
|
964
999
|
};
|
|
965
|
-
GlobalQueue._lanesBlocked = lanesBlocked;
|
|
966
1000
|
GlobalQueue._verdictLane = verdictLane;
|
|
967
1001
|
|
|
968
1002
|
// Verdicts (plan sec. 28 replay, 2026-10-02; maintainer, 2026-10-01: verdicts are
|
|
@@ -1116,7 +1150,7 @@ function heldNotFinal(owner) {
|
|
|
1116
1150
|
!latestActive &&
|
|
1117
1151
|
!owner._x._reask &&
|
|
1118
1152
|
!(owner._statusFlags & STATUS_UNINITIALIZED) &&
|
|
1119
|
-
(!owner._equals || !owner._equals(owner._value, owner._pendingValue))
|
|
1153
|
+
(!!owner._x._shownError || !owner._equals || !owner._equals(owner._value, owner._pendingValue))
|
|
1120
1154
|
);
|
|
1121
1155
|
}
|
|
1122
1156
|
/** `isPending`'s answer for an unheld node whose flight is up (cause ii). */
|
|
@@ -1160,6 +1194,38 @@ function latest(fn) {
|
|
|
1160
1194
|
setVerdict(prevVerdict);
|
|
1161
1195
|
}
|
|
1162
1196
|
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Reports whether what `fn` reads is still waiting on an update. Read the
|
|
1199
|
+
* values in question inside `fn`: the probe answers `true` while any of
|
|
1200
|
+
* them depends on a change that is in flight — a write or an async source
|
|
1201
|
+
* that has started producing a new value whose result has not landed yet —
|
|
1202
|
+
* and `false` once the latest value is the one on screen. The answer is
|
|
1203
|
+
* tracked like any read, so a memo or effect that calls `isPending` re-runs
|
|
1204
|
+
* when it flips.
|
|
1205
|
+
*
|
|
1206
|
+
* A read that has no value yet is loading, not pending: inside `fn` it
|
|
1207
|
+
* follows the surrounding `Loading` path like any other read, and the probe
|
|
1208
|
+
* reports only updates to a value that already exists. A bare `refresh()`
|
|
1209
|
+
* does not flip `isPending`; pair it with `affects()` to make the re-ask
|
|
1210
|
+
* visible.
|
|
1211
|
+
*
|
|
1212
|
+
* @param fn expression to probe; read the values in question inside it
|
|
1213
|
+
*
|
|
1214
|
+
* @returns `true` while a read inside `fn` depends on a change that has not committed yet
|
|
1215
|
+
*
|
|
1216
|
+
* @example
|
|
1217
|
+
* ```ts
|
|
1218
|
+
* const [query, setQuery] = createSignal("");
|
|
1219
|
+
* const results = createMemo(() => searchProducts(query()));
|
|
1220
|
+
* const stale = createMemo(() => isPending(results));
|
|
1221
|
+
*
|
|
1222
|
+
* setQuery("shoes");
|
|
1223
|
+
* // stale() is true until the new results land, while results() keeps
|
|
1224
|
+
* // serving the previous list — dim it instead of showing a spinner.
|
|
1225
|
+
* ```
|
|
1226
|
+
*
|
|
1227
|
+
* @description https://v2.solidjs.com/reference/solid-js/reactivity/is-pending
|
|
1228
|
+
*/
|
|
1163
1229
|
function isPending(fn) {
|
|
1164
1230
|
markVerdictReader(1);
|
|
1165
1231
|
const prevProbing = probing;
|
|
@@ -1186,12 +1252,17 @@ function isPending(fn) {
|
|
|
1186
1252
|
setVerdict(prevVerdict);
|
|
1187
1253
|
}
|
|
1188
1254
|
}
|
|
1255
|
+
/** The staging, or the staged error thrown (D-41). */
|
|
1256
|
+
function staged(el) {
|
|
1257
|
+
if (el._config & CONFIG_STAGED_ERROR) throw el._pendingValue;
|
|
1258
|
+
return el._pendingValue;
|
|
1259
|
+
}
|
|
1189
1260
|
/** `latest()` of a held node: the flushed staging — the proposal — and, for
|
|
1190
1261
|
* a tracked reader of a rewrite not yet flushed, the late-linker re-run in
|
|
1191
1262
|
* the flush that carries it. */
|
|
1192
1263
|
function heldLatest(el, c) {
|
|
1193
1264
|
const u = unflushedValue(el);
|
|
1194
|
-
if (u === NOT_PENDING) return el
|
|
1265
|
+
if (u === NOT_PENDING) return staged(el);
|
|
1195
1266
|
if (c !== null) markLateLinker(c);
|
|
1196
1267
|
return u;
|
|
1197
1268
|
}
|
|
@@ -1239,7 +1310,10 @@ function verdictValue(el, c) {
|
|
|
1239
1310
|
probeFound = true;
|
|
1240
1311
|
if (tracked && flushTransaction !== null) verdictRead(c, flushTransaction);
|
|
1241
1312
|
}
|
|
1242
|
-
if (
|
|
1313
|
+
if (
|
|
1314
|
+
el._config & CONFIG_OVERRIDE &&
|
|
1315
|
+
!(owner._statusFlags & STATUS_ERROR && !(el._config & CONFIG_GUESS))
|
|
1316
|
+
) {
|
|
1243
1317
|
const l = txOf(el);
|
|
1244
1318
|
if (tracked) verdictRead(c, l._parent ?? l);
|
|
1245
1319
|
const flying = (owner._statusFlags & STATUS_PENDING) !== 0 && !quietPending(owner);
|
|
@@ -1304,7 +1378,7 @@ function verdictValue(el, c) {
|
|
|
1304
1378
|
// has not shown content) and answers for the commit, where it is final.
|
|
1305
1379
|
if (el._pendingValue !== NOT_PENDING && !(owner._statusFlags & STATUS_PENDING)) {
|
|
1306
1380
|
if (probing && owned) joinPass(c, el);
|
|
1307
|
-
return el
|
|
1381
|
+
return staged(el);
|
|
1308
1382
|
}
|
|
1309
1383
|
} else if (el._pendingValue !== NOT_PENDING) {
|
|
1310
1384
|
if (owner._statusFlags & STATUS_PENDING && !uninitialized)
|
|
@@ -1321,7 +1395,7 @@ function verdictValue(el, c) {
|
|
|
1321
1395
|
// A28: an unflushed write is not what `latest` serves; a tracked
|
|
1322
1396
|
// reader derived from the flushed world runs again in the carrying
|
|
1323
1397
|
// flush.
|
|
1324
|
-
if (globalQueue._running) return el
|
|
1398
|
+
if (globalQueue._running) return staged(el);
|
|
1325
1399
|
if (c !== null) markLateLinker(c);
|
|
1326
1400
|
}
|
|
1327
1401
|
return el._value;
|
|
@@ -1343,7 +1417,7 @@ function verdictValue(el, c) {
|
|
|
1343
1417
|
// Installed at module evaluation — present exactly when something imports
|
|
1344
1418
|
// `latest` or `isPending`. (`read` dispatches to `verdictValue` through
|
|
1345
1419
|
// `verdict`, set while a window is open.)
|
|
1346
|
-
|
|
1420
|
+
installVerdictHooks(observeFlight);
|
|
1347
1421
|
/** A store read inside an untracked `isPending` probe found a live mark on
|
|
1348
1422
|
* a record with no node to carry it (store/affects.ts): not final. */
|
|
1349
1423
|
GlobalQueue._witnessMark = () => {
|
|
@@ -1589,7 +1663,7 @@ let externalSourceConfig = null;
|
|
|
1589
1663
|
* ```
|
|
1590
1664
|
*/
|
|
1591
1665
|
// Wires a freshly created computed through the active external-source bridge.
|
|
1592
|
-
// Lives here (installed
|
|
1666
|
+
// Lives here (installed as a scheduler hook while a config is active) rather than
|
|
1593
1667
|
// inline in core: esbuild cannot literal-track the mutable config binding the
|
|
1594
1668
|
// way rollup does, so an inline `if (externalSourceConfig)` block ships in
|
|
1595
1669
|
// every bundle even though only enableExternalSource() can make it reachable.
|
|
@@ -1611,8 +1685,8 @@ function externalUntrack(fn) {
|
|
|
1611
1685
|
// removed on reset) so core's presence checks stay equivalent to the old
|
|
1612
1686
|
// `externalSourceConfig` truthiness checks.
|
|
1613
1687
|
function syncExternalHooks() {
|
|
1614
|
-
|
|
1615
|
-
|
|
1688
|
+
if (externalSourceConfig) setExternalHooks(wireExternalSource, externalUntrack);
|
|
1689
|
+
else setExternalHooks(undefined, undefined);
|
|
1616
1690
|
}
|
|
1617
1691
|
function enableExternalSource(config) {
|
|
1618
1692
|
const { factory, untrack: untrackFn = fn => fn() } = config;
|
|
@@ -1638,6 +1712,61 @@ function enableExternalSource(config) {
|
|
|
1638
1712
|
syncExternalHooks();
|
|
1639
1713
|
}
|
|
1640
1714
|
|
|
1715
|
+
// Deferred hydration, dependency edges: plain-signal identities.
|
|
1716
|
+
// While on (`setSignalIdentities`; the server-components client turns it on
|
|
1717
|
+
// before hydrating), a plain signal created under an id-bearing owner is
|
|
1718
|
+
// named `<owner id>~<n>`, n counting the plain signals (and plain store
|
|
1719
|
+
// roots: `createStore` over an object, store/edges.ts) created under that
|
|
1720
|
+
// owner in its current pass (transparent owners defer to the nearest
|
|
1721
|
+
// id-bearing one, as child ids do). The server names the same signal the
|
|
1722
|
+
// same way and restarts the count on every re-run, so an edge it shipped (a
|
|
1723
|
+
// slot fill read it) matches the client node; the write hook receives the
|
|
1724
|
+
// node and looks its name up (`signalName`). The count restarts when the owner's pass
|
|
1725
|
+
// generation (`_depGen`, bumped by every recompute) moves on. Only the
|
|
1726
|
+
// frames client imports this module, so nothing here ships without it.
|
|
1727
|
+
// Names live in a side table, not on the node: frames reads them across the
|
|
1728
|
+
// package boundary, where a mangled field would not survive.
|
|
1729
|
+
let counts = null;
|
|
1730
|
+
const names = new WeakMap();
|
|
1731
|
+
/** @internal The identity of a plain signal named while identities were on. */
|
|
1732
|
+
function signalName(node) {
|
|
1733
|
+
return node === undefined ? undefined : names.get(node);
|
|
1734
|
+
}
|
|
1735
|
+
function identify(node) {
|
|
1736
|
+
let o = getOwner();
|
|
1737
|
+
if (!o) return;
|
|
1738
|
+
while (o._config & CONFIG_TRANSPARENT && o._parent) o = o._parent;
|
|
1739
|
+
if (o.id == null) return;
|
|
1740
|
+
const gen = o._depGen;
|
|
1741
|
+
let c = counts.get(o);
|
|
1742
|
+
if (c === undefined || c[0] !== gen) counts.set(o, (c = [gen, 0]));
|
|
1743
|
+
const sid = o.id + "~" + c[1]++;
|
|
1744
|
+
// A store root named twice (one object made into two stores, or a store
|
|
1745
|
+
// handed to `createStore`) is one target under two server names: its
|
|
1746
|
+
// writes cannot be attributed (an empty name).
|
|
1747
|
+
names.set(node, names.has(node) ? "" : sid);
|
|
1748
|
+
}
|
|
1749
|
+
/** @internal Name plain signals from now on (`on`), or stop. */
|
|
1750
|
+
function setSignalIdentities(on) {
|
|
1751
|
+
counts = on ? counts || new WeakMap() : null;
|
|
1752
|
+
signalHook = on ? identify : null;
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* @internal Called with every plain signal `createSignal` makes, and every
|
|
1756
|
+
* plain store root `createStore` makes, while set (the namer above). Never
|
|
1757
|
+
* set unless `setSignalIdentities` is imported, so a bundle without it keeps
|
|
1758
|
+
* only a null check.
|
|
1759
|
+
*/
|
|
1760
|
+
let signalHook = null;
|
|
1761
|
+
/**
|
|
1762
|
+
* @internal Dev only: `watch` is called with every node read while set (the
|
|
1763
|
+
* dev hook `onRead`) — the frames client's identity-drift check watches the
|
|
1764
|
+
* named signals a deferred occurrence reads as it mounts. A no-op in prod.
|
|
1765
|
+
*/
|
|
1766
|
+
function setReadWatch(watch) {
|
|
1767
|
+
DEV$1.hooks.onRead = watch || undefined;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1641
1770
|
/**
|
|
1642
1771
|
* Context provides a form of dependency injection. It is used to save from needing to pass
|
|
1643
1772
|
* data as props through intermediate components. This function creates a new context object
|
|
@@ -1798,7 +1927,7 @@ function runEffect(node, type) {
|
|
|
1798
1927
|
// hidden subtree. The synchronous first render on creation is not a queued
|
|
1799
1928
|
// run and goes through. `_modified` stays set; the boundary re-queues the
|
|
1800
1929
|
// run (`release`).
|
|
1801
|
-
if (
|
|
1930
|
+
if (heldRunHook?.(node)) return;
|
|
1802
1931
|
// Error arm (#2840), user effects only: a compute-phase error that is still
|
|
1803
1932
|
// the node's settled state at effect time runs the bundle's error handler in
|
|
1804
1933
|
// this same imperative, writable scope. Unwrap the StatusError used for
|
|
@@ -1893,7 +2022,7 @@ function trackedEffect(fn, options) {
|
|
|
1893
2022
|
// pass, but several passes in one flush each enqueue `_run` into the same
|
|
1894
2023
|
// user queue, and this gate is what collapses them into one run.
|
|
1895
2024
|
if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
|
|
1896
|
-
if (
|
|
2025
|
+
if (heldRunHook?.(node)) return;
|
|
1897
2026
|
setTrackedQueueCallback(true);
|
|
1898
2027
|
try {
|
|
1899
2028
|
node._modified = false;
|
|
@@ -2024,6 +2153,155 @@ function reportClientError(error, owner, thrower) {
|
|
|
2024
2153
|
}
|
|
2025
2154
|
}
|
|
2026
2155
|
|
|
2156
|
+
/**
|
|
2157
|
+
* Ownership stamp (#3360): every backing the store ALLOCATES (CoW clones,
|
|
2158
|
+
* privatized committed backings) carries its owning target under this
|
|
2159
|
+
* enumerable symbol. One property write replaces the two weak-collection
|
|
2160
|
+
* registrations (ownership set + raw→target map) a fresh object used to pay
|
|
2161
|
+
* per draft — V8's identity-hash + ephemeron cost dominated the one-key
|
|
2162
|
+
* write floor. Enumerable so a spread copy (the plain-data clone path) stays
|
|
2163
|
+
* on the fast path and carries the stamp along.
|
|
2164
|
+
*
|
|
2165
|
+
* Owned backings are never user-reachable (`snapshot` copies them, the traps
|
|
2166
|
+
* hide the key), so every raw key walk in the store must skip `$OWNER`, and
|
|
2167
|
+
* ownership is answered by `isOwned` — a user object never carries it.
|
|
2168
|
+
* Overlay drafts (`Object.create(v)` over an owned `v`) inherit the stamp.
|
|
2169
|
+
*/
|
|
2170
|
+
const $OWNER = Symbol("STORE_OWNER");
|
|
2171
|
+
/** raw → target for plain (family-less) stores. Family stores use their own
|
|
2172
|
+
* map so one raw can wrap once per family (§7b). */
|
|
2173
|
+
const storeLookup = new WeakMap();
|
|
2174
|
+
function setLookup(raw, t) {
|
|
2175
|
+
(t.fam?.map ?? storeLookup).set(raw, t);
|
|
2176
|
+
}
|
|
2177
|
+
function isOwned(raw) {
|
|
2178
|
+
return raw[$OWNER] !== undefined;
|
|
2179
|
+
}
|
|
2180
|
+
/** The target wrapping `raw` in `fam` (or the plain lookup). An owned raw
|
|
2181
|
+
* answers from its stamp; a user raw from the map. */
|
|
2182
|
+
function lookupTarget(raw, fam) {
|
|
2183
|
+
const owner = raw[$OWNER];
|
|
2184
|
+
return owner !== undefined && owner.fam === fam ? owner : (fam?.map ?? storeLookup).get(raw);
|
|
2185
|
+
}
|
|
2186
|
+
function devAssertNeverUserMutation(target) {
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
/** Sticky descendants flag up the parent chain (§6d): adoption descends
|
|
2190
|
+
* into a changed child pair only where a target with nodes exists below. */
|
|
2191
|
+
function markDescendants(target) {
|
|
2192
|
+
let t = target;
|
|
2193
|
+
while (t && !t.d) {
|
|
2194
|
+
t.d = true;
|
|
2195
|
+
t = t.u;
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
/** Observe tier: the owner in scope at `createStore` (attribution names and
|
|
2199
|
+
* owner paths); the `_owner` write never reaches the proxy. */
|
|
2200
|
+
const storeOwners = new WeakMap();
|
|
2201
|
+
/** Brand keys the proxy answers directly (never user data). */
|
|
2202
|
+
const $TRACK = Symbol("STORE_TRACK"),
|
|
2203
|
+
$TARGET = Symbol("STORE_TARGET"),
|
|
2204
|
+
$PROXY = Symbol("STORE_PROXY"),
|
|
2205
|
+
$RECORD = Symbol("VIEW_RECORD");
|
|
2206
|
+
// ---------------------------------------------------------------------------
|
|
2207
|
+
// raw marking (R42 / RUL-12: a value is never both wrapped and raw)
|
|
2208
|
+
const rawValues = new WeakSet();
|
|
2209
|
+
let rawValuesUsed = false;
|
|
2210
|
+
function isRawValue(value) {
|
|
2211
|
+
return rawValuesUsed && rawValues.has(value);
|
|
2212
|
+
}
|
|
2213
|
+
/** Mark a value so no store ever wraps it (sticky; it reads as a leaf). */
|
|
2214
|
+
function markRaw(value) {
|
|
2215
|
+
if (isWrappable(value)) {
|
|
2216
|
+
rawValuesUsed = true;
|
|
2217
|
+
rawValues.add(value);
|
|
2218
|
+
}
|
|
2219
|
+
return value;
|
|
2220
|
+
}
|
|
2221
|
+
/** A shallow store's ingested record: raw by contract (values served
|
|
2222
|
+
* verbatim, slots replaced by reference). */
|
|
2223
|
+
function markRawOne(v) {
|
|
2224
|
+
if (isWrappable(v)) {
|
|
2225
|
+
if (v[$TARGET] !== undefined) return;
|
|
2226
|
+
if (lookupTarget(v, null) !== undefined)
|
|
2227
|
+
throw new Error(
|
|
2228
|
+
"shallow store: an ingested record is already tracked as a deep store — one value cannot present both wrapped and raw"
|
|
2229
|
+
);
|
|
2230
|
+
rawValuesUsed = true;
|
|
2231
|
+
rawValues.add(v);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
function markRawIngest(container) {
|
|
2235
|
+
if (Array.isArray(container)) {
|
|
2236
|
+
for (let i = 0, len = container.length; i < len; i++) markRawOne(container[i]);
|
|
2237
|
+
} else {
|
|
2238
|
+
for (const k in container) markRawOne(container[k]);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
// ---------------------------------------------------------------------------
|
|
2242
|
+
// wrappability
|
|
2243
|
+
const OBJECT_PROTO = Object.prototype;
|
|
2244
|
+
const wrappableProtos = new WeakMap();
|
|
2245
|
+
function isWrappable(obj) {
|
|
2246
|
+
if (obj == null || typeof obj !== "object" || Object.isFrozen(obj)) return false;
|
|
2247
|
+
const proto = Object.getPrototypeOf(obj);
|
|
2248
|
+
if (proto === OBJECT_PROTO || proto === null) return true;
|
|
2249
|
+
if (Array.isArray(obj)) return true;
|
|
2250
|
+
let wrappable = wrappableProtos.get(proto);
|
|
2251
|
+
if (wrappable === undefined) {
|
|
2252
|
+
wrappable =
|
|
2253
|
+
Object.prototype.toString.call(obj) === "[object Object]" &&
|
|
2254
|
+
(typeof Node === "undefined" || !(obj instanceof Node));
|
|
2255
|
+
wrappableProtos.set(proto, wrappable);
|
|
2256
|
+
}
|
|
2257
|
+
return wrappable;
|
|
2258
|
+
}
|
|
2259
|
+
function ownEnumerableKeys(o) {
|
|
2260
|
+
return Reflect.ownKeys(o).filter(k => Object.prototype.propertyIsEnumerable.call(o, k));
|
|
2261
|
+
}
|
|
2262
|
+
// ---------------------------------------------------------------------------
|
|
2263
|
+
// write override: a projection's draft ops carry it (the derive is the
|
|
2264
|
+
// author — its writes must land outside the sync setter scope too, in a
|
|
2265
|
+
// continuation after an `await`/`yield`). Projections return in S3; the
|
|
2266
|
+
// traps already honour it.
|
|
2267
|
+
let writeOverride = false;
|
|
2268
|
+
function setWriteOverride(value) {
|
|
2269
|
+
writeOverride = value;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
/**
|
|
2273
|
+
* Deferred hydration, dependency edges for stores. Only the frames client
|
|
2274
|
+
* imports this module, so nothing here ships without it.
|
|
2275
|
+
*
|
|
2276
|
+
* A plain store root (`createStore` over an object) is named like a plain
|
|
2277
|
+
* signal (core/identities.ts: `<owner id>~<n>`, on the same count), and the
|
|
2278
|
+
* whole store goes by that one name: the server records it for any read of
|
|
2279
|
+
* the store, at any depth, and a setter call on the store — on its root or
|
|
2280
|
+
* on a proxy nested in it — is a write to it. Its readers mount at the
|
|
2281
|
+
* setter's entry, before the draft runs, as a plain signal's do.
|
|
2282
|
+
*/
|
|
2283
|
+
/**
|
|
2284
|
+
* @internal The name of the store `x` belongs to — `x` a store proxy (a
|
|
2285
|
+
* setter's entry) or one of its property nodes (the setter notifying it) —
|
|
2286
|
+
* or undefined when it has none or it cannot be placed: a projection or
|
|
2287
|
+
* optimistic family, a store chained over another store's proxy, a root
|
|
2288
|
+
* named twice, or a proxy reached through a store other than the one it
|
|
2289
|
+
* was named as the root of.
|
|
2290
|
+
*/
|
|
2291
|
+
function storeName(x) {
|
|
2292
|
+
if (x === undefined) return undefined;
|
|
2293
|
+
let t = x._config & CONFIG_SLOT_NODE ? x._host : x[$TARGET];
|
|
2294
|
+
if (t === undefined) return undefined;
|
|
2295
|
+
// Up to the root; a target on the way named as a root of its own is one
|
|
2296
|
+
// object in two stores.
|
|
2297
|
+
while (t.u !== null) {
|
|
2298
|
+
if (t.fam !== null || t.ch || signalName(t) !== undefined) return undefined;
|
|
2299
|
+
t = t.u;
|
|
2300
|
+
}
|
|
2301
|
+
if (t.fam !== null || t.ch) return undefined;
|
|
2302
|
+
return signalName(t) || undefined;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2027
2305
|
/**
|
|
2028
2306
|
* Low-level reactive-cleanup primitive. Registers a callback that runs when
|
|
2029
2307
|
* the surrounding owner is disposed.
|
|
@@ -2112,6 +2390,7 @@ function createSignal(first, second) {
|
|
|
2112
2390
|
}
|
|
2113
2391
|
const node = signal(first, second);
|
|
2114
2392
|
registerGraph(node, getOwner());
|
|
2393
|
+
if (signalHook !== null) signalHook(node);
|
|
2115
2394
|
return [accessor(node), setSignal.bind(null, node)];
|
|
2116
2395
|
}
|
|
2117
2396
|
function createOptimistic(first, second) {
|
|
@@ -2193,7 +2472,7 @@ function createMemo(compute, options) {
|
|
|
2193
2472
|
* );
|
|
2194
2473
|
* ```
|
|
2195
2474
|
*
|
|
2196
|
-
* @description https://
|
|
2475
|
+
* @description https://v2.solidjs.com/reference/solid-js/reactivity/create-effect
|
|
2197
2476
|
*/
|
|
2198
2477
|
function createEffect(compute, effectFn, options) {
|
|
2199
2478
|
if (effectFn === undefined) {
|
|
@@ -2242,7 +2521,7 @@ function createEffect(compute, effectFn, options) {
|
|
|
2242
2521
|
* }
|
|
2243
2522
|
* ```
|
|
2244
2523
|
*
|
|
2245
|
-
* @description https://
|
|
2524
|
+
* @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-render-effect
|
|
2246
2525
|
*/
|
|
2247
2526
|
function createRenderEffect(compute, effectFn, options) {
|
|
2248
2527
|
effect(compute, effectFn, undefined, options);
|
|
@@ -2290,7 +2569,7 @@ function createRenderEffect(compute, effectFn, options) {
|
|
|
2290
2569
|
* });
|
|
2291
2570
|
* ```
|
|
2292
2571
|
*
|
|
2293
|
-
* @description https://
|
|
2572
|
+
* @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-tracked-effect
|
|
2294
2573
|
*/
|
|
2295
2574
|
function createTrackedEffect(compute, options) {
|
|
2296
2575
|
trackedEffect(compute, options);
|
|
@@ -2319,7 +2598,7 @@ function createTrackedEffect(compute, options) {
|
|
|
2319
2598
|
* setCount(1); // logs once, reaction re-armed for next change
|
|
2320
2599
|
* ```
|
|
2321
2600
|
*
|
|
2322
|
-
* @description https://
|
|
2601
|
+
* @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-reaction
|
|
2323
2602
|
*/
|
|
2324
2603
|
function createReaction(effectFn, options) {
|
|
2325
2604
|
let cl = undefined;
|
|
@@ -2389,15 +2668,21 @@ function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction)
|
|
|
2389
2668
|
// after: the first pass's own error is the promise's rejection.)
|
|
2390
2669
|
computed(
|
|
2391
2670
|
() => {
|
|
2392
|
-
let v
|
|
2671
|
+
let v,
|
|
2672
|
+
failed = false;
|
|
2393
2673
|
try {
|
|
2394
2674
|
v = fn();
|
|
2395
2675
|
} catch (e) {
|
|
2396
2676
|
if (e instanceof NotReadyError) throw e;
|
|
2397
|
-
|
|
2398
|
-
|
|
2677
|
+
// An error is an answer (D-41): delivered by the same frame rule.
|
|
2678
|
+
v = unwrapStatusError(e);
|
|
2679
|
+
failed = true;
|
|
2399
2680
|
}
|
|
2400
2681
|
const c = getOwner();
|
|
2682
|
+
// A truth reader waits out a `createDeferred` flight its value derives
|
|
2683
|
+
// from, which serves everyone else the stale answer (D-40).
|
|
2684
|
+
const lag = config & CONFIG_AUTHORITATIVE && GlobalQueue._lagging?.(c);
|
|
2685
|
+
if (lag) throw new NotReadyError(lag);
|
|
2401
2686
|
// (Outside a flush a mainline pass joins through `passTx`.)
|
|
2402
2687
|
const joined = flushTransaction ?? passTx;
|
|
2403
2688
|
if (
|
|
@@ -2408,6 +2693,7 @@ function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction)
|
|
|
2408
2693
|
staleReader(c, resolveTx(joined));
|
|
2409
2694
|
return;
|
|
2410
2695
|
}
|
|
2696
|
+
if (failed) return void queueMicrotask(() => onError(v));
|
|
2411
2697
|
inPass?.(v);
|
|
2412
2698
|
queueMicrotask(() => onValue(v));
|
|
2413
2699
|
},
|
|
@@ -2769,122 +3055,6 @@ function onSettled(callback) {
|
|
|
2769
3055
|
});
|
|
2770
3056
|
}
|
|
2771
3057
|
|
|
2772
|
-
/**
|
|
2773
|
-
* Ownership stamp (#3360): every backing the store ALLOCATES (CoW clones,
|
|
2774
|
-
* privatized committed backings) carries its owning target under this
|
|
2775
|
-
* enumerable symbol. One property write replaces the two weak-collection
|
|
2776
|
-
* registrations (ownership set + raw→target map) a fresh object used to pay
|
|
2777
|
-
* per draft — V8's identity-hash + ephemeron cost dominated the one-key
|
|
2778
|
-
* write floor. Enumerable so a spread copy (the plain-data clone path) stays
|
|
2779
|
-
* on the fast path and carries the stamp along.
|
|
2780
|
-
*
|
|
2781
|
-
* Owned backings are never user-reachable (`snapshot` copies them, the traps
|
|
2782
|
-
* hide the key), so every raw key walk in the store must skip `$OWNER`, and
|
|
2783
|
-
* ownership is answered by `isOwned` — a user object never carries it.
|
|
2784
|
-
* Overlay drafts (`Object.create(v)` over an owned `v`) inherit the stamp.
|
|
2785
|
-
*/
|
|
2786
|
-
const $OWNER = Symbol("STORE_OWNER");
|
|
2787
|
-
/** raw → target for plain (family-less) stores. Family stores use their own
|
|
2788
|
-
* map so one raw can wrap once per family (§7b). */
|
|
2789
|
-
const storeLookup = new WeakMap();
|
|
2790
|
-
function setLookup(raw, t) {
|
|
2791
|
-
(t.fam?.map ?? storeLookup).set(raw, t);
|
|
2792
|
-
}
|
|
2793
|
-
function isOwned(raw) {
|
|
2794
|
-
return raw[$OWNER] !== undefined;
|
|
2795
|
-
}
|
|
2796
|
-
/** The target wrapping `raw` in `fam` (or the plain lookup). An owned raw
|
|
2797
|
-
* answers from its stamp; a user raw from the map. */
|
|
2798
|
-
function lookupTarget(raw, fam) {
|
|
2799
|
-
const owner = raw[$OWNER];
|
|
2800
|
-
return owner !== undefined && owner.fam === fam ? owner : (fam?.map ?? storeLookup).get(raw);
|
|
2801
|
-
}
|
|
2802
|
-
function devAssertNeverUserMutation(target) {
|
|
2803
|
-
return;
|
|
2804
|
-
}
|
|
2805
|
-
/** Sticky descendants flag up the parent chain (§6d): adoption descends
|
|
2806
|
-
* into a changed child pair only where a target with nodes exists below. */
|
|
2807
|
-
function markDescendants(target) {
|
|
2808
|
-
let t = target;
|
|
2809
|
-
while (t && !t.d) {
|
|
2810
|
-
t.d = true;
|
|
2811
|
-
t = t.u;
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
/** Observe tier: the owner in scope at `createStore` (attribution names and
|
|
2815
|
-
* owner paths); the `_owner` write never reaches the proxy. */
|
|
2816
|
-
const storeOwners = new WeakMap();
|
|
2817
|
-
/** Brand keys the proxy answers directly (never user data). */
|
|
2818
|
-
const $TRACK = Symbol("STORE_TRACK"),
|
|
2819
|
-
$TARGET = Symbol("STORE_TARGET"),
|
|
2820
|
-
$PROXY = Symbol("STORE_PROXY"),
|
|
2821
|
-
$RECORD = Symbol("VIEW_RECORD");
|
|
2822
|
-
// ---------------------------------------------------------------------------
|
|
2823
|
-
// raw marking (R42 / RUL-12: a value is never both wrapped and raw)
|
|
2824
|
-
const rawValues = new WeakSet();
|
|
2825
|
-
let rawValuesUsed = false;
|
|
2826
|
-
function isRawValue(value) {
|
|
2827
|
-
return rawValuesUsed && rawValues.has(value);
|
|
2828
|
-
}
|
|
2829
|
-
/** Mark a value so no store ever wraps it (sticky; it reads as a leaf). */
|
|
2830
|
-
function markRaw(value) {
|
|
2831
|
-
if (isWrappable(value)) {
|
|
2832
|
-
rawValuesUsed = true;
|
|
2833
|
-
rawValues.add(value);
|
|
2834
|
-
}
|
|
2835
|
-
return value;
|
|
2836
|
-
}
|
|
2837
|
-
/** A shallow store's ingested record: raw by contract (values served
|
|
2838
|
-
* verbatim, slots replaced by reference). */
|
|
2839
|
-
function markRawOne(v) {
|
|
2840
|
-
if (isWrappable(v)) {
|
|
2841
|
-
if (v[$TARGET] !== undefined) return;
|
|
2842
|
-
if (lookupTarget(v, null) !== undefined)
|
|
2843
|
-
throw new Error(
|
|
2844
|
-
"shallow store: an ingested record is already tracked as a deep store — one value cannot present both wrapped and raw"
|
|
2845
|
-
);
|
|
2846
|
-
rawValuesUsed = true;
|
|
2847
|
-
rawValues.add(v);
|
|
2848
|
-
}
|
|
2849
|
-
}
|
|
2850
|
-
function markRawIngest(container) {
|
|
2851
|
-
if (Array.isArray(container)) {
|
|
2852
|
-
for (let i = 0, len = container.length; i < len; i++) markRawOne(container[i]);
|
|
2853
|
-
} else {
|
|
2854
|
-
for (const k in container) markRawOne(container[k]);
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
// ---------------------------------------------------------------------------
|
|
2858
|
-
// wrappability
|
|
2859
|
-
const OBJECT_PROTO = Object.prototype;
|
|
2860
|
-
const wrappableProtos = new WeakMap();
|
|
2861
|
-
function isWrappable(obj) {
|
|
2862
|
-
if (obj == null || typeof obj !== "object" || Object.isFrozen(obj)) return false;
|
|
2863
|
-
const proto = Object.getPrototypeOf(obj);
|
|
2864
|
-
if (proto === OBJECT_PROTO || proto === null) return true;
|
|
2865
|
-
if (Array.isArray(obj)) return true;
|
|
2866
|
-
let wrappable = wrappableProtos.get(proto);
|
|
2867
|
-
if (wrappable === undefined) {
|
|
2868
|
-
wrappable =
|
|
2869
|
-
Object.prototype.toString.call(obj) === "[object Object]" &&
|
|
2870
|
-
(typeof Node === "undefined" || !(obj instanceof Node));
|
|
2871
|
-
wrappableProtos.set(proto, wrappable);
|
|
2872
|
-
}
|
|
2873
|
-
return wrappable;
|
|
2874
|
-
}
|
|
2875
|
-
function ownEnumerableKeys(o) {
|
|
2876
|
-
return Reflect.ownKeys(o).filter(k => Object.prototype.propertyIsEnumerable.call(o, k));
|
|
2877
|
-
}
|
|
2878
|
-
// ---------------------------------------------------------------------------
|
|
2879
|
-
// write override: a projection's draft ops carry it (the derive is the
|
|
2880
|
-
// author — its writes must land outside the sync setter scope too, in a
|
|
2881
|
-
// continuation after an `await`/`yield`). Projections return in S3; the
|
|
2882
|
-
// traps already honour it.
|
|
2883
|
-
let writeOverride = false;
|
|
2884
|
-
function setWriteOverride(value) {
|
|
2885
|
-
writeOverride = value;
|
|
2886
|
-
}
|
|
2887
|
-
|
|
2888
3058
|
function mapArray(list, map, options) {
|
|
2889
3059
|
const keyFn = typeof options?.keyed === "function" ? options.keyed : undefined;
|
|
2890
3060
|
const indexes = map.length > 1;
|
|
@@ -3058,6 +3228,79 @@ function commitSmallMove(data, newItems, newLen, srcPos, dstPos, runs) {
|
|
|
3058
3228
|
}
|
|
3059
3229
|
return true;
|
|
3060
3230
|
}
|
|
3231
|
+
/** Stage a PURE REMOVAL without the key map: when the new window is the old
|
|
3232
|
+
* window with items dropped, in order (one-item removals, every-nth, the
|
|
3233
|
+
* table filters), a two-pointer walk lands every survivor in the window
|
|
3234
|
+
* stage with one key call per item and returns the leavers; a shape that is
|
|
3235
|
+
* not an ordered subsequence returns null with the node stage cleared, and
|
|
3236
|
+
* the map path below starts from scratch (it rewrites every window slot of
|
|
3237
|
+
* `temp` / `rows` / `indexes`, so only `tempNodes`' `!== undefined` probe
|
|
3238
|
+
* needs the reset). The stage arrays are scratch — nothing live is touched. */
|
|
3239
|
+
function stageRemoval(data, newItems, start, end, newEnd, temp, tempNodes, rows, indexes) {
|
|
3240
|
+
const key = data._key;
|
|
3241
|
+
const oldItems = data._items;
|
|
3242
|
+
const nodes = data._nodes;
|
|
3243
|
+
const removed = [];
|
|
3244
|
+
let i = start;
|
|
3245
|
+
for (let j = start; j <= newEnd; i++, j++) {
|
|
3246
|
+
const nk = key ? key(newItems[j]) : newItems[j];
|
|
3247
|
+
while (i <= end && (key ? key(oldItems[i]) : oldItems[i]) !== nk) removed.push(nodes[i++]);
|
|
3248
|
+
if (i > end) {
|
|
3249
|
+
tempNodes.fill(undefined);
|
|
3250
|
+
return null;
|
|
3251
|
+
}
|
|
3252
|
+
temp[j] = data._mappings[i];
|
|
3253
|
+
tempNodes[j - start] = nodes[i];
|
|
3254
|
+
if (rows) rows[j - start] = data._rows[i];
|
|
3255
|
+
if (indexes) indexes[j - start] = data._indexes[i];
|
|
3256
|
+
}
|
|
3257
|
+
for (; i <= end; i++) removed.push(nodes[i]);
|
|
3258
|
+
return removed;
|
|
3259
|
+
}
|
|
3260
|
+
/** Land a staged window into the live bookkeeping arrays in place: shift the
|
|
3261
|
+
* suffix by `dif` (a loop walked from the side that never overwrites an
|
|
3262
|
+
* unread slot), take the window from the stage, write survivors' row and
|
|
3263
|
+
* index signals. Its own function so the general path's body stays under
|
|
3264
|
+
* the JIT's optimization budget — the inlined commit measured ~10% slower on
|
|
3265
|
+
* full-window reorders (the small-move note has the same finding). */
|
|
3266
|
+
function commitWindow(data, newItems, newLen, start, end, newEnd, tempNodes, rows, indexes) {
|
|
3267
|
+
const nodes = data._nodes;
|
|
3268
|
+
const liveRows = data._rows;
|
|
3269
|
+
const liveIndexes = data._indexes;
|
|
3270
|
+
const oldLen = data._len;
|
|
3271
|
+
const dif = newLen - oldLen;
|
|
3272
|
+
let i, j;
|
|
3273
|
+
if (dif > 0) {
|
|
3274
|
+
// Grow first: a store past the end from the top is a third slower than
|
|
3275
|
+
// filling a pre-sized tail (measured on 1k refs, 2026-10-15).
|
|
3276
|
+
nodes.length = newLen;
|
|
3277
|
+
if (liveRows) liveRows.length = newLen;
|
|
3278
|
+
if (liveIndexes) liveIndexes.length = newLen;
|
|
3279
|
+
for (i = oldLen - 1, j = newLen - 1; i > end; i--, j--) {
|
|
3280
|
+
nodes[j] = nodes[i];
|
|
3281
|
+
if (liveRows) liveRows[j] = liveRows[i];
|
|
3282
|
+
if (liveIndexes) liveIndexes[j] = liveIndexes[i];
|
|
3283
|
+
}
|
|
3284
|
+
} else if (dif < 0) {
|
|
3285
|
+
for (i = end + 1, j = newEnd + 1; i < oldLen; i++, j++) {
|
|
3286
|
+
nodes[j] = nodes[i];
|
|
3287
|
+
if (liveRows) liveRows[j] = liveRows[i];
|
|
3288
|
+
if (liveIndexes) liveIndexes[j] = liveIndexes[i];
|
|
3289
|
+
}
|
|
3290
|
+
nodes.length = newLen;
|
|
3291
|
+
if (liveRows) liveRows.length = newLen;
|
|
3292
|
+
if (liveIndexes) liveIndexes.length = newLen;
|
|
3293
|
+
}
|
|
3294
|
+
for (j = start; j <= newEnd; j++) {
|
|
3295
|
+
nodes[j] = tempNodes[j - start];
|
|
3296
|
+
if (liveRows) setSignal((liveRows[j] = rows[j - start]), newItems[j]);
|
|
3297
|
+
if (liveIndexes) setSignal((liveIndexes[j] = indexes[j - start]), j);
|
|
3298
|
+
}
|
|
3299
|
+
for (j = newEnd + 1; j < newLen; j++) {
|
|
3300
|
+
if (liveRows) setSignal(liveRows[j], newItems[j]);
|
|
3301
|
+
if (liveIndexes && dif !== 0) setSignal(liveIndexes[j], j);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3061
3304
|
function updateKeyedMap() {
|
|
3062
3305
|
// Untracked reads inside the internal owner resolve via _parentComputed:
|
|
3063
3306
|
// routed through the node, a store-proxy lookup sees pending writes (not a
|
|
@@ -3072,6 +3315,10 @@ function updateKeyedMap() {
|
|
|
3072
3315
|
runWithOwner(this._owner, () => {
|
|
3073
3316
|
let i,
|
|
3074
3317
|
j,
|
|
3318
|
+
// The stage slot a mapper writes its row/index signal into: `j` on the
|
|
3319
|
+
// create-all path, `j - start` on the general path, whose stage arrays
|
|
3320
|
+
// are sized to the changed WINDOW, not the list.
|
|
3321
|
+
slot,
|
|
3075
3322
|
rows,
|
|
3076
3323
|
indexes,
|
|
3077
3324
|
// Mappers write freshly-created row/index signals into the STAGE
|
|
@@ -3079,19 +3326,19 @@ function updateKeyedMap() {
|
|
|
3079
3326
|
mapper = this._rows
|
|
3080
3327
|
? this._byIndex
|
|
3081
3328
|
? () => {
|
|
3082
|
-
rows[
|
|
3083
|
-
return this._map(accessor(rows[
|
|
3329
|
+
rows[slot] = signal(newItems[j], pureOptions);
|
|
3330
|
+
return this._map(accessor(rows[slot]), j);
|
|
3084
3331
|
}
|
|
3085
3332
|
: () => {
|
|
3086
|
-
rows[
|
|
3087
|
-
indexes && (indexes[
|
|
3088
|
-
return this._map(accessor(rows[
|
|
3333
|
+
rows[slot] = signal(newItems[j], pureOptions);
|
|
3334
|
+
indexes && (indexes[slot] = signal(j, pureOptions));
|
|
3335
|
+
return this._map(accessor(rows[slot]), indexes ? accessor(indexes[slot]) : undefined);
|
|
3089
3336
|
}
|
|
3090
3337
|
: this._indexes
|
|
3091
3338
|
? () => {
|
|
3092
3339
|
const item = newItems[j];
|
|
3093
|
-
indexes[
|
|
3094
|
-
return this._map(item, accessor(indexes[
|
|
3340
|
+
indexes[slot] = signal(j, pureOptions);
|
|
3341
|
+
return this._map(item, accessor(indexes[slot]));
|
|
3095
3342
|
}
|
|
3096
3343
|
: () => {
|
|
3097
3344
|
const item = newItems[j];
|
|
@@ -3122,7 +3369,10 @@ function updateKeyedMap() {
|
|
|
3122
3369
|
rows = this._rows && new Array(newLen);
|
|
3123
3370
|
indexes = this._indexes && new Array(newLen);
|
|
3124
3371
|
try {
|
|
3125
|
-
for (j = 0; j < newLen; j++)
|
|
3372
|
+
for (j = 0; j < newLen; j++) {
|
|
3373
|
+
slot = j;
|
|
3374
|
+
mappings[j] = runWithOwner((nodes[j] = createOwner()), mapper);
|
|
3375
|
+
}
|
|
3126
3376
|
} catch (err) {
|
|
3127
3377
|
for (i = 0; i <= j; i++) nodes[i]?.dispose();
|
|
3128
3378
|
throw err;
|
|
@@ -3224,81 +3474,89 @@ function updateKeyedMap() {
|
|
|
3224
3474
|
if (k <= hi && trySmallMove(this, newItems, newLen, start)) return;
|
|
3225
3475
|
}
|
|
3226
3476
|
const dif = newLen - this._len;
|
|
3477
|
+
// WINDOW-SIZED STAGING, IN-PLACE COMMIT (2026-10-15). The strong-abort
|
|
3478
|
+
// contract (#2903) only needs the pass to touch nothing live before
|
|
3479
|
+
// every mapper has succeeded; it never needed four fresh newLen-sized
|
|
3480
|
+
// arrays. Only the changed window [start, newEnd] is staged — the
|
|
3481
|
+
// prefix and suffix are, by construction, the rows that did not move
|
|
3482
|
+
// relative to each other — and at commit the bookkeeping arrays
|
|
3483
|
+
// (`_nodes`, `_rows`, `_indexes`) take the window in place with the
|
|
3484
|
+
// suffix shifted by a plain loop (`copyWithin` is a generic
|
|
3485
|
+
// per-element builtin in V8 — 30× slower than the loop on 1k refs,
|
|
3486
|
+
// measured 2026-10-15). `_mappings` alone is
|
|
3487
|
+
// rebuilt: its fresh identity is the change signal downstream relies
|
|
3488
|
+
// on. A one-row edit on a 500-row list used to allocate ~20 KB of
|
|
3489
|
+
// staging plus a position-sized chain array per commit (uibench's
|
|
3490
|
+
// one-item tree ops at 4–6× Octane, jfb-reorder `removefirst` at
|
|
3491
|
+
// 2.5×); now it stages one slot.
|
|
3492
|
+
const wLen = newEnd - start + 1;
|
|
3227
3493
|
const temp = new Array(newLen);
|
|
3228
|
-
const tempNodes = new Array(
|
|
3229
|
-
rows = this._rows ? new Array(
|
|
3230
|
-
indexes = this._indexes ? new Array(
|
|
3231
|
-
//
|
|
3232
|
-
//
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
tempNodes[j] = this._nodes[i];
|
|
3252
|
-
rows && (rows[j] = this._rows[i]);
|
|
3253
|
-
indexes && (indexes[j] = this._indexes[i]);
|
|
3254
|
-
j = newIndicesNext[j];
|
|
3494
|
+
const tempNodes = new Array(wLen);
|
|
3495
|
+
rows = this._rows ? new Array(wLen) : undefined;
|
|
3496
|
+
indexes = this._indexes ? new Array(wLen) : undefined;
|
|
3497
|
+
// A shrinking pass is tried as a pure removal first (stageRemoval: no
|
|
3498
|
+
// key map, one key call per item); anything else takes the map path.
|
|
3499
|
+
const pure =
|
|
3500
|
+
dif < 0
|
|
3501
|
+
? stageRemoval(this, newItems, start, end, newEnd, temp, tempNodes, rows, indexes)
|
|
3502
|
+
: null;
|
|
3503
|
+
if (pure !== null) {
|
|
3504
|
+
// (The list-churn census below wants removals AND creations in one
|
|
3505
|
+
// pass; a pure removal has none, so no item ledger is kept here.)
|
|
3506
|
+
if (pure.length !== 0) removed = pure;
|
|
3507
|
+
} else {
|
|
3508
|
+
// 0) prepare a map of all indices in the changed window of newItems,
|
|
3509
|
+
// scanning backwards so we encounter them in natural order
|
|
3510
|
+
newIndices = new Map();
|
|
3511
|
+
newIndicesNext = new Array(wLen);
|
|
3512
|
+
for (j = newEnd; j >= start; j--) {
|
|
3513
|
+
item = newItems[j];
|
|
3514
|
+
key = this._key ? this._key(item) : item;
|
|
3515
|
+
i = newIndices.get(key);
|
|
3516
|
+
newIndicesNext[j - start] = i === undefined ? -1 : i;
|
|
3255
3517
|
newIndices.set(key, j);
|
|
3256
|
-
} else {
|
|
3257
|
-
(removed ??= []).push(this._nodes[i]);
|
|
3258
|
-
if (true && attrHooks !== null) (removedItems ??= []).push(item);
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
// 2) create new rows into the temp arrays; an abort disposes only these
|
|
3262
|
-
try {
|
|
3263
|
-
for (j = start; j <= newEnd; j++) {
|
|
3264
|
-
if (tempNodes[j] !== undefined) continue;
|
|
3265
|
-
(created ??= []).push((tempNodes[j] = createOwner()));
|
|
3266
|
-
if (true && attrHooks !== null) (createdItems ??= []).push(newItems[j]);
|
|
3267
|
-
temp[j] = runWithOwner(tempNodes[j], mapper);
|
|
3268
3518
|
}
|
|
3269
|
-
|
|
3270
|
-
if
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
temp[j] = this._mappings[j - dif];
|
|
3288
|
-
tempNodes[j] = this._nodes[j - dif];
|
|
3289
|
-
if (rows) {
|
|
3290
|
-
rows[j] = this._rows[j - dif];
|
|
3291
|
-
write(rows[j], newItems[j]);
|
|
3519
|
+
// 1) step through the old changed window and see if items can be found
|
|
3520
|
+
// in the new set; if so, stage them at their new positions; if not,
|
|
3521
|
+
// queue them for disposal at commit
|
|
3522
|
+
for (i = start; i <= end; i++) {
|
|
3523
|
+
item = this._items[i];
|
|
3524
|
+
key = this._key ? this._key(item) : item;
|
|
3525
|
+
j = newIndices.get(key);
|
|
3526
|
+
if (j !== undefined && j !== -1) {
|
|
3527
|
+
temp[j] = this._mappings[i];
|
|
3528
|
+
tempNodes[j - start] = this._nodes[i];
|
|
3529
|
+
rows && (rows[j - start] = this._rows[i]);
|
|
3530
|
+
indexes && (indexes[j - start] = this._indexes[i]);
|
|
3531
|
+
j = newIndicesNext[j - start];
|
|
3532
|
+
newIndices.set(key, j);
|
|
3533
|
+
} else {
|
|
3534
|
+
(removed ??= []).push(this._nodes[i]);
|
|
3535
|
+
if (true && attrHooks !== null) (removedItems ??= []).push(item);
|
|
3536
|
+
}
|
|
3292
3537
|
}
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3538
|
+
// 2) create new rows into the window stage; an abort disposes only these
|
|
3539
|
+
try {
|
|
3540
|
+
for (j = start; j <= newEnd; j++) {
|
|
3541
|
+
if (tempNodes[j - start] !== undefined) continue;
|
|
3542
|
+
(created ??= []).push((tempNodes[j - start] = createOwner()));
|
|
3543
|
+
if (true && attrHooks !== null) (createdItems ??= []).push(newItems[j]);
|
|
3544
|
+
slot = j - start;
|
|
3545
|
+
temp[j] = runWithOwner(tempNodes[j - start], mapper);
|
|
3546
|
+
}
|
|
3547
|
+
} catch (err) {
|
|
3548
|
+
if (created) for (i = 0; i < created.length; i++) created[i].dispose();
|
|
3549
|
+
throw err;
|
|
3296
3550
|
}
|
|
3297
3551
|
}
|
|
3552
|
+
// 3) commit: the retained prefix and suffix land in the fresh mapped
|
|
3553
|
+
// array; the bookkeeping arrays take the staged window IN PLACE (own
|
|
3554
|
+
// function: kept out of this body on purpose, see commitWindow); then
|
|
3555
|
+
// the exited rows dispose
|
|
3556
|
+
for (i = 0; i < start; i++) temp[i] = this._mappings[i];
|
|
3557
|
+
for (j = newEnd + 1; j < newLen; j++) temp[j] = this._mappings[j - dif];
|
|
3558
|
+
commitWindow(this, newItems, newLen, start, end, newEnd, tempNodes, rows, indexes);
|
|
3298
3559
|
this._mappings = temp;
|
|
3299
|
-
this._nodes = tempNodes;
|
|
3300
|
-
rows && (this._rows = rows);
|
|
3301
|
-
indexes && (this._indexes = indexes);
|
|
3302
3560
|
this._len = newLen;
|
|
3303
3561
|
// save a copy of the mapped items for the next update
|
|
3304
3562
|
this._items = newItems.slice(0);
|
|
@@ -3328,7 +3586,7 @@ function updateKeyedMap() {
|
|
|
3328
3586
|
* const view = repeat(count, i => `Item ${i}`, { fallback: () => "empty" });
|
|
3329
3587
|
* ```
|
|
3330
3588
|
*
|
|
3331
|
-
* @description https://
|
|
3589
|
+
* @description https://v2.solidjs.com/reference/solid-js/advanced/jsx-component-primitives/repeat
|
|
3332
3590
|
*/
|
|
3333
3591
|
function repeat(count, map, options) {
|
|
3334
3592
|
const wrappedMap = options?.name
|
|
@@ -3658,11 +3916,16 @@ function noteNode(target, node, key) {
|
|
|
3658
3916
|
// dispatches CONFIG_SLOT_NODE nodes here (graph.ts) instead of holding a
|
|
3659
3917
|
// per-node closure in a per-node NodeExtension. A dropped node is
|
|
3660
3918
|
// unreachable through the store — a fresh read makes a fresh node.
|
|
3919
|
+
// A staged write — or a lane's value (a guess, S4) — is state only the node
|
|
3920
|
+
// holds: its release waits for the commit that resolves it. So does a leaf of
|
|
3921
|
+
// a held container (#4146): a mainline write it carried is in the held
|
|
3922
|
+
// staging too, where it would read as the hold's until the landing.
|
|
3923
|
+
const slotKept = node =>
|
|
3924
|
+
node._pendingValue !== NOT_PENDING ||
|
|
3925
|
+
node._config & CONFIG_OVERRIDE ||
|
|
3926
|
+
node._host.k?._config & CONFIG_HELD;
|
|
3661
3927
|
setSlotUnobserved(node => {
|
|
3662
|
-
|
|
3663
|
-
// node holds: defer the release to the commit that resolves it.
|
|
3664
|
-
if (node._pendingValue !== NOT_PENDING || node._config & CONFIG_OVERRIDE)
|
|
3665
|
-
deferredReleases.add(node);
|
|
3928
|
+
if (slotKept(node)) deferredReleases.add(node);
|
|
3666
3929
|
else releaseSlot(node);
|
|
3667
3930
|
});
|
|
3668
3931
|
/** A node born while its container has a staging is born from the two
|
|
@@ -3722,7 +3985,9 @@ function getNode(
|
|
|
3722
3985
|
// the hottest store path, and the disabled cost must stay one null
|
|
3723
3986
|
// check (nodes created before enable() stay generically named).
|
|
3724
3987
|
if (attrHooks !== null) {
|
|
3725
|
-
|
|
3988
|
+
const root = storeRoot(target);
|
|
3989
|
+
created._name = (storeNames.get(root) ?? "store") + "." + String(key);
|
|
3990
|
+
created._loc = storeLocs.get(root);
|
|
3726
3991
|
stampNodeOwner(created, target);
|
|
3727
3992
|
}
|
|
3728
3993
|
nodes[key] = node;
|
|
@@ -3775,7 +4040,7 @@ function getContainerNode(target) {
|
|
|
3775
4040
|
* staging: nothing allocates, nothing is swept at the commit (dbmon's tick
|
|
3776
4041
|
* re-created, queued, swept and dropped ~7000 container nodes). If the
|
|
3777
4042
|
* flush parks, the seam materializes and holds the node then
|
|
3778
|
-
* (`
|
|
4043
|
+
* (`storeParkHook`) — the one case the staging home exists for. */
|
|
3779
4044
|
function stageOn(target, pb) {
|
|
3780
4045
|
if (
|
|
3781
4046
|
target.k === null &&
|
|
@@ -3798,7 +4063,7 @@ function deriveHolds(target) {
|
|
|
3798
4063
|
const fw = target.fam?.node;
|
|
3799
4064
|
return fw != null && !userWrite && (fw._config & CONFIG_HELD) !== 0;
|
|
3800
4065
|
}
|
|
3801
|
-
|
|
4066
|
+
const storePark = t => {
|
|
3802
4067
|
for (let i = 0; i < foldList.length; i++) {
|
|
3803
4068
|
const target = foldList[i];
|
|
3804
4069
|
if (target.k === null && target.pb !== null) holdNode(getContainerNode(target), t);
|
|
@@ -3846,11 +4111,16 @@ function stampNodeOwner(created, target) {
|
|
|
3846
4111
|
created._owner = storeRootOwner(target) ?? null;
|
|
3847
4112
|
}
|
|
3848
4113
|
const storeNames = new WeakMap();
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
4114
|
+
// The store's `loc` option, carried onto every property node beside its
|
|
4115
|
+
// label (the same gate: nodes created before the engine is installed carry
|
|
4116
|
+
// neither).
|
|
4117
|
+
const storeLocs = new WeakMap();
|
|
4118
|
+
function nameStore(proxy, name, loc) {
|
|
4119
|
+
if (attrHooks !== null) {
|
|
4120
|
+
const target = proxy[$TARGET];
|
|
4121
|
+
if (name) storeNames.set(target, name);
|
|
4122
|
+
if (loc) storeLocs.set(target, loc);
|
|
4123
|
+
}
|
|
3854
4124
|
}
|
|
3855
4125
|
// ---------------------------------------------------------------------------
|
|
3856
4126
|
// clones (CoW privatization)
|
|
@@ -3934,7 +4204,8 @@ function overlayRebuilds(t, pb) {
|
|
|
3934
4204
|
if (t.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
|
|
3935
4205
|
if (t.del !== null && t.del.size !== 0) return true;
|
|
3936
4206
|
const wk = t.wk;
|
|
3937
|
-
if (wk !== null && wk !== WK_ALL && wk.size <= OVERLAY_REBUILD_MIN_ADDS)
|
|
4207
|
+
if (wk !== null && wk !== WK_ALL && wk.u === undefined && wk.size <= OVERLAY_REBUILD_MIN_ADDS)
|
|
4208
|
+
return false;
|
|
3938
4209
|
const v = t.v;
|
|
3939
4210
|
let adds = 0;
|
|
3940
4211
|
for (const key of Reflect.ownKeys(pb))
|
|
@@ -4164,11 +4435,7 @@ function drainFolds() {
|
|
|
4164
4435
|
}
|
|
4165
4436
|
if (deferredReleases.size !== 0) {
|
|
4166
4437
|
for (const node of deferredReleases) {
|
|
4167
|
-
if (
|
|
4168
|
-
node._pendingValue === NOT_PENDING &&
|
|
4169
|
-
!(node._config & CONFIG_OVERRIDE) &&
|
|
4170
|
-
node._subs === null
|
|
4171
|
-
) {
|
|
4438
|
+
if (node._subs === null && !slotKept(node)) {
|
|
4172
4439
|
deferredReleases.delete(node);
|
|
4173
4440
|
releaseSlot(node);
|
|
4174
4441
|
}
|
|
@@ -4197,10 +4464,10 @@ GlobalQueue._slotCovered = n => {
|
|
|
4197
4464
|
}
|
|
4198
4465
|
return n._value;
|
|
4199
4466
|
};
|
|
4200
|
-
|
|
4467
|
+
installStoreHooks(storePark, () => {
|
|
4201
4468
|
drainFolds();
|
|
4202
4469
|
optHooks?.sweep();
|
|
4203
|
-
};
|
|
4470
|
+
});
|
|
4204
4471
|
/** The store half of `affects()` (store/affects.ts) makes its marks' nodes
|
|
4205
4472
|
* and walks records through these: it never imports the engine. */
|
|
4206
4473
|
GlobalQueue._storeNode = getNode;
|
|
@@ -4248,10 +4515,11 @@ function foldTarget(t, old) {
|
|
|
4248
4515
|
const v = t.v;
|
|
4249
4516
|
const wk = t.wk;
|
|
4250
4517
|
if (wk !== null && wk !== WK_ALL) {
|
|
4251
|
-
for (
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4518
|
+
for (let w = wk; w !== undefined; w = w.u)
|
|
4519
|
+
for (const key of w) {
|
|
4520
|
+
if (hasOwn$1.call(pb, key)) copyOwn(v, pb, key);
|
|
4521
|
+
else delete v[key];
|
|
4522
|
+
}
|
|
4255
4523
|
} else {
|
|
4256
4524
|
for (const key of Reflect.ownKeys(pb)) {
|
|
4257
4525
|
const d = Object.getOwnPropertyDescriptor(pb, key);
|
|
@@ -4424,7 +4692,10 @@ function notifyWrites(t) {
|
|
|
4424
4692
|
// value can change when ANY key is written), or a non-plain prototype
|
|
4425
4693
|
// (class instances: prototype getters derive from arbitrary fields).
|
|
4426
4694
|
// Overlay pbs chain to the COMMITTED object (#3044) — plainness is the
|
|
4427
|
-
// committed container's prototype, not the overlay's.
|
|
4695
|
+
// committed container's prototype, not the overlay's. THIS setter's keys
|
|
4696
|
+
// only (D-8, #3743): a key an earlier setter of the batch wrote and this
|
|
4697
|
+
// one did not is no second proposal — its node was told then, and telling
|
|
4698
|
+
// a held node its unchanged value again would join this tick to the hold.
|
|
4428
4699
|
const wk0 = t.wk;
|
|
4429
4700
|
const writtenKeys = wk0 === WK_ALL || t.a === true || !plainProto(t.ovl ? t.v : pb) ? null : wk0;
|
|
4430
4701
|
if (attrHooks !== null) reportReplacedContainers(t, old, pb, writtenKeys);
|
|
@@ -5084,6 +5355,22 @@ function optRead(target) {
|
|
|
5084
5355
|
const UNSAFE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
|
5085
5356
|
// ---------------------------------------------------------------------------
|
|
5086
5357
|
// traps
|
|
5358
|
+
/** A trap write: the target is told at the setter's exit, the key recorded
|
|
5359
|
+
* in this setter's `wk`. A setter's first write to a target still staged by
|
|
5360
|
+
* an earlier setter of the batch opens a set of its own (the notify visits
|
|
5361
|
+
* it, D-8) and chains the earlier one behind for the fold's union. */
|
|
5362
|
+
function touch(target, key) {
|
|
5363
|
+
let wk = target.wk;
|
|
5364
|
+
if (!pendingNotify.has(target)) {
|
|
5365
|
+
pendingNotify.add(target);
|
|
5366
|
+
if (wk !== null && wk !== WK_ALL) {
|
|
5367
|
+
wk = new Set();
|
|
5368
|
+
wk.u = target.wk;
|
|
5369
|
+
target.wk = wk;
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5372
|
+
if (wk !== WK_ALL) (target.wk ??= new Set()).add(key);
|
|
5373
|
+
}
|
|
5087
5374
|
const traps = {
|
|
5088
5375
|
get(target, key, receiver) {
|
|
5089
5376
|
// One typeof gates every brand-symbol compare off the hot string path.
|
|
@@ -5331,16 +5618,23 @@ const traps = {
|
|
|
5331
5618
|
if (key === "__proto__") return true; // pollution guard (core R30)
|
|
5332
5619
|
const uv = target.s ? value : unwrapValue(value);
|
|
5333
5620
|
const pb = ensurePB(target);
|
|
5334
|
-
|
|
5621
|
+
// #4146 (R1: node existence is unobservable; #3706: the hold's unit is
|
|
5622
|
+
// the key): a user setter writing a key a HELD container's staging left
|
|
5623
|
+
// unchanged would write it into the held staging — read as the hold's
|
|
5624
|
+
// until its landing, while the same key with a leaf (a live reader)
|
|
5625
|
+
// publishes at the flush. The key gets its leaf first, born from the two
|
|
5626
|
+
// frames (`bornStaged`: a key the hold left unchanged — or added — is
|
|
5627
|
+
// unstaged and held by no one; a key the hold changed is born held, A34
|
|
5628
|
+
// (1)), and the write stages on it (A28; a write by the holder joins it,
|
|
5629
|
+
// A34) — as it would with a reader's leaf. Kept while the container is
|
|
5630
|
+
// held (`deferredReleases`).
|
|
5631
|
+
if (userWrite && target.k?._config & CONFIG_HELD) deferredReleases.add(getNode(target, key));
|
|
5335
5632
|
if (Array.isArray(pb)) {
|
|
5633
|
+
touch(target, key);
|
|
5336
5634
|
if (key === "length") target.wk = WK_ALL;
|
|
5337
|
-
else
|
|
5338
|
-
const wk = (target.wk ??= new Set());
|
|
5339
|
-
wk.add(key);
|
|
5340
|
-
wk.add("length");
|
|
5341
|
-
}
|
|
5635
|
+
else touch(target, "length");
|
|
5342
5636
|
} else {
|
|
5343
|
-
|
|
5637
|
+
touch(target, key);
|
|
5344
5638
|
if (!(key in pb)) target.kc++;
|
|
5345
5639
|
}
|
|
5346
5640
|
if (UNSAFE_KEYS.has(key)) {
|
|
@@ -5375,8 +5669,7 @@ const traps = {
|
|
|
5375
5669
|
if ("value" in desc) desc = { ...desc, value: unwrapValue(desc.value) };
|
|
5376
5670
|
const pb = ensurePB(target);
|
|
5377
5671
|
if (target.a || !(desc.enumerable && desc.writable && desc.configurable)) target.sc = 1;
|
|
5378
|
-
|
|
5379
|
-
if (target.wk !== WK_ALL) (target.wk ??= new Set()).add(key);
|
|
5672
|
+
touch(target, key);
|
|
5380
5673
|
Object.defineProperty(pb, key, desc);
|
|
5381
5674
|
if (target.del !== null) target.del.delete(key);
|
|
5382
5675
|
if (override) notifyWrites(target);
|
|
@@ -5387,10 +5680,22 @@ const traps = {
|
|
|
5387
5680
|
const override = !draft && writeOverride;
|
|
5388
5681
|
if (!draft && !override) return true;
|
|
5389
5682
|
const pb = ensurePB(target);
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
delete
|
|
5393
|
-
|
|
5683
|
+
touch(target, key);
|
|
5684
|
+
// The key count (`kc`, the overlay gate and the rebuild cap) follows a
|
|
5685
|
+
// delete the way it follows an add: an own key of the draft leaving — a
|
|
5686
|
+
// clone's key, or an overlay's own add — is one fewer now. An overlay's
|
|
5687
|
+
// override of a committed key is the exception: the committed key's
|
|
5688
|
+
// deletion is recorded in `del` and counted at the rebuild, as before.
|
|
5689
|
+
// Without the decrement the natural "delete the old key, set the new
|
|
5690
|
+
// one" shape drifted the count up by one per write until a three-key
|
|
5691
|
+
// object crossed the overlay threshold and paid a rebuild on every
|
|
5692
|
+
// commit after.
|
|
5693
|
+
const committed = target.ovl && hasOwn$1.call(target.v, key);
|
|
5694
|
+
if (hasOwn$1.call(pb, key)) {
|
|
5695
|
+
delete pb[key];
|
|
5696
|
+
committed || target.kc--;
|
|
5697
|
+
}
|
|
5698
|
+
if (committed) (target.del ??= new Set()).add(key);
|
|
5394
5699
|
if (override) notifyWrites(target);
|
|
5395
5700
|
return true;
|
|
5396
5701
|
}
|
|
@@ -5418,7 +5723,7 @@ const ASYNC_STORE_SETTER_MESSAGE =
|
|
|
5418
5723
|
* write violation — the user guard (`mapArray`'s pattern: the node-level
|
|
5419
5724
|
* writes carry `ownedWrite`; the setter asks). */
|
|
5420
5725
|
function devGuardStoreSetterWrite() {
|
|
5421
|
-
if (context && !(context._config & CONFIG_CHILDREN_FORBIDDEN)) {
|
|
5726
|
+
if (context && !(context._config & CONFIG_CHILDREN_FORBIDDEN) && !disposalDepth) {
|
|
5422
5727
|
emitDiagnostic({
|
|
5423
5728
|
code: "REACTIVE_WRITE_IN_OWNED_SCOPE",
|
|
5424
5729
|
kind: "write",
|
|
@@ -5454,6 +5759,7 @@ function devGuardStoreSetterResult(result) {
|
|
|
5454
5759
|
}
|
|
5455
5760
|
}
|
|
5456
5761
|
function storeSetter(proxy, fn, guard = true) {
|
|
5762
|
+
if (writeHook !== null) writeHook(proxy);
|
|
5457
5763
|
if (guard) devGuardStoreSetterWrite();
|
|
5458
5764
|
const target = proxy[$TARGET];
|
|
5459
5765
|
const prevScopes = writeScopes;
|
|
@@ -5521,6 +5827,8 @@ function createStore$1(initialValue, options) {
|
|
|
5521
5827
|
throw new Error("createStore({ shallow }): value is already a store proxy");
|
|
5522
5828
|
}
|
|
5523
5829
|
const proxy = wrap(initialValue);
|
|
5830
|
+
// Dependency edges (store/edges.ts): the root is named like a plain signal.
|
|
5831
|
+
if (signalHook !== null) signalHook(proxy[$TARGET]);
|
|
5524
5832
|
if (shallow) {
|
|
5525
5833
|
proxy[$TARGET].s = true;
|
|
5526
5834
|
markRawIngest(initialValue);
|
|
@@ -5535,7 +5843,7 @@ function createStore$1(initialValue, options) {
|
|
|
5535
5843
|
// disabled, buys nothing.
|
|
5536
5844
|
if (attrHooks !== null) {
|
|
5537
5845
|
storeOwners.set(proxy[$TARGET], owner);
|
|
5538
|
-
nameStore(proxy, options?.name);
|
|
5846
|
+
nameStore(proxy, options?.name, options?.loc);
|
|
5539
5847
|
}
|
|
5540
5848
|
}
|
|
5541
5849
|
const setter = fn => storeSetter(proxy, fn);
|
|
@@ -6299,6 +6607,11 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
|
|
|
6299
6607
|
// A landing (not the sync pass's own commit): the readers held
|
|
6300
6608
|
// pending on the derive settle.
|
|
6301
6609
|
if (!first || !(owner._statusFlags & STATUS_UNINITIALIZED)) settleFamily(fam);
|
|
6610
|
+
// A commit is the derive's change for the readers that observed its
|
|
6611
|
+
// flight (linked by `pullFamily`; a settled pull does not link), as
|
|
6612
|
+
// a memo's landing is for its readers: one that caught the NotReady
|
|
6613
|
+
// (a fallback value, not pending) re-runs too (#4070).
|
|
6614
|
+
insertSubs(owner);
|
|
6302
6615
|
};
|
|
6303
6616
|
const sync = handleAsync(owner, result, commit, (error, pending) => {
|
|
6304
6617
|
// The flight rejected: the leaves' readers learn it from here —
|
|
@@ -6340,6 +6653,9 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
|
|
|
6340
6653
|
return owner;
|
|
6341
6654
|
}
|
|
6342
6655
|
function createProjectionInternal(fn, seed, options) {
|
|
6656
|
+
// Seedless (`fn` takes no draft): the root starts as an empty record. It is
|
|
6657
|
+
// a draft like any seed, never observable before the first landing (A25).
|
|
6658
|
+
seed ||= {};
|
|
6343
6659
|
const keyOption = options?.key === undefined ? "id" : options.key;
|
|
6344
6660
|
const fam = {
|
|
6345
6661
|
map: new WeakMap(),
|
|
@@ -6362,9 +6678,13 @@ function createProjectionInternal(fn, seed, options) {
|
|
|
6362
6678
|
}
|
|
6363
6679
|
let nodeOptions;
|
|
6364
6680
|
if (options?.seedLoadingValue) nodeOptions = { loadingValue: undefined };
|
|
6365
|
-
if (
|
|
6366
|
-
|
|
6367
|
-
|
|
6681
|
+
if (attrHooks !== null)
|
|
6682
|
+
// As createStore: the owner in scope stamps the projection's property
|
|
6683
|
+
// nodes, so attribution places them under it (#4035).
|
|
6684
|
+
storeOwners.set(store[$TARGET], getOwner());
|
|
6685
|
+
if (options?.name || options?.loc) {
|
|
6686
|
+
nodeOptions = { ...nodeOptions, name: options.name, loc: options.loc };
|
|
6687
|
+
nameStore(store, options.name, options.loc);
|
|
6368
6688
|
}
|
|
6369
6689
|
const node = computed(() => {
|
|
6370
6690
|
if (!fam.node) fam.node = getOwner();
|
|
@@ -6372,14 +6692,11 @@ function createProjectionInternal(fn, seed, options) {
|
|
|
6372
6692
|
}, nodeOptions);
|
|
6373
6693
|
node._config &= ~CONFIG_AUTO_DISPOSE;
|
|
6374
6694
|
fam.node = node;
|
|
6695
|
+
// The derive's leaves are not its dependents: a mark's repoll
|
|
6696
|
+
// (affects-marks.ts) reaches their readers through this.
|
|
6697
|
+
node.fam = fam;
|
|
6375
6698
|
return { store, node };
|
|
6376
6699
|
}
|
|
6377
|
-
/**
|
|
6378
|
-
* A derived store: `fn` runs in a computed and writes its draft; readers of
|
|
6379
|
-
* the store's keys re-run only when the keys they read change (the
|
|
6380
|
-
* firewall). `fn` may be async or an async iterator; the seed is a draft,
|
|
6381
|
-
* never an observable value, until the first landing (A25).
|
|
6382
|
-
*/
|
|
6383
6700
|
function createProjection(fn, seed, options) {
|
|
6384
6701
|
return createProjectionInternal(fn, seed, options).store;
|
|
6385
6702
|
}
|
|
@@ -6906,7 +7223,9 @@ function optimisticView(
|
|
|
6906
7223
|
function createOptimisticStore(first, second, third) {
|
|
6907
7224
|
const derived = typeof first === "function";
|
|
6908
7225
|
const options = derived ? third : second;
|
|
6909
|
-
|
|
7226
|
+
// Seedless derived form: the root starts as an empty record (see
|
|
7227
|
+
// createProjectionInternal).
|
|
7228
|
+
const initialValue = derived ? second || {} : first;
|
|
6910
7229
|
const keyOption = options?.key === undefined ? "id" : options.key;
|
|
6911
7230
|
const fam = {
|
|
6912
7231
|
map: new WeakMap(),
|
|
@@ -6922,7 +7241,7 @@ function createOptimisticStore(first, second, third) {
|
|
|
6922
7241
|
: row => (isWrappable(row) ? row[keyOption] : undefined)
|
|
6923
7242
|
};
|
|
6924
7243
|
const store = wrap(initialValue, null, null, fam);
|
|
6925
|
-
nameStore(store, options?.name);
|
|
7244
|
+
nameStore(store, options?.name, options?.loc);
|
|
6926
7245
|
if (fam.shallow) {
|
|
6927
7246
|
store[$TARGET].s = true;
|
|
6928
7247
|
markRawIngest(initialValue);
|
|
@@ -6931,7 +7250,8 @@ function createOptimisticStore(first, second, third) {
|
|
|
6931
7250
|
const fn = first;
|
|
6932
7251
|
let nodeOptions;
|
|
6933
7252
|
if (options?.seedLoadingValue) nodeOptions = { loadingValue: undefined };
|
|
6934
|
-
if (options?.name
|
|
7253
|
+
if (options?.name || options?.loc)
|
|
7254
|
+
nodeOptions = { ...nodeOptions, name: options.name, loc: options.loc };
|
|
6935
7255
|
const node = computed(() => {
|
|
6936
7256
|
if (!fam.node) fam.node = getOwner();
|
|
6937
7257
|
runProjectionComputed(store, fn, keyOption);
|
|
@@ -8000,6 +8320,83 @@ function deep(value) {
|
|
|
8000
8320
|
return deep$1(value);
|
|
8001
8321
|
}
|
|
8002
8322
|
|
|
8323
|
+
// The mark core: a declared not-final state on a node that reads pending
|
|
8324
|
+
// (`isPending` → true) on the node and everything derived from it, without
|
|
8325
|
+
// holding anything or entangling a reader. Two declarers share it:
|
|
8326
|
+
// `affects()` (affects.ts — a mark scoped to a transaction, with a store
|
|
8327
|
+
// half) and `createDeferred` (deferred.ts — a mark whose lifetime is the
|
|
8328
|
+
// node's own flight). Split out so the second does not ship the first.
|
|
8329
|
+
//
|
|
8330
|
+
// Representation: a count on the marked node (`_x._marks`). Coverage of the
|
|
8331
|
+
// derivations is pull-derived at probe time — `isPending`'s read walks the
|
|
8332
|
+
// probed node's dependencies for a marked one (verdict.ts → `_marked`) — so
|
|
8333
|
+
// nothing is stored downstream and a mid-window recompute strands nothing.
|
|
8334
|
+
// The push half is the verdict readers' re-derivation when a node's marks
|
|
8335
|
+
// start and end (`repoll`): the only notification a mark makes.
|
|
8336
|
+
/** Live marks, every declarer — the gate on the probe's walk. */
|
|
8337
|
+
let active = 0;
|
|
8338
|
+
/** The verdict readers downstream of `node` — through derivations, stopping
|
|
8339
|
+
* at effects — re-derive: the mark's one notification (a probe's answer
|
|
8340
|
+
* changed with no value notifying anyone). Truth readers (`until`, the
|
|
8341
|
+
* `refresh` waiter) too, which a lagging flight parked (deferred.ts) — but
|
|
8342
|
+
* not through a derived store, whose leaves change only once its derive
|
|
8343
|
+
* re-runs: they re-derive at that write. */
|
|
8344
|
+
function repoll(node, seen = new Set(), readers = CONFIG_VERDICT | CONFIG_AUTHORITATIVE) {
|
|
8345
|
+
for (let s = node._subs; s !== null; s = s._nextSub) {
|
|
8346
|
+
const sub = s._sub;
|
|
8347
|
+
if (seen.has(sub)) continue;
|
|
8348
|
+
seen.add(sub);
|
|
8349
|
+
if (sub._config & readers) enqueueSub(sub);
|
|
8350
|
+
if (!sub._type) repoll(sub, seen, readers);
|
|
8351
|
+
// A derived store's derive: its leaves' readers derive from it too
|
|
8352
|
+
// (store/projection.ts; a live target's property, has and keys nodes).
|
|
8353
|
+
const fam = sub.fam;
|
|
8354
|
+
if (fam)
|
|
8355
|
+
for (const t of fam.live)
|
|
8356
|
+
for (const o of [t.n, t.h, { k: t.k, d: t.dk }])
|
|
8357
|
+
if (o) for (const k of Reflect.ownKeys(o)) o[k] && repoll(o[k], seen, CONFIG_VERDICT);
|
|
8358
|
+
}
|
|
8359
|
+
}
|
|
8360
|
+
/** A node is covered by a live mark iff it carries one, or derives — through
|
|
8361
|
+
* its current dependencies — from one that does. A store slot derives from
|
|
8362
|
+
* its family's derive (a derived store's leaves are not the derive's
|
|
8363
|
+
* dependents, store/projection.ts), so coverage hops there. A real error
|
|
8364
|
+
* outranks an inherited mark (A24 (c)): coverage does not flow through an
|
|
8365
|
+
* errored node (a direct mark on one still reads pending). Mid-recompute,
|
|
8366
|
+
* only the validated prefix of the dependency list is this pass's. */
|
|
8367
|
+
function marked(el, seen) {
|
|
8368
|
+
if (el._x !== null && el._x._marks !== 0) return true;
|
|
8369
|
+
const c = el;
|
|
8370
|
+
if (c._statusFlags & STATUS_ERROR || seen.has(el)) return false;
|
|
8371
|
+
seen.add(el);
|
|
8372
|
+
if (c._config & CONFIG_SLOT_NODE) {
|
|
8373
|
+
const derive = c._host?.fam?.node;
|
|
8374
|
+
return derive != null && marked(derive, seen);
|
|
8375
|
+
}
|
|
8376
|
+
const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
|
|
8377
|
+
if (tail !== null)
|
|
8378
|
+
for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
|
|
8379
|
+
if (marked(d._dep, seen)) return true;
|
|
8380
|
+
if (d === tail) break;
|
|
8381
|
+
}
|
|
8382
|
+
return false;
|
|
8383
|
+
}
|
|
8384
|
+
/** One mark on `node`. The declarer repolls once its bookkeeping is done. */
|
|
8385
|
+
function mark(node) {
|
|
8386
|
+
ext(node)._marks++;
|
|
8387
|
+
active++;
|
|
8388
|
+
}
|
|
8389
|
+
/** Drops one mark from `node`: true when it was the last — the node's
|
|
8390
|
+
* verdict readers have re-derived. */
|
|
8391
|
+
function unmark(node) {
|
|
8392
|
+
active--;
|
|
8393
|
+
if (--node._x._marks !== 0) return false;
|
|
8394
|
+
repoll(node);
|
|
8395
|
+
return true;
|
|
8396
|
+
}
|
|
8397
|
+
GlobalQueue._marked = el => active !== 0 && marked(el, new Set());
|
|
8398
|
+
GlobalQueue._mark = mark;
|
|
8399
|
+
|
|
8003
8400
|
/**
|
|
8004
8401
|
* Store — the store half of `affects()` (plan sec. 29, plan sec. 37; A24 (4), #2882,
|
|
8005
8402
|
* #2893, #2904).
|
|
@@ -8128,7 +8525,7 @@ function releaseMarkScope(carrier) {
|
|
|
8128
8525
|
const entry = scopes.get(carrier);
|
|
8129
8526
|
if (entry === undefined) return;
|
|
8130
8527
|
scopes.delete(carrier);
|
|
8131
|
-
|
|
8528
|
+
releaseMarksHook(entry._inherited);
|
|
8132
8529
|
}
|
|
8133
8530
|
|
|
8134
8531
|
// `affects()` — the declaration verb of the pending model (A24 (4), ruled
|
|
@@ -8144,62 +8541,23 @@ function releaseMarkScope(carrier) {
|
|
|
8144
8541
|
// data joins nothing through it. A mark is never a re-ask, so a declared
|
|
8145
8542
|
// reload's own `refresh()` cannot silence it; a real error outranks it.
|
|
8146
8543
|
//
|
|
8147
|
-
// Representation:
|
|
8148
|
-
//
|
|
8149
|
-
//
|
|
8150
|
-
//
|
|
8151
|
-
// nothing is stored downstream and a mid-window recompute strands nothing.
|
|
8152
|
-
// The push half is the verdict readers' re-derivation at registration and at
|
|
8153
|
-
// release (`repoll`): the only notification a mark makes.
|
|
8544
|
+
// Representation: the mark core (affects-marks.ts) — a count on the marked
|
|
8545
|
+
// node, coverage pull-derived at probe time, readers repolled at registration
|
|
8546
|
+
// and release. Here: each mark's scope (the transaction's `_marks`, or the
|
|
8547
|
+
// ambient list) and the store half.
|
|
8154
8548
|
//
|
|
8155
8549
|
// Installed on `GlobalQueue` when imported: a program that never declares a
|
|
8156
8550
|
// mark carries none of this.
|
|
8157
|
-
/** Live marks, every scope — the gate on the probe's walk. */
|
|
8158
|
-
let active = 0;
|
|
8159
8551
|
/** Marks declared outside a transaction: released at the next seam. */
|
|
8160
8552
|
const ambient = [];
|
|
8161
|
-
/** The verdict readers downstream of `node` — through derivations, stopping
|
|
8162
|
-
* at effects — re-derive: the mark's one notification (a probe's answer
|
|
8163
|
-
* changed with no value notifying anyone). */
|
|
8164
|
-
function repoll(node, seen = new Set()) {
|
|
8165
|
-
for (let s = node._subs; s !== null; s = s._nextSub) {
|
|
8166
|
-
const sub = s._sub;
|
|
8167
|
-
if (seen.has(sub)) continue;
|
|
8168
|
-
seen.add(sub);
|
|
8169
|
-
if (sub._config & CONFIG_VERDICT) enqueueSub(sub);
|
|
8170
|
-
if (!sub._type) repoll(sub, seen);
|
|
8171
|
-
}
|
|
8172
|
-
}
|
|
8173
|
-
/** A node is covered by a live mark iff it carries one, or derives — through
|
|
8174
|
-
* its current dependencies — from one that does. A real error outranks an
|
|
8175
|
-
* inherited mark (A24 (c)): coverage does not flow through an errored node
|
|
8176
|
-
* (a direct mark on one still reads pending). Mid-recompute, only the
|
|
8177
|
-
* validated prefix of the dependency list is this pass's. */
|
|
8178
|
-
function marked(el, seen) {
|
|
8179
|
-
if (el._x !== null && el._x._marks !== 0) return true;
|
|
8180
|
-
const c = el;
|
|
8181
|
-
if (c._statusFlags & STATUS_ERROR || seen.has(el)) return false;
|
|
8182
|
-
seen.add(el);
|
|
8183
|
-
const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
|
|
8184
|
-
if (tail !== null)
|
|
8185
|
-
for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
|
|
8186
|
-
if (marked(d._dep, seen)) return true;
|
|
8187
|
-
if (d === tail) break;
|
|
8188
|
-
}
|
|
8189
|
-
return false;
|
|
8190
|
-
}
|
|
8191
8553
|
/** Releases one scope's marks (a landed transaction's, the ambient list at
|
|
8192
8554
|
* the seam): a node whose last mark drops re-derives its verdict readers. */
|
|
8193
8555
|
function release$1(nodes) {
|
|
8194
8556
|
for (let i = 0; i < nodes.length; i++) {
|
|
8195
8557
|
const n = nodes[i];
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
// A store mark's carrier: its scope (and the marks the nodes born in
|
|
8200
|
-
// its window inherited) goes with it.
|
|
8201
|
-
releaseMarkScope(n);
|
|
8202
|
-
}
|
|
8558
|
+
// A store mark's carrier: its scope (and the marks the nodes born in
|
|
8559
|
+
// its window inherited) goes with the last mark.
|
|
8560
|
+
if (unmark(n)) releaseMarkScope(n);
|
|
8203
8561
|
}
|
|
8204
8562
|
nodes.length = 0;
|
|
8205
8563
|
}
|
|
@@ -8215,10 +8573,6 @@ function register(node) {
|
|
|
8215
8573
|
}
|
|
8216
8574
|
repoll(node);
|
|
8217
8575
|
}
|
|
8218
|
-
function mark(node) {
|
|
8219
|
-
ext(node)._marks++;
|
|
8220
|
-
active++;
|
|
8221
|
-
}
|
|
8222
8576
|
function affects(target, key) {
|
|
8223
8577
|
if (arguments.length > 2)
|
|
8224
8578
|
invalid(
|
|
@@ -8256,10 +8610,7 @@ function invalid(detail) {
|
|
|
8256
8610
|
emitDiagnostic({ code: "INVALID_AFFECTS_TARGET", kind: "write", severity: "error", message });
|
|
8257
8611
|
throw new Error(message);
|
|
8258
8612
|
}
|
|
8259
|
-
|
|
8260
|
-
GlobalQueue._mark = mark;
|
|
8261
|
-
GlobalQueue._releaseMarks = release$1;
|
|
8262
|
-
GlobalQueue._releaseAmbientMarks = parked => {
|
|
8613
|
+
installMarkHooks(release$1, parked => {
|
|
8263
8614
|
if (ambient.length === 0) return;
|
|
8264
8615
|
// The flush parked: the declaration's window is the frame's — the marks
|
|
8265
8616
|
// are its transaction's ("nothing async below ⇒ no window").
|
|
@@ -8282,7 +8633,7 @@ GlobalQueue._releaseAmbientMarks = parked => {
|
|
|
8282
8633
|
}
|
|
8283
8634
|
ambient.length = kept;
|
|
8284
8635
|
if (done.length !== 0) release$1(done);
|
|
8285
|
-
};
|
|
8636
|
+
});
|
|
8286
8637
|
function inFlight(n) {
|
|
8287
8638
|
return (
|
|
8288
8639
|
(n._statusFlags & STATUS_PENDING) !== 0 ||
|
|
@@ -8290,6 +8641,174 @@ function inFlight(n) {
|
|
|
8290
8641
|
);
|
|
8291
8642
|
}
|
|
8292
8643
|
|
|
8644
|
+
// `createDeferred` — an async memo that may lag, but never leads (D-40,
|
|
8645
|
+
// docs/create-deferred.md).
|
|
8646
|
+
//
|
|
8647
|
+
// Once the node has a committed value, its own non-final work is invisible
|
|
8648
|
+
// to the graph: its readers keep the committed value and hold nothing, its
|
|
8649
|
+
// landings are changes of their own, and only the verdict (`isPending`) and
|
|
8650
|
+
// the truth readers (`until`, `refresh`'s waiter) see the flight.
|
|
8651
|
+
//
|
|
8652
|
+
// Built from parts the engine already has, so core pays nothing for it:
|
|
8653
|
+
//
|
|
8654
|
+
// - **Read side: the loading window** (`_loading`, async.ts). Every pass of
|
|
8655
|
+
// a node with a committed value opens the window before it asks, so a
|
|
8656
|
+
// flight serves the committed value instead of throwing, and an unready
|
|
8657
|
+
// source parks the node instead of pending it. `CONFIG_REDERIVE` makes a
|
|
8658
|
+
// source going pending a question for the node's pass (which parks) rather
|
|
8659
|
+
// than a status pushed through it to its readers.
|
|
8660
|
+
// - **Verdict: a mark** (affects-marks.ts) whose lifetime is the flight —
|
|
8661
|
+
// started when a pass asks a new question (a `refresh()` re-ask is quiet, F-9),
|
|
8662
|
+
// dropped at the landing, the rejection, a sync answer or the node's death.
|
|
8663
|
+
// - **Never leads:** a flight that lands while an input it read is held
|
|
8664
|
+
// derived from that hold's world, so it lands into the hold (`holdNode`;
|
|
8665
|
+
// `setSignal` stages it there). An input that changed after the pass
|
|
8666
|
+
// would have superseded the flight, so a held input at the landing is one
|
|
8667
|
+
// the pass read held.
|
|
8668
|
+
// - **Truth readers** wait the flight out (`GlobalQueue._lagging`, signals.ts
|
|
8669
|
+
// `watch`): `await refresh(d)` delivers the landing, `until` sees only
|
|
8670
|
+
// settled values.
|
|
8671
|
+
/** Deferred nodes with a flight up. */
|
|
8672
|
+
const lagging = new Set();
|
|
8673
|
+
/** The deferred node with a flight up that `c`'s value derives from, if
|
|
8674
|
+
* any: `c` itself, or one reached through its current dependencies. */
|
|
8675
|
+
function lagOf(c, seen) {
|
|
8676
|
+
if (lagging.has(c)) return c;
|
|
8677
|
+
if (seen.has(c)) return;
|
|
8678
|
+
seen.add(c);
|
|
8679
|
+
if (c._config & CONFIG_SLOT_NODE) return lagOf(c._host?.fam?.node ?? c, seen);
|
|
8680
|
+
const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
|
|
8681
|
+
if (tail !== null)
|
|
8682
|
+
for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
|
|
8683
|
+
const l = lagOf(d._dep, seen);
|
|
8684
|
+
if (l) return l;
|
|
8685
|
+
if (d === tail) break;
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8688
|
+
GlobalQueue._lagging = c => (lagging.size !== 0 ? lagOf(c, new Set()) : undefined);
|
|
8689
|
+
/**
|
|
8690
|
+
* Creates an async memo that may lag, but never leads. Before its first
|
|
8691
|
+
* answer it is an ordinary async memo: readers suspend to the nearest
|
|
8692
|
+
* `<Loading>`. After it, a refetch holds nothing: readers keep the last
|
|
8693
|
+
* committed value until the new one lands, and the rest of the screen moves
|
|
8694
|
+
* on without it. `isPending` reports the refetch, on the node and on what
|
|
8695
|
+
* derives from it. Use it for independent widgets (dashboards, feeds,
|
|
8696
|
+
* search results) where the previous answer with a "refreshing" indicator is
|
|
8697
|
+
* better than waiting with everything else.
|
|
8698
|
+
*
|
|
8699
|
+
* - It never shows a value ahead of a held change: a refetch that read a
|
|
8700
|
+
* held value lands with that hold.
|
|
8701
|
+
* - A rejected refetch errors like any memo; the stale value never masks it.
|
|
8702
|
+
* - `refresh(d)` is a quiet re-ask, and `await refresh(d)` and `until`
|
|
8703
|
+
* wait for the landing rather than the stale value.
|
|
8704
|
+
* - Combine with `loadingValue` for a node that never suspends at all.
|
|
8705
|
+
*
|
|
8706
|
+
* @example
|
|
8707
|
+
* ```tsx
|
|
8708
|
+
* const rows = createDeferred(() => fetchRows(store.period));
|
|
8709
|
+
*
|
|
8710
|
+
* <section class={{ stale: isPending(() => rows()) }}>
|
|
8711
|
+
* <For each={rows()}>{row => <Row row={row} />}</For>
|
|
8712
|
+
* </section>
|
|
8713
|
+
* ```
|
|
8714
|
+
*/
|
|
8715
|
+
function createDeferred(fn, options) {
|
|
8716
|
+
let node;
|
|
8717
|
+
/** The flight in force: a promise, a stream, or the error an unready
|
|
8718
|
+
* source parked the node with. */
|
|
8719
|
+
let flight = null;
|
|
8720
|
+
let marked = false;
|
|
8721
|
+
let answered = false;
|
|
8722
|
+
/** The flight `r` is over. An async answer (`held`) arriving while an
|
|
8723
|
+
* input is held lands into that hold first. */
|
|
8724
|
+
const land = (r, held) => {
|
|
8725
|
+
if (r === null || flight !== r) return;
|
|
8726
|
+
flight = null;
|
|
8727
|
+
lagging.delete(node);
|
|
8728
|
+
if (held)
|
|
8729
|
+
for (let d = node._deps; d !== null; d = d._nextDep)
|
|
8730
|
+
if (d._dep._config & CONFIG_HELD) {
|
|
8731
|
+
holdNode(node, txOf(d._dep));
|
|
8732
|
+
break;
|
|
8733
|
+
}
|
|
8734
|
+
if (marked) {
|
|
8735
|
+
marked = false;
|
|
8736
|
+
unmark(node);
|
|
8737
|
+
schedule();
|
|
8738
|
+
}
|
|
8739
|
+
};
|
|
8740
|
+
const ask = r => {
|
|
8741
|
+
flight = r;
|
|
8742
|
+
lagging.add(node);
|
|
8743
|
+
if (!marked && !(node._flags & REACTIVE_REASK)) {
|
|
8744
|
+
marked = true;
|
|
8745
|
+
mark(node);
|
|
8746
|
+
repoll(node);
|
|
8747
|
+
}
|
|
8748
|
+
};
|
|
8749
|
+
/** Registers the flight and serves the pass's value: the committed one,
|
|
8750
|
+
* or over a held landing the landing itself, which a lagging question
|
|
8751
|
+
* does not replace (D-40) — restaged as the committed value, the hold's
|
|
8752
|
+
* readers would re-derive against the old input. */
|
|
8753
|
+
const serve = r => {
|
|
8754
|
+
const v = handleAsync(node, r);
|
|
8755
|
+
return node._config & CONFIG_HELD && node._pendingValue !== NOT_PENDING
|
|
8756
|
+
? node._pendingValue
|
|
8757
|
+
: v;
|
|
8758
|
+
};
|
|
8759
|
+
const end = () => node._flags & REACTIVE_DISPOSED && land(flight);
|
|
8760
|
+
return accessor(
|
|
8761
|
+
computed(prev => {
|
|
8762
|
+
node = getOwner();
|
|
8763
|
+
node._config |= CONFIG_REDERIVE;
|
|
8764
|
+
cleanup(end);
|
|
8765
|
+
// Answered: the node has a real committed answer — not a first load,
|
|
8766
|
+
// not a `loadingValue` window still open (both are loading, F-9).
|
|
8767
|
+
// Sticky, and read before this wrapper ever opens the window itself.
|
|
8768
|
+
const open = (answered ||= !(node._statusFlags & STATUS_UNINITIALIZED) && !node._loading);
|
|
8769
|
+
if (open) ((node._loading = true), (node._config |= CONFIG_LAGS));
|
|
8770
|
+
let r;
|
|
8771
|
+
try {
|
|
8772
|
+
r = fn(prev);
|
|
8773
|
+
} catch (e) {
|
|
8774
|
+
// The wrapping form: an unready source parks the node in the window,
|
|
8775
|
+
// and the wait is its flight until the source's settle re-runs it.
|
|
8776
|
+
if (open && e instanceof NotReadyError) ask(e);
|
|
8777
|
+
throw e;
|
|
8778
|
+
}
|
|
8779
|
+
if (open && typeof r === "object" && r !== null) {
|
|
8780
|
+
if (isThenable(r)) {
|
|
8781
|
+
ask(r);
|
|
8782
|
+
const done = () => land(r, true);
|
|
8783
|
+
r.then(done, done);
|
|
8784
|
+
return serve(r);
|
|
8785
|
+
}
|
|
8786
|
+
const iterate = r[Symbol.asyncIterator];
|
|
8787
|
+
if (iterate) {
|
|
8788
|
+
// A stream's flight ends at its first yield (or its end).
|
|
8789
|
+
const it = iterate.call(r);
|
|
8790
|
+
let first = true;
|
|
8791
|
+
const done = () => land(s, true);
|
|
8792
|
+
const s = {
|
|
8793
|
+
// Registered ahead of the engine's own reaction to the same
|
|
8794
|
+
// promise, as for a plain flight: verdict and value in one flush.
|
|
8795
|
+
next: v => {
|
|
8796
|
+
const p = it.next(v);
|
|
8797
|
+
if (first) ((first = false), p.then(done, done));
|
|
8798
|
+
return p;
|
|
8799
|
+
},
|
|
8800
|
+
return: v => it.return?.(v) ?? Promise.resolve({ done: true, value: v })
|
|
8801
|
+
};
|
|
8802
|
+
ask(s);
|
|
8803
|
+
return serve({ [Symbol.asyncIterator]: () => s });
|
|
8804
|
+
}
|
|
8805
|
+
}
|
|
8806
|
+
land(flight);
|
|
8807
|
+
return r;
|
|
8808
|
+
}, options)
|
|
8809
|
+
);
|
|
8810
|
+
}
|
|
8811
|
+
|
|
8293
8812
|
// CARVE 4: `flatten` lived in boundaries.ts but is the general children
|
|
8294
8813
|
// resolver the renderers (`web`, `universal`, `solid`'s `children()`) walk
|
|
8295
8814
|
// through — not a boundary feature. Moved verbatim so the boundary carve
|
|
@@ -8381,7 +8900,7 @@ function flattenArray(children, results = [], options) {
|
|
|
8381
8900
|
* the root holds the frame as for any reader. The tree's own status takes
|
|
8382
8901
|
* the same route from the output's pass (`read(tree)` throws it).
|
|
8383
8902
|
*
|
|
8384
|
-
* The hold (`blocked`, scheduler.ts) asks `
|
|
8903
|
+
* The hold (`blocked`, scheduler.ts) asks `hiddenBy`: a reader behind a
|
|
8385
8904
|
* fallback — any boundary on its chain whose last pass chose the fallback —
|
|
8386
8905
|
* is not on screen and holds nothing. An error boundary flipping to its
|
|
8387
8906
|
* fallback is the same as a loading one resetting: every reader under it
|
|
@@ -8416,6 +8935,11 @@ const boundaryOf = node => node._context[BOUNDARY];
|
|
|
8416
8935
|
/** Catches what reaches it: an error boundary always; a loading one until it
|
|
8417
8936
|
* has shown content, or while re-armed. */
|
|
8418
8937
|
const isCollecting = b => b._type === STATUS_ERROR || !b._initialized || b._armed;
|
|
8938
|
+
/** A reader `b` waits on: recorded for its `on`, and `b` visited at the seam. */
|
|
8939
|
+
function record(b, node) {
|
|
8940
|
+
b._readers.add(node);
|
|
8941
|
+
collecting.add(b);
|
|
8942
|
+
}
|
|
8419
8943
|
/** The reader goes to the fallback: recorded, and the output re-derives
|
|
8420
8944
|
* (a no-op while the output's own pass is running — its post-check
|
|
8421
8945
|
* decides). */
|
|
@@ -8432,8 +8956,7 @@ function caught(b, node, error) {
|
|
|
8432
8956
|
setSignal(b._error, caught);
|
|
8433
8957
|
reportClientError(caught, b._owner, node);
|
|
8434
8958
|
}
|
|
8435
|
-
b
|
|
8436
|
-
collecting.add(b);
|
|
8959
|
+
record(b, node);
|
|
8437
8960
|
if (b._armed) flip(b);
|
|
8438
8961
|
// Already showing the fallback: one more reader to wait on changes
|
|
8439
8962
|
// nothing on screen. The fallback is instantiated once per show — a later
|
|
@@ -8449,8 +8972,9 @@ function caught(b, node, error) {
|
|
|
8449
8972
|
function flip(b) {
|
|
8450
8973
|
b._initialized = false;
|
|
8451
8974
|
b._lane = b._armLane;
|
|
8452
|
-
|
|
8453
|
-
|
|
8975
|
+
const o = b._output;
|
|
8976
|
+
if (!b._lane && o._config & CONFIG_HELD && txOf(o) !== flushTransaction) unhold(o);
|
|
8977
|
+
if (b._lane !== null && b._output !== null) laneStageHook(b._output, b._lane, true, false);
|
|
8454
8978
|
}
|
|
8455
8979
|
function redraw(b) {
|
|
8456
8980
|
if (b._output !== null && !(b._output._flags & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
|
|
@@ -8458,7 +8982,7 @@ function redraw(b) {
|
|
|
8458
8982
|
schedule();
|
|
8459
8983
|
}
|
|
8460
8984
|
}
|
|
8461
|
-
/**
|
|
8985
|
+
/** `catchHook` (scheduler.ts): status from a frame reader, nearest boundary first.
|
|
8462
8986
|
* A loading boundary on the way records a pending reader whether or not it
|
|
8463
8987
|
* catches it (its `on` may collect it later). */
|
|
8464
8988
|
function catchStatus(node, flags, error) {
|
|
@@ -8471,7 +8995,7 @@ function catchStatus(node, flags, error) {
|
|
|
8471
8995
|
// lands with its run, not a round after). Others still waiting: nothing
|
|
8472
8996
|
// on screen changes, the fallback stays as it is.
|
|
8473
8997
|
if (collecting.size !== 0)
|
|
8474
|
-
for (let b = boundaryOf(node); b
|
|
8998
|
+
for (let b = boundaryOf(node); b; b = b._parent)
|
|
8475
8999
|
if (
|
|
8476
9000
|
!(node._statusFlags & unsettled(b)) &&
|
|
8477
9001
|
b._readers.delete(node) &&
|
|
@@ -8482,28 +9006,29 @@ function catchStatus(node, flags, error) {
|
|
|
8482
9006
|
}
|
|
8483
9007
|
return false;
|
|
8484
9008
|
}
|
|
8485
|
-
for (let b = boundaryOf(node); b
|
|
9009
|
+
for (let b = boundaryOf(node); b; b = b._parent) {
|
|
8486
9010
|
if (!(b._type & flags)) continue;
|
|
8487
9011
|
if (isCollecting(b)) {
|
|
8488
9012
|
caught(b, node, error);
|
|
8489
|
-
return true;
|
|
9013
|
+
return b._armed ? CAUGHT_REARMED : true;
|
|
8490
9014
|
}
|
|
8491
|
-
b
|
|
8492
|
-
collecting.add(b);
|
|
9015
|
+
record(b, node);
|
|
8493
9016
|
}
|
|
8494
9017
|
return false;
|
|
8495
9018
|
}
|
|
8496
|
-
/** The nearest boundary
|
|
9019
|
+
/** The nearest loading boundary that would catch `node` was re-armed this
|
|
9020
|
+
* flush. */
|
|
9021
|
+
function armedOver(node) {
|
|
9022
|
+
for (let b = boundaryOf(node); b; b = b._parent)
|
|
9023
|
+
if (b._type & STATUS_PENDING && isCollecting(b)) return b._armed;
|
|
9024
|
+
}
|
|
9025
|
+
/** The nearest boundary on `r`'s chain showing its fallback, if any
|
|
9026
|
+
* (`hiddenHook`: `r` is behind a fallback). */
|
|
8497
9027
|
function hiddenBy(r) {
|
|
8498
|
-
for (let b = boundaryOf(r); b
|
|
8499
|
-
if (b._fallback) return b;
|
|
9028
|
+
for (let b = boundaryOf(r); b; b = b._parent) if (b._fallback) return b;
|
|
8500
9029
|
return undefined;
|
|
8501
9030
|
}
|
|
8502
|
-
/**
|
|
8503
|
-
function hidden(r) {
|
|
8504
|
-
return hiddenBy(r) !== undefined;
|
|
8505
|
-
}
|
|
8506
|
-
/** GlobalQueue._heldRun: a queued run behind a fallback waits for the reveal
|
|
9031
|
+
/** `heldRunHook` (scheduler.ts): a queued run behind a fallback waits for the reveal
|
|
8507
9032
|
* (maintainer, 2026-10-02: the queue is held, the synchronous first render
|
|
8508
9033
|
* goes through). A user effect's, which would read a DOM that is not
|
|
8509
9034
|
* attached ("many depend on reading the DOM, something that can't happen
|
|
@@ -8548,7 +9073,10 @@ function prune(b, pass) {
|
|
|
8548
9073
|
for (const r of b._readers) {
|
|
8549
9074
|
if (r._flags & REACTIVE_DISPOSED) b._readers.delete(r);
|
|
8550
9075
|
else if (!(r._statusFlags & mask)) {
|
|
8551
|
-
if (
|
|
9076
|
+
if (
|
|
9077
|
+
(r !== b._tree || b._armed) &&
|
|
9078
|
+
(r._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD
|
|
9079
|
+
) {
|
|
8552
9080
|
// Landed, held: the content is that transaction's — the output's
|
|
8553
9081
|
// pass enters it and the reveal lands with the reader's run. The
|
|
8554
9082
|
// seam, past its park decision, waits for the commit instead. (A
|
|
@@ -8578,7 +9106,7 @@ function ready(b) {
|
|
|
8578
9106
|
prune(b, false) === 0)
|
|
8579
9107
|
);
|
|
8580
9108
|
}
|
|
8581
|
-
/**
|
|
9109
|
+
/** `boundarySeamHook` (scheduler.ts), end of the seam: readers that settled, died,
|
|
8582
9110
|
* or landed and committed are dropped; a fallback with none left reveals
|
|
8583
9111
|
* next round. An arm resolves: nothing collected, nothing happened. */
|
|
8584
9112
|
function boundarySeam() {
|
|
@@ -8591,6 +9119,18 @@ function boundarySeam() {
|
|
|
8591
9119
|
// A parked frame's boundary (zombie) keeps its state for its revival.
|
|
8592
9120
|
if (flags & REACTIVE_ZOMBIE) continue;
|
|
8593
9121
|
prune(b, false);
|
|
9122
|
+
const o = b._output;
|
|
9123
|
+
if (
|
|
9124
|
+
b._armed &&
|
|
9125
|
+
!b._initialized &&
|
|
9126
|
+
o._config & CONFIG_HELD &&
|
|
9127
|
+
[...b._readers].every(
|
|
9128
|
+
r => !(r._statusFlags & STATUS_PENDING) && r._config & CONFIG_HELD && txOf(r) === txOf(o)
|
|
9129
|
+
)
|
|
9130
|
+
) {
|
|
9131
|
+
b._readers.clear();
|
|
9132
|
+
b._initialized = true;
|
|
9133
|
+
}
|
|
8594
9134
|
if (b._armed && !b._initialized && !b._ahead) reportOutsideHold(b);
|
|
8595
9135
|
b._armed = false;
|
|
8596
9136
|
if (b._readers.size === 0) {
|
|
@@ -8617,15 +9157,17 @@ function reportOutsideHold(b) {
|
|
|
8617
9157
|
if (outside(s._sub, false)) return true;
|
|
8618
9158
|
return false;
|
|
8619
9159
|
}
|
|
8620
|
-
if (
|
|
8621
|
-
for (let o = boundaryOf(reader); o
|
|
8622
|
-
if (o === b) return false;
|
|
9160
|
+
if (hiddenBy(reader)) return false;
|
|
9161
|
+
for (let o = boundaryOf(reader); o; o = o._parent) if (o === b) return false;
|
|
8623
9162
|
return true;
|
|
8624
9163
|
};
|
|
8625
9164
|
for (const r of b._readers) {
|
|
8626
9165
|
const sources = r._x?._pendingSources;
|
|
8627
9166
|
if (sources === undefined) continue;
|
|
8628
9167
|
for (const source of sources) {
|
|
9168
|
+
// Only the flight the change itself started (D-16): an outside reader
|
|
9169
|
+
// of an earlier write's flight holds nothing the change waits on.
|
|
9170
|
+
if (source._time !== clock) continue;
|
|
8629
9171
|
for (let s = source._subs; s !== null; s = s._nextSub) {
|
|
8630
9172
|
if (!outside(s._sub, true)) continue;
|
|
8631
9173
|
const name = source._name;
|
|
@@ -8650,15 +9192,6 @@ function reportOutsideHold(b) {
|
|
|
8650
9192
|
}
|
|
8651
9193
|
}
|
|
8652
9194
|
}
|
|
8653
|
-
/** The `on` pass is a verdict's: it probed itself (CONFIG_VERDICT), or it
|
|
8654
|
-
* read a memo that did (#3528's `createMemo(() => isPending(m2))`) — its
|
|
8655
|
-
* swap is display-ahead either way. */
|
|
8656
|
-
function verdictDerived(c) {
|
|
8657
|
-
if (c._config & CONFIG_VERDICT) return true;
|
|
8658
|
-
for (let d = c._deps; d !== null; d = d._nextDep)
|
|
8659
|
-
if (d._dep._config & CONFIG_VERDICT) return true;
|
|
8660
|
-
return false;
|
|
8661
|
-
}
|
|
8662
9195
|
/** `on` changed after the mount: fresh again if anything under it is
|
|
8663
9196
|
* pending now; collecting for the rest of the flush either way. */
|
|
8664
9197
|
function arm(b) {
|
|
@@ -8682,20 +9215,32 @@ function arm(b) {
|
|
|
8682
9215
|
return;
|
|
8683
9216
|
}
|
|
8684
9217
|
b._armed = true;
|
|
8685
|
-
// A verdict read in `on` (`isPending
|
|
8686
|
-
//
|
|
9218
|
+
// A verdict read in `on` (`isPending`, or a memo of one — #3528's
|
|
9219
|
+
// `createMemo(() => isPending(m2))`: the mark carries, core.ts `read`)
|
|
9220
|
+
// whose frame has no transaction yet: the swap is still display-ahead — the frame gets one now (it lands
|
|
8687
9221
|
// at this very seam if nothing holds it), and the swap is its verdict
|
|
8688
9222
|
// lane's work, shown ahead of the frame's own runs (#3528).
|
|
8689
9223
|
let lane = passLane;
|
|
8690
|
-
if (lane === null && globalQueue._running &&
|
|
9224
|
+
if (lane === null && globalQueue._running && getOwner()._config & CONFIG_VERDICT) {
|
|
8691
9225
|
joinFuture(null);
|
|
8692
9226
|
lane = GlobalQueue._verdictLane(flushTransaction);
|
|
8693
9227
|
}
|
|
8694
9228
|
b._armLane = lane;
|
|
8695
9229
|
b._ahead = lane !== null;
|
|
8696
9230
|
collecting.add(b);
|
|
9231
|
+
const n = b._readers.size;
|
|
9232
|
+
heldUnder(b._owner, flushTransaction, r => record(b, r));
|
|
8697
9233
|
for (const r of b._readers) {
|
|
8698
|
-
if (
|
|
9234
|
+
if (
|
|
9235
|
+
b._readers.size !== n ||
|
|
9236
|
+
(r._statusFlags & STATUS_PENDING && !(r._flags & REACTIVE_DISPOSED))
|
|
9237
|
+
) {
|
|
9238
|
+
// What it collects was forwarded through it to the boundaries above
|
|
9239
|
+
// too (`catchStatus` records a reader in every boundary on the way).
|
|
9240
|
+
// It is this boundary's alone now (B5, the nearest catcher): an outer
|
|
9241
|
+
// re-armed by the same tick does not wait for it (D-14, U-4).
|
|
9242
|
+
for (let a = b._parent; a !== null; a = a._parent)
|
|
9243
|
+
for (const o of b._readers) a._readers.delete(o);
|
|
8699
9244
|
flip(b);
|
|
8700
9245
|
redraw(b);
|
|
8701
9246
|
return;
|
|
@@ -8828,8 +9373,7 @@ function createBoundary(type, fn, show, onFn) {
|
|
|
8828
9373
|
}
|
|
8829
9374
|
// Forwarded: a loading boundary showing content keeps it and
|
|
8830
9375
|
// holds the frame like any reader (recorded for its `on`).
|
|
8831
|
-
b
|
|
8832
|
-
collecting.add(b);
|
|
9376
|
+
record(b, tree);
|
|
8833
9377
|
}
|
|
8834
9378
|
// A pending passes an error boundary by (A6); an error, a loading
|
|
8835
9379
|
// one; a loading boundary showing content forwards its pending.
|
|
@@ -8869,8 +9413,7 @@ function createBoundary(type, fn, show, onFn) {
|
|
|
8869
9413
|
}
|
|
8870
9414
|
// Forwarded: a loading boundary showing content keeps it and
|
|
8871
9415
|
// holds the frame like any reader (recorded for its `on`).
|
|
8872
|
-
b
|
|
8873
|
-
collecting.add(b);
|
|
9416
|
+
record(b, tree);
|
|
8874
9417
|
}
|
|
8875
9418
|
// A pending passes an error boundary by (A6).
|
|
8876
9419
|
throw e;
|
|
@@ -9031,12 +9574,17 @@ function createErrorBoundary(fn, fallback) {
|
|
|
9031
9574
|
}
|
|
9032
9575
|
// Installed at module evaluation — present exactly when something imports a
|
|
9033
9576
|
// boundary. An app without one pays the three null checks and nothing else.
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9577
|
+
installBoundaryHooks(
|
|
9578
|
+
catchStatus,
|
|
9579
|
+
(node, held) =>
|
|
9580
|
+
!held &&
|
|
9581
|
+
(node._statusFlags & STATUS_UNINITIALIZED
|
|
9582
|
+
? catchStatus(node, STATUS_PENDING)
|
|
9583
|
+
: armedOver(node)),
|
|
9584
|
+
hiddenBy,
|
|
9585
|
+
boundarySeam,
|
|
9586
|
+
heldRun
|
|
9587
|
+
);
|
|
9040
9588
|
|
|
9041
9589
|
/**
|
|
9042
9590
|
* Reveal order — `createRevealOrder` (`<Reveal>`): the order in which a group
|
|
@@ -9279,6 +9827,7 @@ export {
|
|
|
9279
9827
|
affects,
|
|
9280
9828
|
configureClientErrors,
|
|
9281
9829
|
createContext,
|
|
9830
|
+
createDeferred,
|
|
9282
9831
|
createEffect,
|
|
9283
9832
|
createErrorBoundary,
|
|
9284
9833
|
createLoadingBoundary,
|
|
@@ -9324,6 +9873,9 @@ export {
|
|
|
9324
9873
|
resolvedTable,
|
|
9325
9874
|
runWithOwner,
|
|
9326
9875
|
setContext,
|
|
9876
|
+
setReadWatch,
|
|
9877
|
+
setSignalIdentities,
|
|
9878
|
+
signalName,
|
|
9327
9879
|
snapshot,
|
|
9328
9880
|
sourceGet,
|
|
9329
9881
|
sourceHas,
|
|
@@ -9332,6 +9884,7 @@ export {
|
|
|
9332
9884
|
storeHasFamily,
|
|
9333
9885
|
storeHasOptimisticFamily,
|
|
9334
9886
|
storeIsShallow,
|
|
9887
|
+
storeName,
|
|
9335
9888
|
storePath,
|
|
9336
9889
|
until,
|
|
9337
9890
|
untrack,
|