@solidjs/signals 2.0.0-rc.15 → 2.0.0-rc.17
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 +2268 -1734
- package/dist/dev.attribution.js +75 -52
- package/dist/dev.js +1348 -658
- package/dist/observe/affects-marks.js +86 -0
- package/dist/observe/affects.js +22 -73
- package/dist/observe/boundaries.js +192 -190
- package/dist/observe/core/async.js +177 -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 +5 -3
- package/dist/observe/core/core.js +520 -364
- package/dist/observe/core/dev.js +66 -38
- package/dist/observe/core/effect.js +50 -48
- package/dist/observe/core/error.js +5 -3
- package/dist/observe/core/external.js +5 -6
- package/dist/observe/core/graph.js +60 -44
- package/dist/observe/core/heap.js +33 -33
- package/dist/observe/core/identities.js +62 -0
- package/dist/observe/core/lanes.js +238 -260
- package/dist/observe/core/owner.js +117 -79
- package/dist/observe/core/scheduler.js +381 -217
- package/dist/observe/core/truth.js +45 -0
- package/dist/observe/core/verdict.js +91 -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 +47 -31
- package/dist/observe/store/affects.js +18 -18
- package/dist/observe/store/edges.js +39 -0
- package/dist/observe/store/optimistic.js +146 -100
- package/dist/observe/store/projection.js +68 -55
- package/dist/observe/store/store.js +180 -118
- package/dist/prod/affects-marks.js +86 -0
- package/dist/prod/affects.js +23 -72
- package/dist/prod/boundaries.js +156 -154
- package/dist/prod/core/async.js +173 -127
- package/dist/prod/core/constants.js +17 -3
- package/dist/prod/core/context.js +5 -3
- package/dist/prod/core/core.js +495 -343
- package/dist/prod/core/effect.js +70 -68
- package/dist/prod/core/error.js +5 -3
- package/dist/prod/core/external.js +5 -6
- package/dist/prod/core/graph.js +60 -44
- package/dist/prod/core/heap.js +38 -38
- package/dist/prod/core/identities.js +62 -0
- package/dist/prod/core/lanes.js +230 -252
- package/dist/prod/core/owner.js +114 -77
- package/dist/prod/core/scheduler.js +327 -163
- package/dist/prod/core/truth.js +45 -0
- package/dist/prod/core/verdict.js +90 -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 +42 -26
- package/dist/prod/store/affects.js +6 -6
- package/dist/prod/store/edges.js +39 -0
- package/dist/prod/store/optimistic.js +143 -98
- package/dist/prod/store/projection.js +25 -19
- package/dist/prod/store/store.js +157 -100
- 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 +54 -5
- package/dist/types/core/dev.d.ts +51 -13
- package/dist/types/core/error.d.ts +9 -1
- 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 +9 -3
- package/dist/types/core/scheduler.d.ts +70 -32
- 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,230 @@
|
|
|
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 haltReactivityAt,
|
|
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 joinPassAt,
|
|
158
|
+
bC as notifyEpoch,
|
|
159
|
+
bD as SUPPORTS_PROXY,
|
|
160
|
+
bE as releaseMarksHook,
|
|
161
|
+
bF as installMarkHooks,
|
|
162
|
+
bG as CONFIG_LAGS,
|
|
163
|
+
bH as REACTIVE_REASK,
|
|
164
|
+
bI as haltReactivity,
|
|
165
|
+
bJ as heldUnder,
|
|
166
|
+
bK as unhold,
|
|
167
|
+
bL as laneStageHook,
|
|
168
|
+
bM as REACTIVE_ZOMBIE,
|
|
169
|
+
bN as installBoundaryHooks,
|
|
170
|
+
bO as CAUGHT_REARMED,
|
|
171
|
+
bP as OBSERVE$1
|
|
152
172
|
} from "./dev-shared.js";
|
|
153
173
|
export {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
174
|
+
bQ as clearSnapshots,
|
|
175
|
+
bR as enforceLoadingBoundary,
|
|
176
|
+
bS as getNextChildId,
|
|
177
|
+
bT as markSnapshotScope,
|
|
178
|
+
bU as peekNextChildId,
|
|
179
|
+
bV as releaseSnapshotScope,
|
|
180
|
+
bW as resetErrorHalt,
|
|
181
|
+
bX as setConsoleFooter,
|
|
182
|
+
bY as setSnapshotCapture,
|
|
183
|
+
bZ as setWriteHook
|
|
163
184
|
} from "./dev-shared.js";
|
|
164
185
|
|
|
186
|
+
// Truth reads (`until`, `refresh`; CONFIG_AUTHORITATIVE) over lane nodes.
|
|
187
|
+
// Side-effect free and apart from lanes.ts so a truth reader alone does not
|
|
188
|
+
// retain the lane engine: its reads reach these only through `laneRead`,
|
|
189
|
+
// which exists once something installs lanes (`createOptimistic`, verdicts).
|
|
190
|
+
/** A lane node's current value: its guess, or the latest it derived — the
|
|
191
|
+
* staging a shown lane holds for its next reveal, else its slot; the
|
|
192
|
+
* committed value while it has none (its first lane pass in flight). */
|
|
193
|
+
function laneValueOf(el) {
|
|
194
|
+
if (!(el._config & CONFIG_GUESS) && el._pendingValue !== NOT_PENDING) return el._pendingValue;
|
|
195
|
+
const v = el._x._lane;
|
|
196
|
+
return v !== NOT_PENDING ? v : el._value;
|
|
197
|
+
}
|
|
198
|
+
/** Truth readers (`until`, `refresh`; CONFIG_AUTHORITATIVE) — pay-for-use:
|
|
199
|
+
* the first one installs these on GlobalQueue (signals.ts `watch`).
|
|
200
|
+
*
|
|
201
|
+
* A truth reader's read of a lane node: the lane's value when it is the
|
|
202
|
+
* truth (#3943), else the truth staged beneath a guess, else the committed
|
|
203
|
+
* value. */
|
|
204
|
+
function truthRead(el) {
|
|
205
|
+
if (truth(el)) return laneValueOf(el);
|
|
206
|
+
if (!(el._config & CONFIG_GUESS)) throw new NotReadyError(null);
|
|
207
|
+
return el._pendingValue !== NOT_PENDING ? el._pendingValue : el._value;
|
|
208
|
+
}
|
|
209
|
+
/** A guess confirmed: its lane's truth readers re-read — of the lane's
|
|
210
|
+
* derivations too, which may be the truth now (#3943). */
|
|
211
|
+
function truthConfirmed(l) {
|
|
212
|
+
for (let i = 0; i < l._nodes.length; i++)
|
|
213
|
+
for (let s = l._nodes[i]._subs; s !== null; s = s._nextSub)
|
|
214
|
+
if (s._sub._config & CONFIG_AUTHORITATIVE) enqueueSub(s._sub);
|
|
215
|
+
}
|
|
216
|
+
/** A lane node's value is the truth when every guess beneath it is
|
|
217
|
+
* confirmed — its source staged a value equal to the guess — and nothing
|
|
218
|
+
* beneath it is held in the parent: same inputs, same value. */
|
|
219
|
+
function truth(n) {
|
|
220
|
+
if (n._config & CONFIG_GUESS)
|
|
221
|
+
return n._pendingValue !== NOT_PENDING && n._equals && n._equals(n._x._lane, n._pendingValue);
|
|
222
|
+
if (n._pendingValue !== NOT_PENDING) return;
|
|
223
|
+
if (n._config & CONFIG_OVERRIDE)
|
|
224
|
+
for (let d = n._deps; d; d = d._nextDep) if (!truth(d._dep)) return;
|
|
225
|
+
return 1;
|
|
226
|
+
}
|
|
227
|
+
|
|
165
228
|
// Lanes (plan sec. 28 replay, 2026-10-02; maintainer rulings 2026-10-01/02). A lane
|
|
166
229
|
// is "a new base of a transition": the sub-frame an optimistic write opens
|
|
167
230
|
// in the transition of the frame it is made in. It sees the screen plus its
|
|
@@ -201,14 +264,6 @@ function newLane(parent) {
|
|
|
201
264
|
lanes.push(l);
|
|
202
265
|
return l;
|
|
203
266
|
}
|
|
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
267
|
/** What the screen shows for a lane's node: the lane's value once the lane
|
|
213
268
|
* has revealed, the committed truth before. */
|
|
214
269
|
function display(el) {
|
|
@@ -273,23 +328,28 @@ function applyGuesses(parent) {
|
|
|
273
328
|
const n = pendingGuesses[i];
|
|
274
329
|
const v = pendingGuesses[i + 1];
|
|
275
330
|
let changed = true;
|
|
331
|
+
// The frame already holding the slot: a guess over it is a second
|
|
332
|
+
// suggestion for the slot, and two cannot finish apart (A34 (1)) — the
|
|
333
|
+
// frames entangle.
|
|
334
|
+
let holder = null;
|
|
276
335
|
if (n._config & CONFIG_OVERRIDE) {
|
|
277
336
|
// 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.
|
|
337
|
+
// over (its staging is void — the guess is the value now). The same
|
|
338
|
+
// value re-asked renews the question; nothing to notify.
|
|
282
339
|
changed = !n._equals || !n._equals(laneValueOf(n), v);
|
|
283
340
|
if (!(n._config & CONFIG_GUESS)) {
|
|
284
341
|
n._pendingValue = NOT_PENDING;
|
|
285
342
|
n._config |= CONFIG_GUESS;
|
|
286
343
|
}
|
|
287
344
|
n._x._lane = v;
|
|
288
|
-
|
|
289
|
-
if (parent !== null && holder !== null) merge$1(parent, holder);
|
|
345
|
+
holder = txOf(n)._parent;
|
|
290
346
|
} else {
|
|
291
347
|
let l;
|
|
292
|
-
|
|
348
|
+
// A held node: a truth staged there, or what a corrected guess showed.
|
|
349
|
+
// The guess composes on it, so it ends with that hold — and with its
|
|
350
|
+
// own frame's (F-11): it lands only when both have (the board's rapid
|
|
351
|
+
// adds — a later add's row reverted with an earlier add's landing).
|
|
352
|
+
if (n._config & CONFIG_HELD) l = newLane((holder = txOf(n)));
|
|
293
353
|
else {
|
|
294
354
|
if (parent === null && inFlight$1(n)) parent = newTransaction(false);
|
|
295
355
|
if (parent === null) continue;
|
|
@@ -305,6 +365,7 @@ function applyGuesses(parent) {
|
|
|
305
365
|
n._config |= CONFIG_OVERRIDE | CONFIG_GUESS;
|
|
306
366
|
list(n, l);
|
|
307
367
|
}
|
|
368
|
+
if (parent !== null && holder !== null) merge$1(parent, holder);
|
|
308
369
|
ext(n)._q = pendingGuesses[i + 2];
|
|
309
370
|
if (changed) {
|
|
310
371
|
// The lane's members re-derive as its work (REACTIVE_LANE_DIRTY): a
|
|
@@ -504,37 +565,6 @@ function supersede(n, value, changed) {
|
|
|
504
565
|
n._pendingValue = value;
|
|
505
566
|
joinFuture(parent);
|
|
506
567
|
}
|
|
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
568
|
/** A lane's end, two ways. `into === null`: the parent landed — the lane's
|
|
539
569
|
* values are the truth now: a guess lands the truth staged beneath it or
|
|
540
570
|
* reverts to the one it covered (`_value`, untouched by the guess); a
|
|
@@ -668,10 +698,21 @@ function dissolveLane(l, into, except) {
|
|
|
668
698
|
* member throws like any. */
|
|
669
699
|
function laneRead(c, el) {
|
|
670
700
|
const guess = el._config & CONFIG_GUESS;
|
|
671
|
-
if (c !== null && c._config & CONFIG_AUTHORITATIVE) return GlobalQueue._truth(el);
|
|
672
|
-
if (c === null) return laneValueOf(el);
|
|
673
701
|
const l = txOf(el);
|
|
702
|
+
// A lane a correction folded under (D-34): store/optimistic.ts reads it.
|
|
703
|
+
// Before the authoritative arm: an `until` outside the lane reads the
|
|
704
|
+
// same truth there, and joins the hold it folded into — its posture over
|
|
705
|
+
// a foreign hold (D-31), which the truth read alone would not take.
|
|
706
|
+
const folded = c !== null && l.folded;
|
|
707
|
+
if (folded) {
|
|
708
|
+
const v = folded(c, el);
|
|
709
|
+
if (v !== NOT_PENDING) return v;
|
|
710
|
+
}
|
|
711
|
+
if (c !== null && c._config & CONFIG_AUTHORITATIVE) return GlobalQueue._truth(el);
|
|
674
712
|
const status = el._statusFlags;
|
|
713
|
+
// An errored member's lane value predates its error; the error is the answer (#4106).
|
|
714
|
+
if (!guess && status & STATUS_ERROR) return NOT_PENDING;
|
|
715
|
+
if (c === null) return laneValueOf(el);
|
|
675
716
|
if (!tracking || c._config & CONFIG_CHILDREN_FORBIDDEN) {
|
|
676
717
|
// An untracked read inside lane work derives from the lane like a
|
|
677
718
|
// tracked one, minus the subscription — `untrack` is about
|
|
@@ -839,10 +880,12 @@ function laneWrite(el, v) {
|
|
|
839
880
|
// question is the one being asked (`question`: a slice's, a derive's
|
|
840
881
|
// continuation's — the flight's); unstamped writes and mainline are
|
|
841
882
|
// current.
|
|
842
|
-
|
|
883
|
+
// A store re-bases a keyed arrangement guess over an older truth, and
|
|
884
|
+
// folds a current one beneath it (D-34): true.
|
|
885
|
+
const older = stale(el, question);
|
|
886
|
+
if (GlobalQueue._laneRebase?.(el, v, older) || older) {
|
|
843
887
|
el._pendingValue = v;
|
|
844
888
|
el._config |= CONFIG_HELD;
|
|
845
|
-
GlobalQueue._laneRebase?.(el, v);
|
|
846
889
|
schedule();
|
|
847
890
|
return v;
|
|
848
891
|
}
|
|
@@ -898,20 +941,16 @@ function answered(el) {
|
|
|
898
941
|
}
|
|
899
942
|
return q;
|
|
900
943
|
}
|
|
901
|
-
GlobalQueue._laneRead = laneRead;
|
|
902
|
-
GlobalQueue._laneStage = laneStage;
|
|
903
|
-
GlobalQueue._laneOutcome = laneOutcome;
|
|
904
|
-
GlobalQueue._laneWrite = laneWrite;
|
|
905
944
|
/** The lanes alive before this seam's guesses opened theirs (a prefix of
|
|
906
945
|
* `lanes`; nothing dissolves one between here and the seam loop): a lane
|
|
907
946
|
* born at the seam has no work to judge yet (its passes run next round) —
|
|
908
947
|
* it is neither shown nor blocked until it has. */
|
|
909
948
|
let judged = 0;
|
|
910
|
-
|
|
949
|
+
const applyGuessesAt = parent => {
|
|
911
950
|
judged = lanes.length;
|
|
912
951
|
if (pendingGuesses.length !== 0) applyGuesses(parent);
|
|
913
952
|
};
|
|
914
|
-
|
|
953
|
+
const laneSeams = leaks => {
|
|
915
954
|
// Passes that read a write the parked frame holds as the screen
|
|
916
955
|
// (`stagedReaders`: lane work, a verdict reader before the verdict): they
|
|
917
956
|
// re-derive on the committed world next round (and register as stale
|
|
@@ -949,8 +988,17 @@ GlobalQueue._laneSeams = leaks => {
|
|
|
949
988
|
for (let k = Math.min(judged, lanes.length) - 1; k >= 0; k--)
|
|
950
989
|
laneSeam(lanes[k], leaked.indexOf(lanes[k]) !== -1);
|
|
951
990
|
};
|
|
952
|
-
|
|
953
|
-
|
|
991
|
+
installLaneHooks(
|
|
992
|
+
laneRead,
|
|
993
|
+
laneStage,
|
|
994
|
+
laneOutcome,
|
|
995
|
+
laneWrite,
|
|
996
|
+
applyGuessesAt,
|
|
997
|
+
laneSeams,
|
|
998
|
+
() => lanes.length !== 0 && laneCorrections(),
|
|
999
|
+
endLanes,
|
|
1000
|
+
lanesBlocked
|
|
1001
|
+
);
|
|
954
1002
|
GlobalQueue._laneGuesses = t => {
|
|
955
1003
|
const out = [];
|
|
956
1004
|
for (let i = 0; i < lanes.length; i++) {
|
|
@@ -962,7 +1010,6 @@ GlobalQueue._laneGuesses = t => {
|
|
|
962
1010
|
}
|
|
963
1011
|
return out;
|
|
964
1012
|
};
|
|
965
|
-
GlobalQueue._lanesBlocked = lanesBlocked;
|
|
966
1013
|
GlobalQueue._verdictLane = verdictLane;
|
|
967
1014
|
|
|
968
1015
|
// Verdicts (plan sec. 28 replay, 2026-10-02; maintainer, 2026-10-01: verdicts are
|
|
@@ -1116,7 +1163,7 @@ function heldNotFinal(owner) {
|
|
|
1116
1163
|
!latestActive &&
|
|
1117
1164
|
!owner._x._reask &&
|
|
1118
1165
|
!(owner._statusFlags & STATUS_UNINITIALIZED) &&
|
|
1119
|
-
(!owner._equals || !owner._equals(owner._value, owner._pendingValue))
|
|
1166
|
+
(!!owner._x._shownError || !owner._equals || !owner._equals(owner._value, owner._pendingValue))
|
|
1120
1167
|
);
|
|
1121
1168
|
}
|
|
1122
1169
|
/** `isPending`'s answer for an unheld node whose flight is up (cause ii). */
|
|
@@ -1160,6 +1207,38 @@ function latest(fn) {
|
|
|
1160
1207
|
setVerdict(prevVerdict);
|
|
1161
1208
|
}
|
|
1162
1209
|
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Reports whether what `fn` reads is still waiting on an update. Read the
|
|
1212
|
+
* values in question inside `fn`: the probe answers `true` while any of
|
|
1213
|
+
* them depends on a change that is in flight — a write or an async source
|
|
1214
|
+
* that has started producing a new value whose result has not landed yet —
|
|
1215
|
+
* and `false` once the latest value is the one on screen. The answer is
|
|
1216
|
+
* tracked like any read, so a memo or effect that calls `isPending` re-runs
|
|
1217
|
+
* when it flips.
|
|
1218
|
+
*
|
|
1219
|
+
* A read that has no value yet is loading, not pending: inside `fn` it
|
|
1220
|
+
* follows the surrounding `Loading` path like any other read, and the probe
|
|
1221
|
+
* reports only updates to a value that already exists. A bare `refresh()`
|
|
1222
|
+
* does not flip `isPending`; pair it with `affects()` to make the re-ask
|
|
1223
|
+
* visible.
|
|
1224
|
+
*
|
|
1225
|
+
* @param fn expression to probe; read the values in question inside it
|
|
1226
|
+
*
|
|
1227
|
+
* @returns `true` while a read inside `fn` depends on a change that has not committed yet
|
|
1228
|
+
*
|
|
1229
|
+
* @example
|
|
1230
|
+
* ```ts
|
|
1231
|
+
* const [query, setQuery] = createSignal("");
|
|
1232
|
+
* const results = createMemo(() => searchProducts(query()));
|
|
1233
|
+
* const stale = createMemo(() => isPending(results));
|
|
1234
|
+
*
|
|
1235
|
+
* setQuery("shoes");
|
|
1236
|
+
* // stale() is true until the new results land, while results() keeps
|
|
1237
|
+
* // serving the previous list — dim it instead of showing a spinner.
|
|
1238
|
+
* ```
|
|
1239
|
+
*
|
|
1240
|
+
* @description https://v2.solidjs.com/reference/solid-js/reactivity/is-pending
|
|
1241
|
+
*/
|
|
1163
1242
|
function isPending(fn) {
|
|
1164
1243
|
markVerdictReader(1);
|
|
1165
1244
|
const prevProbing = probing;
|
|
@@ -1186,12 +1265,17 @@ function isPending(fn) {
|
|
|
1186
1265
|
setVerdict(prevVerdict);
|
|
1187
1266
|
}
|
|
1188
1267
|
}
|
|
1268
|
+
/** The staging, or the staged error thrown (D-41). */
|
|
1269
|
+
function staged(el) {
|
|
1270
|
+
if (el._config & CONFIG_STAGED_ERROR) throw el._pendingValue;
|
|
1271
|
+
return el._pendingValue;
|
|
1272
|
+
}
|
|
1189
1273
|
/** `latest()` of a held node: the flushed staging — the proposal — and, for
|
|
1190
1274
|
* a tracked reader of a rewrite not yet flushed, the late-linker re-run in
|
|
1191
1275
|
* the flush that carries it. */
|
|
1192
1276
|
function heldLatest(el, c) {
|
|
1193
1277
|
const u = unflushedValue(el);
|
|
1194
|
-
if (u === NOT_PENDING) return el
|
|
1278
|
+
if (u === NOT_PENDING) return staged(el);
|
|
1195
1279
|
if (c !== null) markLateLinker(c);
|
|
1196
1280
|
return u;
|
|
1197
1281
|
}
|
|
@@ -1239,7 +1323,10 @@ function verdictValue(el, c) {
|
|
|
1239
1323
|
probeFound = true;
|
|
1240
1324
|
if (tracked && flushTransaction !== null) verdictRead(c, flushTransaction);
|
|
1241
1325
|
}
|
|
1242
|
-
if (
|
|
1326
|
+
if (
|
|
1327
|
+
el._config & CONFIG_OVERRIDE &&
|
|
1328
|
+
!(owner._statusFlags & STATUS_ERROR && !(el._config & CONFIG_GUESS))
|
|
1329
|
+
) {
|
|
1243
1330
|
const l = txOf(el);
|
|
1244
1331
|
if (tracked) verdictRead(c, l._parent ?? l);
|
|
1245
1332
|
const flying = (owner._statusFlags & STATUS_PENDING) !== 0 && !quietPending(owner);
|
|
@@ -1251,6 +1338,9 @@ function verdictValue(el, c) {
|
|
|
1251
1338
|
(!el._equals || !el._equals(el._x._lane, el._pendingValue))))
|
|
1252
1339
|
)
|
|
1253
1340
|
probeFound = true;
|
|
1341
|
+
// A lane a correction folded under (D-34): `latest()` shows the hold's
|
|
1342
|
+
// world whole — the truth beneath each guess, else what it covered.
|
|
1343
|
+
if (latestActive && l.folded) return l.folded(null, el);
|
|
1254
1344
|
return el._x._lane;
|
|
1255
1345
|
}
|
|
1256
1346
|
if (probing && flying) probeFound = true;
|
|
@@ -1304,7 +1394,7 @@ function verdictValue(el, c) {
|
|
|
1304
1394
|
// has not shown content) and answers for the commit, where it is final.
|
|
1305
1395
|
if (el._pendingValue !== NOT_PENDING && !(owner._statusFlags & STATUS_PENDING)) {
|
|
1306
1396
|
if (probing && owned) joinPass(c, el);
|
|
1307
|
-
return el
|
|
1397
|
+
return staged(el);
|
|
1308
1398
|
}
|
|
1309
1399
|
} else if (el._pendingValue !== NOT_PENDING) {
|
|
1310
1400
|
if (owner._statusFlags & STATUS_PENDING && !uninitialized)
|
|
@@ -1321,7 +1411,7 @@ function verdictValue(el, c) {
|
|
|
1321
1411
|
// A28: an unflushed write is not what `latest` serves; a tracked
|
|
1322
1412
|
// reader derived from the flushed world runs again in the carrying
|
|
1323
1413
|
// flush.
|
|
1324
|
-
if (globalQueue._running) return el
|
|
1414
|
+
if (globalQueue._running) return staged(el);
|
|
1325
1415
|
if (c !== null) markLateLinker(c);
|
|
1326
1416
|
}
|
|
1327
1417
|
return el._value;
|
|
@@ -1343,7 +1433,7 @@ function verdictValue(el, c) {
|
|
|
1343
1433
|
// Installed at module evaluation — present exactly when something imports
|
|
1344
1434
|
// `latest` or `isPending`. (`read` dispatches to `verdictValue` through
|
|
1345
1435
|
// `verdict`, set while a window is open.)
|
|
1346
|
-
|
|
1436
|
+
installVerdictHooks(observeFlight);
|
|
1347
1437
|
/** A store read inside an untracked `isPending` probe found a live mark on
|
|
1348
1438
|
* a record with no node to carry it (store/affects.ts): not final. */
|
|
1349
1439
|
GlobalQueue._witnessMark = () => {
|
|
@@ -1589,7 +1679,7 @@ let externalSourceConfig = null;
|
|
|
1589
1679
|
* ```
|
|
1590
1680
|
*/
|
|
1591
1681
|
// Wires a freshly created computed through the active external-source bridge.
|
|
1592
|
-
// Lives here (installed
|
|
1682
|
+
// Lives here (installed as a scheduler hook while a config is active) rather than
|
|
1593
1683
|
// inline in core: esbuild cannot literal-track the mutable config binding the
|
|
1594
1684
|
// way rollup does, so an inline `if (externalSourceConfig)` block ships in
|
|
1595
1685
|
// every bundle even though only enableExternalSource() can make it reachable.
|
|
@@ -1611,8 +1701,8 @@ function externalUntrack(fn) {
|
|
|
1611
1701
|
// removed on reset) so core's presence checks stay equivalent to the old
|
|
1612
1702
|
// `externalSourceConfig` truthiness checks.
|
|
1613
1703
|
function syncExternalHooks() {
|
|
1614
|
-
|
|
1615
|
-
|
|
1704
|
+
if (externalSourceConfig) setExternalHooks(wireExternalSource, externalUntrack);
|
|
1705
|
+
else setExternalHooks(undefined, undefined);
|
|
1616
1706
|
}
|
|
1617
1707
|
function enableExternalSource(config) {
|
|
1618
1708
|
const { factory, untrack: untrackFn = fn => fn() } = config;
|
|
@@ -1638,6 +1728,61 @@ function enableExternalSource(config) {
|
|
|
1638
1728
|
syncExternalHooks();
|
|
1639
1729
|
}
|
|
1640
1730
|
|
|
1731
|
+
// Deferred hydration, dependency edges: plain-signal identities.
|
|
1732
|
+
// While on (`setSignalIdentities`; the server-components client turns it on
|
|
1733
|
+
// before hydrating), a plain signal created under an id-bearing owner is
|
|
1734
|
+
// named `<owner id>~<n>`, n counting the plain signals (and plain store
|
|
1735
|
+
// roots: `createStore` over an object, store/edges.ts) created under that
|
|
1736
|
+
// owner in its current pass (transparent owners defer to the nearest
|
|
1737
|
+
// id-bearing one, as child ids do). The server names the same signal the
|
|
1738
|
+
// same way and restarts the count on every re-run, so an edge it shipped (a
|
|
1739
|
+
// slot fill read it) matches the client node; the write hook receives the
|
|
1740
|
+
// node and looks its name up (`signalName`). The count restarts when the owner's pass
|
|
1741
|
+
// generation (`_depGen`, bumped by every recompute) moves on. Only the
|
|
1742
|
+
// frames client imports this module, so nothing here ships without it.
|
|
1743
|
+
// Names live in a side table, not on the node: frames reads them across the
|
|
1744
|
+
// package boundary, where a mangled field would not survive.
|
|
1745
|
+
let counts = null;
|
|
1746
|
+
const names = new WeakMap();
|
|
1747
|
+
/** @internal The identity of a plain signal named while identities were on. */
|
|
1748
|
+
function signalName(node) {
|
|
1749
|
+
return node === undefined ? undefined : names.get(node);
|
|
1750
|
+
}
|
|
1751
|
+
function identify(node) {
|
|
1752
|
+
let o = getOwner();
|
|
1753
|
+
if (!o) return;
|
|
1754
|
+
while (o._config & CONFIG_TRANSPARENT && o._parent) o = o._parent;
|
|
1755
|
+
if (o.id == null) return;
|
|
1756
|
+
const gen = o._depGen;
|
|
1757
|
+
let c = counts.get(o);
|
|
1758
|
+
if (c === undefined || c[0] !== gen) counts.set(o, (c = [gen, 0]));
|
|
1759
|
+
const sid = o.id + "~" + c[1]++;
|
|
1760
|
+
// A store root named twice (one object made into two stores, or a store
|
|
1761
|
+
// handed to `createStore`) is one target under two server names: its
|
|
1762
|
+
// writes cannot be attributed (an empty name).
|
|
1763
|
+
names.set(node, names.has(node) ? "" : sid);
|
|
1764
|
+
}
|
|
1765
|
+
/** @internal Name plain signals from now on (`on`), or stop. */
|
|
1766
|
+
function setSignalIdentities(on) {
|
|
1767
|
+
counts = on ? counts || new WeakMap() : null;
|
|
1768
|
+
signalHook = on ? identify : null;
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* @internal Called with every plain signal `createSignal` makes, and every
|
|
1772
|
+
* plain store root `createStore` makes, while set (the namer above). Never
|
|
1773
|
+
* set unless `setSignalIdentities` is imported, so a bundle without it keeps
|
|
1774
|
+
* only a null check.
|
|
1775
|
+
*/
|
|
1776
|
+
let signalHook = null;
|
|
1777
|
+
/**
|
|
1778
|
+
* @internal Dev only: `watch` is called with every node read while set (the
|
|
1779
|
+
* dev hook `onRead`) — the frames client's identity-drift check watches the
|
|
1780
|
+
* named signals a deferred occurrence reads as it mounts. A no-op in prod.
|
|
1781
|
+
*/
|
|
1782
|
+
function setReadWatch(watch) {
|
|
1783
|
+
DEV$1.hooks.onRead = watch || undefined;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1641
1786
|
/**
|
|
1642
1787
|
* Context provides a form of dependency injection. It is used to save from needing to pass
|
|
1643
1788
|
* data as props through intermediate components. This function creates a new context object
|
|
@@ -1647,6 +1792,10 @@ function enableExternalSource(config) {
|
|
|
1647
1792
|
* via a `setContext` call.
|
|
1648
1793
|
*/
|
|
1649
1794
|
function createContext(defaultValue, description) {
|
|
1795
|
+
// Dev: an explicit `undefined` default is still no default (`undefined`
|
|
1796
|
+
// means "not provided"); remembered so a failed read can say so (#4181).
|
|
1797
|
+
if (arguments.length && defaultValue === undefined)
|
|
1798
|
+
return { id: Symbol(description), defaultValue, _undefinedDefault: true };
|
|
1650
1799
|
return { id: Symbol(description), defaultValue };
|
|
1651
1800
|
}
|
|
1652
1801
|
/**
|
|
@@ -1667,7 +1816,9 @@ function getContext(context, owner = getOwner()) {
|
|
|
1667
1816
|
let value = owner._context[context.id];
|
|
1668
1817
|
if (value === undefined) value = context.defaultValue;
|
|
1669
1818
|
if (value === undefined) {
|
|
1670
|
-
|
|
1819
|
+
// Dev names the context and why it has no value: no provider, or a
|
|
1820
|
+
// provider that passed `undefined` (the key is set, to `undefined`).
|
|
1821
|
+
throw new ContextNotFoundError(context, context.id in owner._context);
|
|
1671
1822
|
}
|
|
1672
1823
|
return value;
|
|
1673
1824
|
}
|
|
@@ -1757,7 +1908,12 @@ function notifyEffectStatus(status, error) {
|
|
|
1757
1908
|
return;
|
|
1758
1909
|
}
|
|
1759
1910
|
if (!globalQueue.notify(this, STATUS_ERROR, STATUS_ERROR)) {
|
|
1760
|
-
|
|
1911
|
+
// Dev locates the halt at the node that threw: the StatusError's
|
|
1912
|
+
// source, upstream of this effect when a computed failed.
|
|
1913
|
+
haltReactivityAt(
|
|
1914
|
+
unwrapStatusError(actualError),
|
|
1915
|
+
actualError instanceof StatusError ? actualError.source : this
|
|
1916
|
+
);
|
|
1761
1917
|
throw actualError;
|
|
1762
1918
|
}
|
|
1763
1919
|
} else if (this._type === EFFECT_RENDER) {
|
|
@@ -1798,7 +1954,7 @@ function runEffect(node, type) {
|
|
|
1798
1954
|
// hidden subtree. The synchronous first render on creation is not a queued
|
|
1799
1955
|
// run and goes through. `_modified` stays set; the boundary re-queues the
|
|
1800
1956
|
// run (`release`).
|
|
1801
|
-
if (
|
|
1957
|
+
if (heldRunHook?.(node)) return;
|
|
1802
1958
|
// Error arm (#2840), user effects only: a compute-phase error that is still
|
|
1803
1959
|
// the node's settled state at effect time runs the bundle's error handler in
|
|
1804
1960
|
// this same imperative, writable scope. Unwrap the StatusError used for
|
|
@@ -1823,7 +1979,7 @@ function runEffect(node, type) {
|
|
|
1823
1979
|
: console.error(err);
|
|
1824
1980
|
} catch (error) {
|
|
1825
1981
|
if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
|
|
1826
|
-
|
|
1982
|
+
haltReactivityAt(error, node);
|
|
1827
1983
|
throw error;
|
|
1828
1984
|
}
|
|
1829
1985
|
}
|
|
@@ -1859,7 +2015,7 @@ function runEffect(node, type) {
|
|
|
1859
2015
|
ext(node)._error = new StatusError(node, error);
|
|
1860
2016
|
node._statusFlags |= STATUS_ERROR;
|
|
1861
2017
|
if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
|
|
1862
|
-
|
|
2018
|
+
haltReactivityAt(error, node);
|
|
1863
2019
|
throw error;
|
|
1864
2020
|
}
|
|
1865
2021
|
} finally {
|
|
@@ -1893,7 +2049,7 @@ function trackedEffect(fn, options) {
|
|
|
1893
2049
|
// pass, but several passes in one flush each enqueue `_run` into the same
|
|
1894
2050
|
// user queue, and this gate is what collapses them into one run.
|
|
1895
2051
|
if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
|
|
1896
|
-
if (
|
|
2052
|
+
if (heldRunHook?.(node)) return;
|
|
1897
2053
|
setTrackedQueueCallback(true);
|
|
1898
2054
|
try {
|
|
1899
2055
|
node._modified = false;
|
|
@@ -2024,6 +2180,155 @@ function reportClientError(error, owner, thrower) {
|
|
|
2024
2180
|
}
|
|
2025
2181
|
}
|
|
2026
2182
|
|
|
2183
|
+
/**
|
|
2184
|
+
* Ownership stamp (#3360): every backing the store ALLOCATES (CoW clones,
|
|
2185
|
+
* privatized committed backings) carries its owning target under this
|
|
2186
|
+
* enumerable symbol. One property write replaces the two weak-collection
|
|
2187
|
+
* registrations (ownership set + raw→target map) a fresh object used to pay
|
|
2188
|
+
* per draft — V8's identity-hash + ephemeron cost dominated the one-key
|
|
2189
|
+
* write floor. Enumerable so a spread copy (the plain-data clone path) stays
|
|
2190
|
+
* on the fast path and carries the stamp along.
|
|
2191
|
+
*
|
|
2192
|
+
* Owned backings are never user-reachable (`snapshot` copies them, the traps
|
|
2193
|
+
* hide the key), so every raw key walk in the store must skip `$OWNER`, and
|
|
2194
|
+
* ownership is answered by `isOwned` — a user object never carries it.
|
|
2195
|
+
* Overlay drafts (`Object.create(v)` over an owned `v`) inherit the stamp.
|
|
2196
|
+
*/
|
|
2197
|
+
const $OWNER = Symbol("STORE_OWNER");
|
|
2198
|
+
/** raw → target for plain (family-less) stores. Family stores use their own
|
|
2199
|
+
* map so one raw can wrap once per family (§7b). */
|
|
2200
|
+
const storeLookup = new WeakMap();
|
|
2201
|
+
function setLookup(raw, t) {
|
|
2202
|
+
(t.fam?.map ?? storeLookup).set(raw, t);
|
|
2203
|
+
}
|
|
2204
|
+
function isOwned(raw) {
|
|
2205
|
+
return raw[$OWNER] !== undefined;
|
|
2206
|
+
}
|
|
2207
|
+
/** The target wrapping `raw` in `fam` (or the plain lookup). An owned raw
|
|
2208
|
+
* answers from its stamp; a user raw from the map. */
|
|
2209
|
+
function lookupTarget(raw, fam) {
|
|
2210
|
+
const owner = raw[$OWNER];
|
|
2211
|
+
return owner !== undefined && owner.fam === fam ? owner : (fam?.map ?? storeLookup).get(raw);
|
|
2212
|
+
}
|
|
2213
|
+
function devAssertNeverUserMutation(target) {
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
/** Sticky descendants flag up the parent chain (§6d): adoption descends
|
|
2217
|
+
* into a changed child pair only where a target with nodes exists below. */
|
|
2218
|
+
function markDescendants(target) {
|
|
2219
|
+
let t = target;
|
|
2220
|
+
while (t && !t.d) {
|
|
2221
|
+
t.d = true;
|
|
2222
|
+
t = t.u;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
/** Observe tier: the owner in scope at `createStore` (attribution names and
|
|
2226
|
+
* owner paths); the `_owner` write never reaches the proxy. */
|
|
2227
|
+
const storeOwners = new WeakMap();
|
|
2228
|
+
/** Brand keys the proxy answers directly (never user data). */
|
|
2229
|
+
const $TRACK = Symbol("STORE_TRACK"),
|
|
2230
|
+
$TARGET = Symbol("STORE_TARGET"),
|
|
2231
|
+
$PROXY = Symbol("STORE_PROXY"),
|
|
2232
|
+
$RECORD = Symbol("VIEW_RECORD");
|
|
2233
|
+
// ---------------------------------------------------------------------------
|
|
2234
|
+
// raw marking (R42 / RUL-12: a value is never both wrapped and raw)
|
|
2235
|
+
const rawValues = new WeakSet();
|
|
2236
|
+
let rawValuesUsed = false;
|
|
2237
|
+
function isRawValue(value) {
|
|
2238
|
+
return rawValuesUsed && rawValues.has(value);
|
|
2239
|
+
}
|
|
2240
|
+
/** Mark a value so no store ever wraps it (sticky; it reads as a leaf). */
|
|
2241
|
+
function markRaw(value) {
|
|
2242
|
+
if (isWrappable(value)) {
|
|
2243
|
+
rawValuesUsed = true;
|
|
2244
|
+
rawValues.add(value);
|
|
2245
|
+
}
|
|
2246
|
+
return value;
|
|
2247
|
+
}
|
|
2248
|
+
/** A shallow store's ingested record: raw by contract (values served
|
|
2249
|
+
* verbatim, slots replaced by reference). */
|
|
2250
|
+
function markRawOne(v) {
|
|
2251
|
+
if (isWrappable(v)) {
|
|
2252
|
+
if (v[$TARGET] !== undefined) return;
|
|
2253
|
+
if (lookupTarget(v, null) !== undefined)
|
|
2254
|
+
throw new Error(
|
|
2255
|
+
"shallow store: an ingested record is already tracked as a deep store — one value cannot present both wrapped and raw"
|
|
2256
|
+
);
|
|
2257
|
+
rawValuesUsed = true;
|
|
2258
|
+
rawValues.add(v);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
function markRawIngest(container) {
|
|
2262
|
+
if (Array.isArray(container)) {
|
|
2263
|
+
for (let i = 0, len = container.length; i < len; i++) markRawOne(container[i]);
|
|
2264
|
+
} else {
|
|
2265
|
+
for (const k in container) markRawOne(container[k]);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
// ---------------------------------------------------------------------------
|
|
2269
|
+
// wrappability
|
|
2270
|
+
const OBJECT_PROTO = Object.prototype;
|
|
2271
|
+
const wrappableProtos = new WeakMap();
|
|
2272
|
+
function isWrappable(obj) {
|
|
2273
|
+
if (obj == null || typeof obj !== "object" || Object.isFrozen(obj)) return false;
|
|
2274
|
+
const proto = Object.getPrototypeOf(obj);
|
|
2275
|
+
if (proto === OBJECT_PROTO || proto === null) return true;
|
|
2276
|
+
if (Array.isArray(obj)) return true;
|
|
2277
|
+
let wrappable = wrappableProtos.get(proto);
|
|
2278
|
+
if (wrappable === undefined) {
|
|
2279
|
+
wrappable =
|
|
2280
|
+
Object.prototype.toString.call(obj) === "[object Object]" &&
|
|
2281
|
+
(typeof Node === "undefined" || !(obj instanceof Node));
|
|
2282
|
+
wrappableProtos.set(proto, wrappable);
|
|
2283
|
+
}
|
|
2284
|
+
return wrappable;
|
|
2285
|
+
}
|
|
2286
|
+
function ownEnumerableKeys(o) {
|
|
2287
|
+
return Reflect.ownKeys(o).filter(k => Object.prototype.propertyIsEnumerable.call(o, k));
|
|
2288
|
+
}
|
|
2289
|
+
// ---------------------------------------------------------------------------
|
|
2290
|
+
// write override: a projection's draft ops carry it (the derive is the
|
|
2291
|
+
// author — its writes must land outside the sync setter scope too, in a
|
|
2292
|
+
// continuation after an `await`/`yield`). Projections return in S3; the
|
|
2293
|
+
// traps already honour it.
|
|
2294
|
+
let writeOverride = false;
|
|
2295
|
+
function setWriteOverride(value) {
|
|
2296
|
+
writeOverride = value;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
/**
|
|
2300
|
+
* Deferred hydration, dependency edges for stores. Only the frames client
|
|
2301
|
+
* imports this module, so nothing here ships without it.
|
|
2302
|
+
*
|
|
2303
|
+
* A plain store root (`createStore` over an object) is named like a plain
|
|
2304
|
+
* signal (core/identities.ts: `<owner id>~<n>`, on the same count), and the
|
|
2305
|
+
* whole store goes by that one name: the server records it for any read of
|
|
2306
|
+
* the store, at any depth, and a setter call on the store — on its root or
|
|
2307
|
+
* on a proxy nested in it — is a write to it. Its readers mount at the
|
|
2308
|
+
* setter's entry, before the draft runs, as a plain signal's do.
|
|
2309
|
+
*/
|
|
2310
|
+
/**
|
|
2311
|
+
* @internal The name of the store `x` belongs to — `x` a store proxy (a
|
|
2312
|
+
* setter's entry) or one of its property nodes (the setter notifying it) —
|
|
2313
|
+
* or undefined when it has none or it cannot be placed: a projection or
|
|
2314
|
+
* optimistic family, a store chained over another store's proxy, a root
|
|
2315
|
+
* named twice, or a proxy reached through a store other than the one it
|
|
2316
|
+
* was named as the root of.
|
|
2317
|
+
*/
|
|
2318
|
+
function storeName(x) {
|
|
2319
|
+
if (x === undefined) return undefined;
|
|
2320
|
+
let t = x._config & CONFIG_SLOT_NODE ? x._host : x[$TARGET];
|
|
2321
|
+
if (t === undefined) return undefined;
|
|
2322
|
+
// Up to the root; a target on the way named as a root of its own is one
|
|
2323
|
+
// object in two stores.
|
|
2324
|
+
while (t.u !== null) {
|
|
2325
|
+
if (t.fam !== null || t.ch || signalName(t) !== undefined) return undefined;
|
|
2326
|
+
t = t.u;
|
|
2327
|
+
}
|
|
2328
|
+
if (t.fam !== null || t.ch) return undefined;
|
|
2329
|
+
return signalName(t) || undefined;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2027
2332
|
/**
|
|
2028
2333
|
* Low-level reactive-cleanup primitive. Registers a callback that runs when
|
|
2029
2334
|
* the surrounding owner is disposed.
|
|
@@ -2112,6 +2417,7 @@ function createSignal(first, second) {
|
|
|
2112
2417
|
}
|
|
2113
2418
|
const node = signal(first, second);
|
|
2114
2419
|
registerGraph(node, getOwner());
|
|
2420
|
+
if (signalHook !== null) signalHook(node);
|
|
2115
2421
|
return [accessor(node), setSignal.bind(null, node)];
|
|
2116
2422
|
}
|
|
2117
2423
|
function createOptimistic(first, second) {
|
|
@@ -2193,7 +2499,7 @@ function createMemo(compute, options) {
|
|
|
2193
2499
|
* );
|
|
2194
2500
|
* ```
|
|
2195
2501
|
*
|
|
2196
|
-
* @description https://
|
|
2502
|
+
* @description https://v2.solidjs.com/reference/solid-js/reactivity/create-effect
|
|
2197
2503
|
*/
|
|
2198
2504
|
function createEffect(compute, effectFn, options) {
|
|
2199
2505
|
if (effectFn === undefined) {
|
|
@@ -2242,7 +2548,7 @@ function createEffect(compute, effectFn, options) {
|
|
|
2242
2548
|
* }
|
|
2243
2549
|
* ```
|
|
2244
2550
|
*
|
|
2245
|
-
* @description https://
|
|
2551
|
+
* @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-render-effect
|
|
2246
2552
|
*/
|
|
2247
2553
|
function createRenderEffect(compute, effectFn, options) {
|
|
2248
2554
|
effect(compute, effectFn, undefined, options);
|
|
@@ -2290,7 +2596,7 @@ function createRenderEffect(compute, effectFn, options) {
|
|
|
2290
2596
|
* });
|
|
2291
2597
|
* ```
|
|
2292
2598
|
*
|
|
2293
|
-
* @description https://
|
|
2599
|
+
* @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-tracked-effect
|
|
2294
2600
|
*/
|
|
2295
2601
|
function createTrackedEffect(compute, options) {
|
|
2296
2602
|
trackedEffect(compute, options);
|
|
@@ -2319,7 +2625,7 @@ function createTrackedEffect(compute, options) {
|
|
|
2319
2625
|
* setCount(1); // logs once, reaction re-armed for next change
|
|
2320
2626
|
* ```
|
|
2321
2627
|
*
|
|
2322
|
-
* @description https://
|
|
2628
|
+
* @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-reaction
|
|
2323
2629
|
*/
|
|
2324
2630
|
function createReaction(effectFn, options) {
|
|
2325
2631
|
let cl = undefined;
|
|
@@ -2338,7 +2644,12 @@ function createReaction(effectFn, options) {
|
|
|
2338
2644
|
}
|
|
2339
2645
|
runWithOwner(owner, () => {
|
|
2340
2646
|
effect(
|
|
2341
|
-
() =>
|
|
2647
|
+
() => {
|
|
2648
|
+
const node = getOwner();
|
|
2649
|
+
arm = node;
|
|
2650
|
+
tracking();
|
|
2651
|
+
return node;
|
|
2652
|
+
},
|
|
2342
2653
|
node => {
|
|
2343
2654
|
arm = undefined;
|
|
2344
2655
|
cl?.();
|
|
@@ -2389,15 +2700,21 @@ function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction)
|
|
|
2389
2700
|
// after: the first pass's own error is the promise's rejection.)
|
|
2390
2701
|
computed(
|
|
2391
2702
|
() => {
|
|
2392
|
-
let v
|
|
2703
|
+
let v,
|
|
2704
|
+
failed = false;
|
|
2393
2705
|
try {
|
|
2394
2706
|
v = fn();
|
|
2395
2707
|
} catch (e) {
|
|
2396
2708
|
if (e instanceof NotReadyError) throw e;
|
|
2397
|
-
|
|
2398
|
-
|
|
2709
|
+
// An error is an answer (D-41): delivered by the same frame rule.
|
|
2710
|
+
v = unwrapStatusError(e);
|
|
2711
|
+
failed = true;
|
|
2399
2712
|
}
|
|
2400
2713
|
const c = getOwner();
|
|
2714
|
+
// A truth reader waits out a `createDeferred` flight its value derives
|
|
2715
|
+
// from, which serves everyone else the stale answer (D-40).
|
|
2716
|
+
const lag = config & CONFIG_AUTHORITATIVE && GlobalQueue._lagging?.(c);
|
|
2717
|
+
if (lag) throw new NotReadyError(lag);
|
|
2401
2718
|
// (Outside a flush a mainline pass joins through `passTx`.)
|
|
2402
2719
|
const joined = flushTransaction ?? passTx;
|
|
2403
2720
|
if (
|
|
@@ -2408,6 +2725,7 @@ function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction)
|
|
|
2408
2725
|
staleReader(c, resolveTx(joined));
|
|
2409
2726
|
return;
|
|
2410
2727
|
}
|
|
2728
|
+
if (failed) return void queueMicrotask(() => onError(v));
|
|
2411
2729
|
inPass?.(v);
|
|
2412
2730
|
queueMicrotask(() => onValue(v));
|
|
2413
2731
|
},
|
|
@@ -2697,192 +3015,76 @@ function until(fn, options) {
|
|
|
2697
3015
|
* onSettled(() => {
|
|
2698
3016
|
* const onResize = () => setWidth(window.innerWidth);
|
|
2699
3017
|
* window.addEventListener("resize", onResize);
|
|
2700
|
-
* return () => window.removeEventListener("resize", onResize);
|
|
2701
|
-
* });
|
|
2702
|
-
* return width;
|
|
2703
|
-
* }
|
|
2704
|
-
* ```
|
|
2705
|
-
*
|
|
2706
|
-
* @example
|
|
2707
|
-
* ```tsx
|
|
2708
|
-
* // Post-settle "ready" hook — no cleanup needed.
|
|
2709
|
-
* function Dashboard() {
|
|
2710
|
-
* const data = createMemo(async () => fetchData());
|
|
2711
|
-
*
|
|
2712
|
-
* onSettled(() => {
|
|
2713
|
-
* analytics.track("dashboard.ready");
|
|
2714
|
-
* });
|
|
2715
|
-
*
|
|
2716
|
-
* return <Loading fallback={<Spinner />}><pre>{data()}</pre></Loading>;
|
|
2717
|
-
* }
|
|
2718
|
-
* ```
|
|
2719
|
-
*
|
|
2720
|
-
* @example
|
|
2721
|
-
* ```tsx
|
|
2722
|
-
* // Event-handler — runs after the action settles.
|
|
2723
|
-
* function SaveButton() {
|
|
2724
|
-
* const save = action(function* () {
|
|
2725
|
-
* yield api.save();
|
|
2726
|
-
* });
|
|
2727
|
-
*
|
|
2728
|
-
* const handleClick = () => {
|
|
2729
|
-
* save();
|
|
2730
|
-
* onSettled(() => toast("Saved!"));
|
|
2731
|
-
* };
|
|
2732
|
-
*
|
|
2733
|
-
* return <button onClick={handleClick}>Save</button>;
|
|
2734
|
-
* }
|
|
2735
|
-
* ```
|
|
2736
|
-
*
|
|
2737
|
-
* @param callback Function to run; may return a cleanup function that fires
|
|
2738
|
-
* on owner disposal
|
|
2739
|
-
*/
|
|
2740
|
-
function onSettled(callback) {
|
|
2741
|
-
const owner = getOwner();
|
|
2742
|
-
owner && !(owner._config & CONFIG_CHILDREN_FORBIDDEN)
|
|
2743
|
-
? trackedEffect(() => untrack(callback), { name: "onSettled" })
|
|
2744
|
-
: globalQueue.enqueue(EFFECT_USER, function fire() {
|
|
2745
|
-
// Settled means derived. A settle that reverts optimism (or replays
|
|
2746
|
-
// gated reads) only enqueues the affected subscribers; the pass after
|
|
2747
|
-
// the commit re-derives them. Fired in the commit pass, the callback
|
|
2748
|
-
// read the optimistic source already reverted beside a sync memo of it
|
|
2749
|
-
// still holding the optimistic value — reads do not pull (#3411). Fall
|
|
2750
|
-
// to the next pass while the heap has work; `run` swapped the queue,
|
|
2751
|
-
// so this lands there, and `enqueue` keeps the drain alive.
|
|
2752
|
-
if (dirtyQueue._max >= dirtyQueue._min) return globalQueue.enqueue(EFFECT_USER, fire);
|
|
2753
|
-
// Unowned, out-of-band fire (no owner, or a children-forbidden one this
|
|
2754
|
-
// one-shot must not bind to): a returned cleanup has no lifecycle to
|
|
2755
|
-
// attach to. Reject it in dev; in production the return is simply
|
|
2756
|
-
// dropped — never bound to an unrelated owner or run eagerly.
|
|
2757
|
-
const cleanup = callback();
|
|
2758
|
-
if (cleanup !== undefined) {
|
|
2759
|
-
const message =
|
|
2760
|
-
"[SETTLED_CLEANUP_UNOWNED] onSettled returned a cleanup in an unowned scope; a cleanup can only be honored under an owner. Call your setup helper from an owned scope (e.g. the component body) instead of from inside an event handler, tracked effect, or another onSettled.";
|
|
2761
|
-
emitDiagnostic({
|
|
2762
|
-
code: "SETTLED_CLEANUP_UNOWNED",
|
|
2763
|
-
kind: "lifecycle",
|
|
2764
|
-
severity: "error",
|
|
2765
|
-
message
|
|
2766
|
-
});
|
|
2767
|
-
throw new Error(message);
|
|
2768
|
-
}
|
|
2769
|
-
});
|
|
2770
|
-
}
|
|
2771
|
-
|
|
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;
|
|
3018
|
+
* return () => window.removeEventListener("resize", onResize);
|
|
3019
|
+
* });
|
|
3020
|
+
* return width;
|
|
3021
|
+
* }
|
|
3022
|
+
* ```
|
|
3023
|
+
*
|
|
3024
|
+
* @example
|
|
3025
|
+
* ```tsx
|
|
3026
|
+
* // Post-settle "ready" hook — no cleanup needed.
|
|
3027
|
+
* function Dashboard() {
|
|
3028
|
+
* const data = createMemo(async () => fetchData());
|
|
3029
|
+
*
|
|
3030
|
+
* onSettled(() => {
|
|
3031
|
+
* analytics.track("dashboard.ready");
|
|
3032
|
+
* });
|
|
3033
|
+
*
|
|
3034
|
+
* return <Loading fallback={<Spinner />}><pre>{data()}</pre></Loading>;
|
|
3035
|
+
* }
|
|
3036
|
+
* ```
|
|
3037
|
+
*
|
|
3038
|
+
* @example
|
|
3039
|
+
* ```tsx
|
|
3040
|
+
* // Event-handler — runs after the action settles.
|
|
3041
|
+
* function SaveButton() {
|
|
3042
|
+
* const save = action(function* () {
|
|
3043
|
+
* yield api.save();
|
|
3044
|
+
* });
|
|
3045
|
+
*
|
|
3046
|
+
* const handleClick = () => {
|
|
3047
|
+
* save();
|
|
3048
|
+
* onSettled(() => toast("Saved!"));
|
|
3049
|
+
* };
|
|
3050
|
+
*
|
|
3051
|
+
* return <button onClick={handleClick}>Save</button>;
|
|
3052
|
+
* }
|
|
3053
|
+
* ```
|
|
3054
|
+
*
|
|
3055
|
+
* @param callback Function to run; may return a cleanup function that fires
|
|
3056
|
+
* on owner disposal
|
|
3057
|
+
*/
|
|
3058
|
+
function onSettled(callback) {
|
|
3059
|
+
const owner = getOwner();
|
|
3060
|
+
owner && !(owner._config & CONFIG_CHILDREN_FORBIDDEN)
|
|
3061
|
+
? trackedEffect(() => untrack(callback), { name: "onSettled" })
|
|
3062
|
+
: globalQueue.enqueue(EFFECT_USER, function fire() {
|
|
3063
|
+
// Settled means derived. A settle that reverts optimism (or replays
|
|
3064
|
+
// gated reads) only enqueues the affected subscribers; the pass after
|
|
3065
|
+
// the commit re-derives them. Fired in the commit pass, the callback
|
|
3066
|
+
// read the optimistic source already reverted beside a sync memo of it
|
|
3067
|
+
// still holding the optimistic value — reads do not pull (#3411). Fall
|
|
3068
|
+
// to the next pass while the heap has work; `run` swapped the queue,
|
|
3069
|
+
// so this lands there, and `enqueue` keeps the drain alive.
|
|
3070
|
+
if (dirtyQueue._max >= dirtyQueue._min) return globalQueue.enqueue(EFFECT_USER, fire);
|
|
3071
|
+
// Unowned, out-of-band fire (no owner, or a children-forbidden one this
|
|
3072
|
+
// one-shot must not bind to): a returned cleanup has no lifecycle to
|
|
3073
|
+
// attach to. Reject it in dev; in production the return is simply
|
|
3074
|
+
// dropped — never bound to an unrelated owner or run eagerly.
|
|
3075
|
+
const cleanup = callback();
|
|
3076
|
+
if (cleanup !== undefined) {
|
|
3077
|
+
const message =
|
|
3078
|
+
"[SETTLED_CLEANUP_UNOWNED] onSettled returned a cleanup in an unowned scope; a cleanup can only be honored under an owner. Call your setup helper from an owned scope (e.g. the component body) instead of from inside an event handler, tracked effect, or another onSettled.";
|
|
3079
|
+
emitDiagnostic({
|
|
3080
|
+
code: "SETTLED_CLEANUP_UNOWNED",
|
|
3081
|
+
kind: "lifecycle",
|
|
3082
|
+
severity: "error",
|
|
3083
|
+
message
|
|
3084
|
+
});
|
|
3085
|
+
throw new Error(message);
|
|
3086
|
+
}
|
|
3087
|
+
});
|
|
2886
3088
|
}
|
|
2887
3089
|
|
|
2888
3090
|
function mapArray(list, map, options) {
|
|
@@ -3058,6 +3260,79 @@ function commitSmallMove(data, newItems, newLen, srcPos, dstPos, runs) {
|
|
|
3058
3260
|
}
|
|
3059
3261
|
return true;
|
|
3060
3262
|
}
|
|
3263
|
+
/** Stage a PURE REMOVAL without the key map: when the new window is the old
|
|
3264
|
+
* window with items dropped, in order (one-item removals, every-nth, the
|
|
3265
|
+
* table filters), a two-pointer walk lands every survivor in the window
|
|
3266
|
+
* stage with one key call per item and returns the leavers; a shape that is
|
|
3267
|
+
* not an ordered subsequence returns null with the node stage cleared, and
|
|
3268
|
+
* the map path below starts from scratch (it rewrites every window slot of
|
|
3269
|
+
* `temp` / `rows` / `indexes`, so only `tempNodes`' `!== undefined` probe
|
|
3270
|
+
* needs the reset). The stage arrays are scratch — nothing live is touched. */
|
|
3271
|
+
function stageRemoval(data, newItems, start, end, newEnd, temp, tempNodes, rows, indexes) {
|
|
3272
|
+
const key = data._key;
|
|
3273
|
+
const oldItems = data._items;
|
|
3274
|
+
const nodes = data._nodes;
|
|
3275
|
+
const removed = [];
|
|
3276
|
+
let i = start;
|
|
3277
|
+
for (let j = start; j <= newEnd; i++, j++) {
|
|
3278
|
+
const nk = key ? key(newItems[j]) : newItems[j];
|
|
3279
|
+
while (i <= end && (key ? key(oldItems[i]) : oldItems[i]) !== nk) removed.push(nodes[i++]);
|
|
3280
|
+
if (i > end) {
|
|
3281
|
+
tempNodes.fill(undefined);
|
|
3282
|
+
return null;
|
|
3283
|
+
}
|
|
3284
|
+
temp[j] = data._mappings[i];
|
|
3285
|
+
tempNodes[j - start] = nodes[i];
|
|
3286
|
+
if (rows) rows[j - start] = data._rows[i];
|
|
3287
|
+
if (indexes) indexes[j - start] = data._indexes[i];
|
|
3288
|
+
}
|
|
3289
|
+
for (; i <= end; i++) removed.push(nodes[i]);
|
|
3290
|
+
return removed;
|
|
3291
|
+
}
|
|
3292
|
+
/** Land a staged window into the live bookkeeping arrays in place: shift the
|
|
3293
|
+
* suffix by `dif` (a loop walked from the side that never overwrites an
|
|
3294
|
+
* unread slot), take the window from the stage, write survivors' row and
|
|
3295
|
+
* index signals. Its own function so the general path's body stays under
|
|
3296
|
+
* the JIT's optimization budget — the inlined commit measured ~10% slower on
|
|
3297
|
+
* full-window reorders (the small-move note has the same finding). */
|
|
3298
|
+
function commitWindow(data, newItems, newLen, start, end, newEnd, tempNodes, rows, indexes) {
|
|
3299
|
+
const nodes = data._nodes;
|
|
3300
|
+
const liveRows = data._rows;
|
|
3301
|
+
const liveIndexes = data._indexes;
|
|
3302
|
+
const oldLen = data._len;
|
|
3303
|
+
const dif = newLen - oldLen;
|
|
3304
|
+
let i, j;
|
|
3305
|
+
if (dif > 0) {
|
|
3306
|
+
// Grow first: a store past the end from the top is a third slower than
|
|
3307
|
+
// filling a pre-sized tail (measured on 1k refs, 2026-10-15).
|
|
3308
|
+
nodes.length = newLen;
|
|
3309
|
+
if (liveRows) liveRows.length = newLen;
|
|
3310
|
+
if (liveIndexes) liveIndexes.length = newLen;
|
|
3311
|
+
for (i = oldLen - 1, j = newLen - 1; i > end; i--, j--) {
|
|
3312
|
+
nodes[j] = nodes[i];
|
|
3313
|
+
if (liveRows) liveRows[j] = liveRows[i];
|
|
3314
|
+
if (liveIndexes) liveIndexes[j] = liveIndexes[i];
|
|
3315
|
+
}
|
|
3316
|
+
} else if (dif < 0) {
|
|
3317
|
+
for (i = end + 1, j = newEnd + 1; i < oldLen; i++, j++) {
|
|
3318
|
+
nodes[j] = nodes[i];
|
|
3319
|
+
if (liveRows) liveRows[j] = liveRows[i];
|
|
3320
|
+
if (liveIndexes) liveIndexes[j] = liveIndexes[i];
|
|
3321
|
+
}
|
|
3322
|
+
nodes.length = newLen;
|
|
3323
|
+
if (liveRows) liveRows.length = newLen;
|
|
3324
|
+
if (liveIndexes) liveIndexes.length = newLen;
|
|
3325
|
+
}
|
|
3326
|
+
for (j = start; j <= newEnd; j++) {
|
|
3327
|
+
nodes[j] = tempNodes[j - start];
|
|
3328
|
+
if (liveRows) setSignal((liveRows[j] = rows[j - start]), newItems[j]);
|
|
3329
|
+
if (liveIndexes) setSignal((liveIndexes[j] = indexes[j - start]), j);
|
|
3330
|
+
}
|
|
3331
|
+
for (j = newEnd + 1; j < newLen; j++) {
|
|
3332
|
+
if (liveRows) setSignal(liveRows[j], newItems[j]);
|
|
3333
|
+
if (liveIndexes && dif !== 0) setSignal(liveIndexes[j], j);
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3061
3336
|
function updateKeyedMap() {
|
|
3062
3337
|
// Untracked reads inside the internal owner resolve via _parentComputed:
|
|
3063
3338
|
// routed through the node, a store-proxy lookup sees pending writes (not a
|
|
@@ -3072,6 +3347,10 @@ function updateKeyedMap() {
|
|
|
3072
3347
|
runWithOwner(this._owner, () => {
|
|
3073
3348
|
let i,
|
|
3074
3349
|
j,
|
|
3350
|
+
// The stage slot a mapper writes its row/index signal into: `j` on the
|
|
3351
|
+
// create-all path, `j - start` on the general path, whose stage arrays
|
|
3352
|
+
// are sized to the changed WINDOW, not the list.
|
|
3353
|
+
slot,
|
|
3075
3354
|
rows,
|
|
3076
3355
|
indexes,
|
|
3077
3356
|
// Mappers write freshly-created row/index signals into the STAGE
|
|
@@ -3079,19 +3358,19 @@ function updateKeyedMap() {
|
|
|
3079
3358
|
mapper = this._rows
|
|
3080
3359
|
? this._byIndex
|
|
3081
3360
|
? () => {
|
|
3082
|
-
rows[
|
|
3083
|
-
return this._map(accessor(rows[
|
|
3361
|
+
rows[slot] = signal(newItems[j], pureOptions);
|
|
3362
|
+
return this._map(accessor(rows[slot]), j);
|
|
3084
3363
|
}
|
|
3085
3364
|
: () => {
|
|
3086
|
-
rows[
|
|
3087
|
-
indexes && (indexes[
|
|
3088
|
-
return this._map(accessor(rows[
|
|
3365
|
+
rows[slot] = signal(newItems[j], pureOptions);
|
|
3366
|
+
indexes && (indexes[slot] = signal(j, pureOptions));
|
|
3367
|
+
return this._map(accessor(rows[slot]), indexes ? accessor(indexes[slot]) : undefined);
|
|
3089
3368
|
}
|
|
3090
3369
|
: this._indexes
|
|
3091
3370
|
? () => {
|
|
3092
3371
|
const item = newItems[j];
|
|
3093
|
-
indexes[
|
|
3094
|
-
return this._map(item, accessor(indexes[
|
|
3372
|
+
indexes[slot] = signal(j, pureOptions);
|
|
3373
|
+
return this._map(item, accessor(indexes[slot]));
|
|
3095
3374
|
}
|
|
3096
3375
|
: () => {
|
|
3097
3376
|
const item = newItems[j];
|
|
@@ -3108,7 +3387,7 @@ function updateKeyedMap() {
|
|
|
3108
3387
|
this._rows && (this._rows = []);
|
|
3109
3388
|
this._indexes && (this._indexes = []);
|
|
3110
3389
|
}
|
|
3111
|
-
if (this._fallback && !this._mappings
|
|
3390
|
+
if (this._fallback && !this._mappings.length) {
|
|
3112
3391
|
// an aborted fallback attempt leaves an owner without a mapping;
|
|
3113
3392
|
// dispose it before re-creating
|
|
3114
3393
|
this._nodes[0]?.dispose();
|
|
@@ -3122,7 +3401,10 @@ function updateKeyedMap() {
|
|
|
3122
3401
|
rows = this._rows && new Array(newLen);
|
|
3123
3402
|
indexes = this._indexes && new Array(newLen);
|
|
3124
3403
|
try {
|
|
3125
|
-
for (j = 0; j < newLen; j++)
|
|
3404
|
+
for (j = 0; j < newLen; j++) {
|
|
3405
|
+
slot = j;
|
|
3406
|
+
mappings[j] = runWithOwner((nodes[j] = createOwner()), mapper);
|
|
3407
|
+
}
|
|
3126
3408
|
} catch (err) {
|
|
3127
3409
|
for (i = 0; i <= j; i++) nodes[i]?.dispose();
|
|
3128
3410
|
throw err;
|
|
@@ -3224,81 +3506,89 @@ function updateKeyedMap() {
|
|
|
3224
3506
|
if (k <= hi && trySmallMove(this, newItems, newLen, start)) return;
|
|
3225
3507
|
}
|
|
3226
3508
|
const dif = newLen - this._len;
|
|
3509
|
+
// WINDOW-SIZED STAGING, IN-PLACE COMMIT (2026-10-15). The strong-abort
|
|
3510
|
+
// contract (#2903) only needs the pass to touch nothing live before
|
|
3511
|
+
// every mapper has succeeded; it never needed four fresh newLen-sized
|
|
3512
|
+
// arrays. Only the changed window [start, newEnd] is staged — the
|
|
3513
|
+
// prefix and suffix are, by construction, the rows that did not move
|
|
3514
|
+
// relative to each other — and at commit the bookkeeping arrays
|
|
3515
|
+
// (`_nodes`, `_rows`, `_indexes`) take the window in place with the
|
|
3516
|
+
// suffix shifted by a plain loop (`copyWithin` is a generic
|
|
3517
|
+
// per-element builtin in V8 — 30× slower than the loop on 1k refs,
|
|
3518
|
+
// measured 2026-10-15). `_mappings` alone is
|
|
3519
|
+
// rebuilt: its fresh identity is the change signal downstream relies
|
|
3520
|
+
// on. A one-row edit on a 500-row list used to allocate ~20 KB of
|
|
3521
|
+
// staging plus a position-sized chain array per commit (uibench's
|
|
3522
|
+
// one-item tree ops at 4–6× Octane, jfb-reorder `removefirst` at
|
|
3523
|
+
// 2.5×); now it stages one slot.
|
|
3524
|
+
const wLen = newEnd - start + 1;
|
|
3227
3525
|
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];
|
|
3526
|
+
const tempNodes = new Array(wLen);
|
|
3527
|
+
rows = this._rows ? new Array(wLen) : undefined;
|
|
3528
|
+
indexes = this._indexes ? new Array(wLen) : undefined;
|
|
3529
|
+
// A shrinking pass is tried as a pure removal first (stageRemoval: no
|
|
3530
|
+
// key map, one key call per item); anything else takes the map path.
|
|
3531
|
+
const pure =
|
|
3532
|
+
dif < 0
|
|
3533
|
+
? stageRemoval(this, newItems, start, end, newEnd, temp, tempNodes, rows, indexes)
|
|
3534
|
+
: null;
|
|
3535
|
+
if (pure !== null) {
|
|
3536
|
+
// (The list-churn census below wants removals AND creations in one
|
|
3537
|
+
// pass; a pure removal has none, so no item ledger is kept here.)
|
|
3538
|
+
if (pure.length !== 0) removed = pure;
|
|
3539
|
+
} else {
|
|
3540
|
+
// 0) prepare a map of all indices in the changed window of newItems,
|
|
3541
|
+
// scanning backwards so we encounter them in natural order
|
|
3542
|
+
newIndices = new Map();
|
|
3543
|
+
newIndicesNext = new Array(wLen);
|
|
3544
|
+
for (j = newEnd; j >= start; j--) {
|
|
3545
|
+
item = newItems[j];
|
|
3546
|
+
key = this._key ? this._key(item) : item;
|
|
3547
|
+
i = newIndices.get(key);
|
|
3548
|
+
newIndicesNext[j - start] = i === undefined ? -1 : i;
|
|
3255
3549
|
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
3550
|
}
|
|
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]);
|
|
3551
|
+
// 1) step through the old changed window and see if items can be found
|
|
3552
|
+
// in the new set; if so, stage them at their new positions; if not,
|
|
3553
|
+
// queue them for disposal at commit
|
|
3554
|
+
for (i = start; i <= end; i++) {
|
|
3555
|
+
item = this._items[i];
|
|
3556
|
+
key = this._key ? this._key(item) : item;
|
|
3557
|
+
j = newIndices.get(key);
|
|
3558
|
+
if (j !== undefined && j !== -1) {
|
|
3559
|
+
temp[j] = this._mappings[i];
|
|
3560
|
+
tempNodes[j - start] = this._nodes[i];
|
|
3561
|
+
rows && (rows[j - start] = this._rows[i]);
|
|
3562
|
+
indexes && (indexes[j - start] = this._indexes[i]);
|
|
3563
|
+
j = newIndicesNext[j - start];
|
|
3564
|
+
newIndices.set(key, j);
|
|
3565
|
+
} else {
|
|
3566
|
+
(removed ??= []).push(this._nodes[i]);
|
|
3567
|
+
if (true && attrHooks !== null) (removedItems ??= []).push(item);
|
|
3568
|
+
}
|
|
3292
3569
|
}
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3570
|
+
// 2) create new rows into the window stage; an abort disposes only these
|
|
3571
|
+
try {
|
|
3572
|
+
for (j = start; j <= newEnd; j++) {
|
|
3573
|
+
if (tempNodes[j - start] !== undefined) continue;
|
|
3574
|
+
(created ??= []).push((tempNodes[j - start] = createOwner()));
|
|
3575
|
+
if (true && attrHooks !== null) (createdItems ??= []).push(newItems[j]);
|
|
3576
|
+
slot = j - start;
|
|
3577
|
+
temp[j] = runWithOwner(tempNodes[j - start], mapper);
|
|
3578
|
+
}
|
|
3579
|
+
} catch (err) {
|
|
3580
|
+
if (created) for (i = 0; i < created.length; i++) created[i].dispose();
|
|
3581
|
+
throw err;
|
|
3296
3582
|
}
|
|
3297
3583
|
}
|
|
3584
|
+
// 3) commit: the retained prefix and suffix land in the fresh mapped
|
|
3585
|
+
// array; the bookkeeping arrays take the staged window IN PLACE (own
|
|
3586
|
+
// function: kept out of this body on purpose, see commitWindow); then
|
|
3587
|
+
// the exited rows dispose
|
|
3588
|
+
for (i = 0; i < start; i++) temp[i] = this._mappings[i];
|
|
3589
|
+
for (j = newEnd + 1; j < newLen; j++) temp[j] = this._mappings[j - dif];
|
|
3590
|
+
commitWindow(this, newItems, newLen, start, end, newEnd, tempNodes, rows, indexes);
|
|
3298
3591
|
this._mappings = temp;
|
|
3299
|
-
this._nodes = tempNodes;
|
|
3300
|
-
rows && (this._rows = rows);
|
|
3301
|
-
indexes && (this._indexes = indexes);
|
|
3302
3592
|
this._len = newLen;
|
|
3303
3593
|
// save a copy of the mapped items for the next update
|
|
3304
3594
|
this._items = newItems.slice(0);
|
|
@@ -3328,7 +3618,7 @@ function updateKeyedMap() {
|
|
|
3328
3618
|
* const view = repeat(count, i => `Item ${i}`, { fallback: () => "empty" });
|
|
3329
3619
|
* ```
|
|
3330
3620
|
*
|
|
3331
|
-
* @description https://
|
|
3621
|
+
* @description https://v2.solidjs.com/reference/solid-js/advanced/jsx-component-primitives/repeat
|
|
3332
3622
|
*/
|
|
3333
3623
|
function repeat(count, map, options) {
|
|
3334
3624
|
const wrappedMap = options?.name
|
|
@@ -3378,7 +3668,7 @@ function updateRepeat() {
|
|
|
3378
3668
|
// Reset offset to match the cleared data (#2767, repro 2).
|
|
3379
3669
|
this._offset = 0;
|
|
3380
3670
|
}
|
|
3381
|
-
if (this._fallback && !this._mappings
|
|
3671
|
+
if (this._fallback && !this._mappings.length) {
|
|
3382
3672
|
// an aborted fallback attempt leaves an owner without a mapping;
|
|
3383
3673
|
// dispose it before re-creating
|
|
3384
3674
|
this._nodes[0]?.dispose();
|
|
@@ -3658,11 +3948,16 @@ function noteNode(target, node, key) {
|
|
|
3658
3948
|
// dispatches CONFIG_SLOT_NODE nodes here (graph.ts) instead of holding a
|
|
3659
3949
|
// per-node closure in a per-node NodeExtension. A dropped node is
|
|
3660
3950
|
// unreachable through the store — a fresh read makes a fresh node.
|
|
3951
|
+
// A staged write — or a lane's value (a guess, S4) — is state only the node
|
|
3952
|
+
// holds: its release waits for the commit that resolves it. So does a leaf of
|
|
3953
|
+
// a held container (#4146): a mainline write it carried is in the held
|
|
3954
|
+
// staging too, where it would read as the hold's until the landing.
|
|
3955
|
+
const slotKept = node =>
|
|
3956
|
+
node._pendingValue !== NOT_PENDING ||
|
|
3957
|
+
node._config & CONFIG_OVERRIDE ||
|
|
3958
|
+
node._host.k?._config & CONFIG_HELD;
|
|
3661
3959
|
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);
|
|
3960
|
+
if (slotKept(node)) deferredReleases.add(node);
|
|
3666
3961
|
else releaseSlot(node);
|
|
3667
3962
|
});
|
|
3668
3963
|
/** A node born while its container has a staging is born from the two
|
|
@@ -3678,9 +3973,13 @@ function bornStaged(target, node, staged, key) {
|
|
|
3678
3973
|
if (k === null) return; // a node-less staging holds nothing (`stageOn`)
|
|
3679
3974
|
// Held by the container's transaction only for a key the HELD staging
|
|
3680
3975
|
// changed — a mainline layer's change above it stages mainline. (A
|
|
3681
|
-
// container carrying a guess is a lane's: the flush's park decides
|
|
3682
|
-
|
|
3683
|
-
|
|
3976
|
+
// container carrying a guess is a lane's: the flush's park decides —
|
|
3977
|
+
// unless a correction folded under the guess, D-34: the key is that
|
|
3978
|
+
// correction's hold's.)
|
|
3979
|
+
if (k._config & CONFIG_HELD && heldKeyChanged(target, key)) {
|
|
3980
|
+
const l = txOf(k);
|
|
3981
|
+
if (!(k._config & CONFIG_OVERRIDE) || l.folded) holdNode(node, resolveTx(l._parent ?? l));
|
|
3982
|
+
}
|
|
3684
3983
|
// A28 (4): a batch written inside a creation-time pass is promoted — its
|
|
3685
3984
|
// late-materialized leaf with it.
|
|
3686
3985
|
if (k._config & CONFIG_PROMOTED) notePromotedWrite(node);
|
|
@@ -3722,7 +4021,9 @@ function getNode(
|
|
|
3722
4021
|
// the hottest store path, and the disabled cost must stay one null
|
|
3723
4022
|
// check (nodes created before enable() stay generically named).
|
|
3724
4023
|
if (attrHooks !== null) {
|
|
3725
|
-
|
|
4024
|
+
const root = storeRoot(target);
|
|
4025
|
+
created._name = (storeNames.get(root) ?? "store") + "." + String(key);
|
|
4026
|
+
created._loc = storeLocs.get(root);
|
|
3726
4027
|
stampNodeOwner(created, target);
|
|
3727
4028
|
}
|
|
3728
4029
|
nodes[key] = node;
|
|
@@ -3775,7 +4076,7 @@ function getContainerNode(target) {
|
|
|
3775
4076
|
* staging: nothing allocates, nothing is swept at the commit (dbmon's tick
|
|
3776
4077
|
* re-created, queued, swept and dropped ~7000 container nodes). If the
|
|
3777
4078
|
* flush parks, the seam materializes and holds the node then
|
|
3778
|
-
* (`
|
|
4079
|
+
* (`storeParkHook`) — the one case the staging home exists for. */
|
|
3779
4080
|
function stageOn(target, pb) {
|
|
3780
4081
|
if (
|
|
3781
4082
|
target.k === null &&
|
|
@@ -3798,7 +4099,7 @@ function deriveHolds(target) {
|
|
|
3798
4099
|
const fw = target.fam?.node;
|
|
3799
4100
|
return fw != null && !userWrite && (fw._config & CONFIG_HELD) !== 0;
|
|
3800
4101
|
}
|
|
3801
|
-
|
|
4102
|
+
const storePark = t => {
|
|
3802
4103
|
for (let i = 0; i < foldList.length; i++) {
|
|
3803
4104
|
const target = foldList[i];
|
|
3804
4105
|
if (target.k === null && target.pb !== null) holdNode(getContainerNode(target), t);
|
|
@@ -3846,11 +4147,16 @@ function stampNodeOwner(created, target) {
|
|
|
3846
4147
|
created._owner = storeRootOwner(target) ?? null;
|
|
3847
4148
|
}
|
|
3848
4149
|
const storeNames = new WeakMap();
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
4150
|
+
// The store's `loc` option, carried onto every property node beside its
|
|
4151
|
+
// label (the same gate: nodes created before the engine is installed carry
|
|
4152
|
+
// neither).
|
|
4153
|
+
const storeLocs = new WeakMap();
|
|
4154
|
+
function nameStore(proxy, name, loc) {
|
|
4155
|
+
if (attrHooks !== null) {
|
|
4156
|
+
const target = proxy[$TARGET];
|
|
4157
|
+
if (name) storeNames.set(target, name);
|
|
4158
|
+
if (loc) storeLocs.set(target, loc);
|
|
4159
|
+
}
|
|
3854
4160
|
}
|
|
3855
4161
|
// ---------------------------------------------------------------------------
|
|
3856
4162
|
// clones (CoW privatization)
|
|
@@ -3934,7 +4240,8 @@ function overlayRebuilds(t, pb) {
|
|
|
3934
4240
|
if (t.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
|
|
3935
4241
|
if (t.del !== null && t.del.size !== 0) return true;
|
|
3936
4242
|
const wk = t.wk;
|
|
3937
|
-
if (wk !== null && wk !== WK_ALL && wk.size <= OVERLAY_REBUILD_MIN_ADDS)
|
|
4243
|
+
if (wk !== null && wk !== WK_ALL && wk.u === undefined && wk.size <= OVERLAY_REBUILD_MIN_ADDS)
|
|
4244
|
+
return false;
|
|
3938
4245
|
const v = t.v;
|
|
3939
4246
|
let adds = 0;
|
|
3940
4247
|
for (const key of Reflect.ownKeys(pb))
|
|
@@ -4164,11 +4471,7 @@ function drainFolds() {
|
|
|
4164
4471
|
}
|
|
4165
4472
|
if (deferredReleases.size !== 0) {
|
|
4166
4473
|
for (const node of deferredReleases) {
|
|
4167
|
-
if (
|
|
4168
|
-
node._pendingValue === NOT_PENDING &&
|
|
4169
|
-
!(node._config & CONFIG_OVERRIDE) &&
|
|
4170
|
-
node._subs === null
|
|
4171
|
-
) {
|
|
4474
|
+
if (node._subs === null && !slotKept(node)) {
|
|
4172
4475
|
deferredReleases.delete(node);
|
|
4173
4476
|
releaseSlot(node);
|
|
4174
4477
|
}
|
|
@@ -4197,10 +4500,10 @@ GlobalQueue._slotCovered = n => {
|
|
|
4197
4500
|
}
|
|
4198
4501
|
return n._value;
|
|
4199
4502
|
};
|
|
4200
|
-
|
|
4503
|
+
installStoreHooks(storePark, () => {
|
|
4201
4504
|
drainFolds();
|
|
4202
4505
|
optHooks?.sweep();
|
|
4203
|
-
};
|
|
4506
|
+
});
|
|
4204
4507
|
/** The store half of `affects()` (store/affects.ts) makes its marks' nodes
|
|
4205
4508
|
* and walks records through these: it never imports the engine. */
|
|
4206
4509
|
GlobalQueue._storeNode = getNode;
|
|
@@ -4248,10 +4551,11 @@ function foldTarget(t, old) {
|
|
|
4248
4551
|
const v = t.v;
|
|
4249
4552
|
const wk = t.wk;
|
|
4250
4553
|
if (wk !== null && wk !== WK_ALL) {
|
|
4251
|
-
for (
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4554
|
+
for (let w = wk; w !== undefined; w = w.u)
|
|
4555
|
+
for (const key of w) {
|
|
4556
|
+
if (hasOwn$1.call(pb, key)) copyOwn(v, pb, key);
|
|
4557
|
+
else delete v[key];
|
|
4558
|
+
}
|
|
4255
4559
|
} else {
|
|
4256
4560
|
for (const key of Reflect.ownKeys(pb)) {
|
|
4257
4561
|
const d = Object.getOwnPropertyDescriptor(pb, key);
|
|
@@ -4424,7 +4728,10 @@ function notifyWrites(t) {
|
|
|
4424
4728
|
// value can change when ANY key is written), or a non-plain prototype
|
|
4425
4729
|
// (class instances: prototype getters derive from arbitrary fields).
|
|
4426
4730
|
// Overlay pbs chain to the COMMITTED object (#3044) — plainness is the
|
|
4427
|
-
// committed container's prototype, not the overlay's.
|
|
4731
|
+
// committed container's prototype, not the overlay's. THIS setter's keys
|
|
4732
|
+
// only (D-8, #3743): a key an earlier setter of the batch wrote and this
|
|
4733
|
+
// one did not is no second proposal — its node was told then, and telling
|
|
4734
|
+
// a held node its unchanged value again would join this tick to the hold.
|
|
4428
4735
|
const wk0 = t.wk;
|
|
4429
4736
|
const writtenKeys = wk0 === WK_ALL || t.a === true || !plainProto(t.ovl ? t.v : pb) ? null : wk0;
|
|
4430
4737
|
if (attrHooks !== null) reportReplacedContainers(t, old, pb, writtenKeys);
|
|
@@ -5084,6 +5391,63 @@ function optRead(target) {
|
|
|
5084
5391
|
const UNSAFE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
|
5085
5392
|
// ---------------------------------------------------------------------------
|
|
5086
5393
|
// traps
|
|
5394
|
+
/** A trap write: the target is told at the setter's exit, the key recorded
|
|
5395
|
+
* in this setter's `wk`. A setter's first write to a target still staged by
|
|
5396
|
+
* an earlier setter of the batch opens a set of its own (the notify visits
|
|
5397
|
+
* it, D-8) and chains the earlier one behind for the fold's union. */
|
|
5398
|
+
function touch(target, key) {
|
|
5399
|
+
let wk = target.wk;
|
|
5400
|
+
if (!pendingNotify.has(target)) {
|
|
5401
|
+
pendingNotify.add(target);
|
|
5402
|
+
if (wk !== null && wk !== WK_ALL) {
|
|
5403
|
+
wk = new Set();
|
|
5404
|
+
wk.u = target.wk;
|
|
5405
|
+
target.wk = wk;
|
|
5406
|
+
}
|
|
5407
|
+
}
|
|
5408
|
+
if (wk !== WK_ALL) (target.wk ??= new Set()).add(key);
|
|
5409
|
+
}
|
|
5410
|
+
/** Dev: targets (and their keys) already warned about for a write outside
|
|
5411
|
+
* the setter — #4179's once-per-target+key memory. */
|
|
5412
|
+
let warnedOutsideWrites;
|
|
5413
|
+
/** Dev: set while the runtime itself writes through a proxy it knows the
|
|
5414
|
+
* trap ignores (`registerGraph`'s `_owner` stamp). */
|
|
5415
|
+
let internalOutsideWrite = false;
|
|
5416
|
+
/** Dev: a write, defineProperty or delete on a store proxy outside its
|
|
5417
|
+
* setter's draft is ignored (the trap returns `true` so strict-mode code
|
|
5418
|
+
* does not throw) — say so once per target+key instead of silently dropping
|
|
5419
|
+
* it (#4179). */
|
|
5420
|
+
function warnWriteOutsideSetter(target, key, operation) {
|
|
5421
|
+
if (internalOutsideWrite) return;
|
|
5422
|
+
warnedOutsideWrites ??= new WeakMap();
|
|
5423
|
+
let keys = warnedOutsideWrites.get(target);
|
|
5424
|
+
if (keys === undefined) warnedOutsideWrites.set(target, (keys = new Set()));
|
|
5425
|
+
if (keys.has(key)) return;
|
|
5426
|
+
keys.add(key);
|
|
5427
|
+
const name = String(key);
|
|
5428
|
+
const access =
|
|
5429
|
+
typeof key === "string" && /^[A-Za-z_$][\w$]*$/.test(key)
|
|
5430
|
+
? `.${key}`
|
|
5431
|
+
: typeof key === "string" && /^\d+$/.test(key)
|
|
5432
|
+
? `[${key}]`
|
|
5433
|
+
: `[${typeof key === "string" ? JSON.stringify(key) : name}]`;
|
|
5434
|
+
const hint =
|
|
5435
|
+
operation === "deleteProperty"
|
|
5436
|
+
? `setStore(s => { delete s${access} })`
|
|
5437
|
+
: `setStore(s => { s${access} = value })`;
|
|
5438
|
+
reportDiagnostic(
|
|
5439
|
+
emitDiagnostic({
|
|
5440
|
+
code: "STORE_WRITE_OUTSIDE_SETTER",
|
|
5441
|
+
kind: "write",
|
|
5442
|
+
severity: "warn",
|
|
5443
|
+
message:
|
|
5444
|
+
`[STORE_WRITE_OUTSIDE_SETTER] ${operation === "deleteProperty" ? "Deleting" : "Writing"} store property "${name}" outside its setter was ignored. ` +
|
|
5445
|
+
`Stores are read-only outside the setter's draft: use \`${hint}\`.`,
|
|
5446
|
+
nodeName: name,
|
|
5447
|
+
data: { operation, property: name }
|
|
5448
|
+
})
|
|
5449
|
+
);
|
|
5450
|
+
}
|
|
5087
5451
|
const traps = {
|
|
5088
5452
|
get(target, key, receiver) {
|
|
5089
5453
|
// One typeof gates every brand-symbol compare off the hot string path.
|
|
@@ -5327,20 +5691,30 @@ const traps = {
|
|
|
5327
5691
|
set(target, key, value) {
|
|
5328
5692
|
const draft = inDraft(target);
|
|
5329
5693
|
const override = !draft && writeOverride;
|
|
5330
|
-
if (!draft && !override)
|
|
5694
|
+
if (!draft && !override) {
|
|
5695
|
+
warnWriteOutsideSetter(target, key, "set");
|
|
5696
|
+
return true;
|
|
5697
|
+
}
|
|
5331
5698
|
if (key === "__proto__") return true; // pollution guard (core R30)
|
|
5332
5699
|
const uv = target.s ? value : unwrapValue(value);
|
|
5333
5700
|
const pb = ensurePB(target);
|
|
5334
|
-
|
|
5701
|
+
// #4146 (R1: node existence is unobservable; #3706: the hold's unit is
|
|
5702
|
+
// the key): a user setter writing a key a HELD container's staging left
|
|
5703
|
+
// unchanged would write it into the held staging — read as the hold's
|
|
5704
|
+
// until its landing, while the same key with a leaf (a live reader)
|
|
5705
|
+
// publishes at the flush. The key gets its leaf first, born from the two
|
|
5706
|
+
// frames (`bornStaged`: a key the hold left unchanged — or added — is
|
|
5707
|
+
// unstaged and held by no one; a key the hold changed is born held, A34
|
|
5708
|
+
// (1)), and the write stages on it (A28; a write by the holder joins it,
|
|
5709
|
+
// A34) — as it would with a reader's leaf. Kept while the container is
|
|
5710
|
+
// held (`deferredReleases`).
|
|
5711
|
+
if (userWrite && target.k?._config & CONFIG_HELD) deferredReleases.add(getNode(target, key));
|
|
5335
5712
|
if (Array.isArray(pb)) {
|
|
5713
|
+
touch(target, key);
|
|
5336
5714
|
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
|
-
}
|
|
5715
|
+
else touch(target, "length");
|
|
5342
5716
|
} else {
|
|
5343
|
-
|
|
5717
|
+
touch(target, key);
|
|
5344
5718
|
if (!(key in pb)) target.kc++;
|
|
5345
5719
|
}
|
|
5346
5720
|
if (UNSAFE_KEYS.has(key)) {
|
|
@@ -5369,14 +5743,16 @@ const traps = {
|
|
|
5369
5743
|
defineProperty(target, key, desc) {
|
|
5370
5744
|
const draft = inDraft(target);
|
|
5371
5745
|
const override = !draft && writeOverride;
|
|
5372
|
-
if (!draft && !override)
|
|
5746
|
+
if (!draft && !override) {
|
|
5747
|
+
warnWriteOutsideSetter(target, key, "defineProperty");
|
|
5748
|
+
return true;
|
|
5749
|
+
}
|
|
5373
5750
|
if (key === "__proto__") return true;
|
|
5374
5751
|
if (desc.get || desc.set) target.a = true;
|
|
5375
5752
|
if ("value" in desc) desc = { ...desc, value: unwrapValue(desc.value) };
|
|
5376
5753
|
const pb = ensurePB(target);
|
|
5377
5754
|
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);
|
|
5755
|
+
touch(target, key);
|
|
5380
5756
|
Object.defineProperty(pb, key, desc);
|
|
5381
5757
|
if (target.del !== null) target.del.delete(key);
|
|
5382
5758
|
if (override) notifyWrites(target);
|
|
@@ -5385,12 +5761,27 @@ const traps = {
|
|
|
5385
5761
|
deleteProperty(target, key) {
|
|
5386
5762
|
const draft = inDraft(target);
|
|
5387
5763
|
const override = !draft && writeOverride;
|
|
5388
|
-
if (!draft && !override)
|
|
5764
|
+
if (!draft && !override) {
|
|
5765
|
+
warnWriteOutsideSetter(target, key, "deleteProperty");
|
|
5766
|
+
return true;
|
|
5767
|
+
}
|
|
5389
5768
|
const pb = ensurePB(target);
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
delete
|
|
5393
|
-
|
|
5769
|
+
touch(target, key);
|
|
5770
|
+
// The key count (`kc`, the overlay gate and the rebuild cap) follows a
|
|
5771
|
+
// delete the way it follows an add: an own key of the draft leaving — a
|
|
5772
|
+
// clone's key, or an overlay's own add — is one fewer now. An overlay's
|
|
5773
|
+
// override of a committed key is the exception: the committed key's
|
|
5774
|
+
// deletion is recorded in `del` and counted at the rebuild, as before.
|
|
5775
|
+
// Without the decrement the natural "delete the old key, set the new
|
|
5776
|
+
// one" shape drifted the count up by one per write until a three-key
|
|
5777
|
+
// object crossed the overlay threshold and paid a rebuild on every
|
|
5778
|
+
// commit after.
|
|
5779
|
+
const committed = target.ovl && hasOwn$1.call(target.v, key);
|
|
5780
|
+
if (hasOwn$1.call(pb, key)) {
|
|
5781
|
+
delete pb[key];
|
|
5782
|
+
committed || target.kc--;
|
|
5783
|
+
}
|
|
5784
|
+
if (committed) (target.del ??= new Set()).add(key);
|
|
5394
5785
|
if (override) notifyWrites(target);
|
|
5395
5786
|
return true;
|
|
5396
5787
|
}
|
|
@@ -5418,7 +5809,7 @@ const ASYNC_STORE_SETTER_MESSAGE =
|
|
|
5418
5809
|
* write violation — the user guard (`mapArray`'s pattern: the node-level
|
|
5419
5810
|
* writes carry `ownedWrite`; the setter asks). */
|
|
5420
5811
|
function devGuardStoreSetterWrite() {
|
|
5421
|
-
if (context && !(context._config & CONFIG_CHILDREN_FORBIDDEN)) {
|
|
5812
|
+
if (context && !(context._config & CONFIG_CHILDREN_FORBIDDEN) && !disposalDepth) {
|
|
5422
5813
|
emitDiagnostic({
|
|
5423
5814
|
code: "REACTIVE_WRITE_IN_OWNED_SCOPE",
|
|
5424
5815
|
kind: "write",
|
|
@@ -5454,6 +5845,7 @@ function devGuardStoreSetterResult(result) {
|
|
|
5454
5845
|
}
|
|
5455
5846
|
}
|
|
5456
5847
|
function storeSetter(proxy, fn, guard = true) {
|
|
5848
|
+
if (writeHook !== null) writeHook(proxy);
|
|
5457
5849
|
if (guard) devGuardStoreSetterWrite();
|
|
5458
5850
|
const target = proxy[$TARGET];
|
|
5459
5851
|
const prevScopes = writeScopes;
|
|
@@ -5521,6 +5913,8 @@ function createStore$1(initialValue, options) {
|
|
|
5521
5913
|
throw new Error("createStore({ shallow }): value is already a store proxy");
|
|
5522
5914
|
}
|
|
5523
5915
|
const proxy = wrap(initialValue);
|
|
5916
|
+
// Dependency edges (store/edges.ts): the root is named like a plain signal.
|
|
5917
|
+
if (signalHook !== null) signalHook(proxy[$TARGET]);
|
|
5524
5918
|
if (shallow) {
|
|
5525
5919
|
proxy[$TARGET].s = true;
|
|
5526
5920
|
markRawIngest(initialValue);
|
|
@@ -5528,14 +5922,17 @@ function createStore$1(initialValue, options) {
|
|
|
5528
5922
|
{
|
|
5529
5923
|
const owner = getOwner();
|
|
5530
5924
|
// Dev-tier graph registration (owner signal lists, onGraph); the
|
|
5531
|
-
// `_owner` write itself never reaches the proxy, see storeOwners
|
|
5925
|
+
// `_owner` write itself never reaches the proxy, see storeOwners — an
|
|
5926
|
+
// internal ignored write, not the user's (#4179's warning skips it).
|
|
5927
|
+
internalOutsideWrite = true;
|
|
5532
5928
|
registerGraph(proxy, owner);
|
|
5929
|
+
internalOutsideWrite = false;
|
|
5533
5930
|
// Only once the engine is installed, like the node stamping it feeds: a
|
|
5534
5931
|
// WeakMap.set per fresh store is a growing ephemeron table and,
|
|
5535
5932
|
// disabled, buys nothing.
|
|
5536
5933
|
if (attrHooks !== null) {
|
|
5537
5934
|
storeOwners.set(proxy[$TARGET], owner);
|
|
5538
|
-
nameStore(proxy, options?.name);
|
|
5935
|
+
nameStore(proxy, options?.name, options?.loc);
|
|
5539
5936
|
}
|
|
5540
5937
|
}
|
|
5541
5938
|
const setter = fn => storeSetter(proxy, fn);
|
|
@@ -6299,6 +6696,11 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
|
|
|
6299
6696
|
// A landing (not the sync pass's own commit): the readers held
|
|
6300
6697
|
// pending on the derive settle.
|
|
6301
6698
|
if (!first || !(owner._statusFlags & STATUS_UNINITIALIZED)) settleFamily(fam);
|
|
6699
|
+
// A commit is the derive's change for the readers that observed its
|
|
6700
|
+
// flight (linked by `pullFamily`; a settled pull does not link), as
|
|
6701
|
+
// a memo's landing is for its readers: one that caught the NotReady
|
|
6702
|
+
// (a fallback value, not pending) re-runs too (#4070).
|
|
6703
|
+
insertSubs(owner);
|
|
6302
6704
|
};
|
|
6303
6705
|
const sync = handleAsync(owner, result, commit, (error, pending) => {
|
|
6304
6706
|
// The flight rejected: the leaves' readers learn it from here —
|
|
@@ -6340,6 +6742,9 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
|
|
|
6340
6742
|
return owner;
|
|
6341
6743
|
}
|
|
6342
6744
|
function createProjectionInternal(fn, seed, options) {
|
|
6745
|
+
// Seedless (`fn` takes no draft): the root starts as an empty record. It is
|
|
6746
|
+
// a draft like any seed, never observable before the first landing (A25).
|
|
6747
|
+
seed ||= {};
|
|
6343
6748
|
const keyOption = options?.key === undefined ? "id" : options.key;
|
|
6344
6749
|
const fam = {
|
|
6345
6750
|
map: new WeakMap(),
|
|
@@ -6362,9 +6767,13 @@ function createProjectionInternal(fn, seed, options) {
|
|
|
6362
6767
|
}
|
|
6363
6768
|
let nodeOptions;
|
|
6364
6769
|
if (options?.seedLoadingValue) nodeOptions = { loadingValue: undefined };
|
|
6365
|
-
if (
|
|
6366
|
-
|
|
6367
|
-
|
|
6770
|
+
if (attrHooks !== null)
|
|
6771
|
+
// As createStore: the owner in scope stamps the projection's property
|
|
6772
|
+
// nodes, so attribution places them under it (#4035).
|
|
6773
|
+
storeOwners.set(store[$TARGET], getOwner());
|
|
6774
|
+
if (options?.name || options?.loc) {
|
|
6775
|
+
nodeOptions = { ...nodeOptions, name: options.name, loc: options.loc };
|
|
6776
|
+
nameStore(store, options.name, options.loc);
|
|
6368
6777
|
}
|
|
6369
6778
|
const node = computed(() => {
|
|
6370
6779
|
if (!fam.node) fam.node = getOwner();
|
|
@@ -6372,14 +6781,11 @@ function createProjectionInternal(fn, seed, options) {
|
|
|
6372
6781
|
}, nodeOptions);
|
|
6373
6782
|
node._config &= ~CONFIG_AUTO_DISPOSE;
|
|
6374
6783
|
fam.node = node;
|
|
6784
|
+
// The derive's leaves are not its dependents: a mark's repoll
|
|
6785
|
+
// (affects-marks.ts) reaches their readers through this.
|
|
6786
|
+
node.fam = fam;
|
|
6375
6787
|
return { store, node };
|
|
6376
6788
|
}
|
|
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
6789
|
function createProjection(fn, seed, options) {
|
|
6384
6790
|
return createProjectionInternal(fn, seed, options).store;
|
|
6385
6791
|
}
|
|
@@ -6492,10 +6898,12 @@ function notifyOptimisticWrites(t, pb) {
|
|
|
6492
6898
|
if (structural) {
|
|
6493
6899
|
const rows = isArr ? pb.map(unwrapValue) : shallowKeys(pb);
|
|
6494
6900
|
// The rows the setter removed from the view it saw, by key: a later
|
|
6495
|
-
// re-base over another action's truth must not bring them back.
|
|
6901
|
+
// re-base over another action's truth must not bring them back. A set
|
|
6902
|
+
// (empty or not) marks a keyed arrangement guess (`fold`).
|
|
6496
6903
|
let removed = null;
|
|
6497
6904
|
const keyFn = t.fam.key;
|
|
6498
6905
|
if (isArr && keyFn !== null && keyFn !== undefined) {
|
|
6906
|
+
removed = new Set();
|
|
6499
6907
|
const kept = new Set();
|
|
6500
6908
|
for (let i = 0; i < rows.length; i++) {
|
|
6501
6909
|
const k = isWrappable(rows[i]) ? keyFn(rows[i]) : undefined;
|
|
@@ -6504,7 +6912,7 @@ function notifyOptimisticWrites(t, pb) {
|
|
|
6504
6912
|
for (let i = 0; i < old.length; i++) {
|
|
6505
6913
|
const r = unwrapValue(old[i]);
|
|
6506
6914
|
const k = isWrappable(r) ? keyFn(r) : undefined;
|
|
6507
|
-
if (k !== undefined && !kept.has(k))
|
|
6915
|
+
if (k !== undefined && !kept.has(k)) removed.add(k);
|
|
6508
6916
|
}
|
|
6509
6917
|
}
|
|
6510
6918
|
optimisticWrite(getContainerNode(t), new LaneView(rows, base, removed));
|
|
@@ -6640,7 +7048,8 @@ function reconcileDraft(draft, incoming, keyFn) {
|
|
|
6640
7048
|
* committed length differs, and as the `LaneView`'s base for structural
|
|
6641
7049
|
* reads. Unkeyed families compose
|
|
6642
7050
|
* positionally — the guess stands as written (the documented residue). */
|
|
6643
|
-
GlobalQueue._laneRebase = (el, truth) => {
|
|
7051
|
+
GlobalQueue._laneRebase = (el, truth, older) => {
|
|
7052
|
+
if (!older) return fold(el, truth);
|
|
6644
7053
|
const t = el._host;
|
|
6645
7054
|
if (t === undefined || el._key !== $TRACK) return;
|
|
6646
7055
|
const view = el._x._lane;
|
|
@@ -6701,6 +7110,51 @@ GlobalQueue._laneRebase = (el, truth) => {
|
|
|
6701
7110
|
laneSet(len, rows.length, lane);
|
|
6702
7111
|
}
|
|
6703
7112
|
};
|
|
7113
|
+
/** D-34 for a keyed arrangement guess: rows by key, not positions. A
|
|
7114
|
+
* current truth that differs folds into the hold beneath it instead of
|
|
7115
|
+
* voiding the lane: the guess stays the lane's value and the screen's until
|
|
7116
|
+
* the hold commits, where rows match by key, and the graph outside the lane
|
|
7117
|
+
* re-derives from the truth now (`foldedRead`). A confirmation is silent
|
|
7118
|
+
* (A17): only truth readers are told. True: the lane stages the truth
|
|
7119
|
+
* beneath (`laneWrite`). */
|
|
7120
|
+
function fold(el, truth) {
|
|
7121
|
+
const k = el._host?.k;
|
|
7122
|
+
const l = txOf(el);
|
|
7123
|
+
// A guess the screen never showed is void at the correction (D-34); a
|
|
7124
|
+
// keyed arrangement guess's view carries its removed-keys set.
|
|
7125
|
+
if (!l._shown || !(k?._config & CONFIG_OVERRIDE) || !k._x._lane?.removed) return false;
|
|
7126
|
+
const guess = el._x._lane;
|
|
7127
|
+
if ((!el._equals || !el._equals(guess, truth)) && !GlobalQueue._echoes(guess, truth)) {
|
|
7128
|
+
l.folded = foldedRead;
|
|
7129
|
+
insertSubs(el);
|
|
7130
|
+
} else GlobalQueue._confirmed?.(l);
|
|
7131
|
+
joinFuture(resolveTx(l._parent ?? l));
|
|
7132
|
+
return true;
|
|
7133
|
+
}
|
|
7134
|
+
/** A folded lane's guess read by a deriving pass outside the lane, tracked
|
|
7135
|
+
* or not (`untrack` is about dependencies, not about which world a pass
|
|
7136
|
+
* derives from), or by `latest()` (`c` null): the hold's world — the truth
|
|
7137
|
+
* staged beneath the guess, else the value it covered — and the pass joins
|
|
7138
|
+
* the hold. Lane passes and the screen keep the guess: held truth is masked
|
|
7139
|
+
* from lane passes only (#3164). The store's own derive reconciles its
|
|
7140
|
+
* landings against the arrangement the guesses made: it is the author, not
|
|
7141
|
+
* a reader. */
|
|
7142
|
+
function foldedRead(c, el) {
|
|
7143
|
+
if (c !== null) {
|
|
7144
|
+
if (
|
|
7145
|
+
!(el._config & CONFIG_GUESS) ||
|
|
7146
|
+
passLane !== null ||
|
|
7147
|
+
c._type === EFFECT_RENDER ||
|
|
7148
|
+
c._config & CONFIG_CHILDREN_FORBIDDEN ||
|
|
7149
|
+
el._host?.fam?.node === c
|
|
7150
|
+
)
|
|
7151
|
+
return NOT_PENDING;
|
|
7152
|
+
const l = txOf(el);
|
|
7153
|
+
joinPassAt(c, resolveTx(l._parent ?? l));
|
|
7154
|
+
}
|
|
7155
|
+
if (el._pendingValue !== NOT_PENDING) return el._pendingValue;
|
|
7156
|
+
return el._config & CONFIG_SLOT_NODE ? GlobalQueue._slotCovered(el) : el._value;
|
|
7157
|
+
}
|
|
6704
7158
|
function sameRow(a, b, keyFn) {
|
|
6705
7159
|
if (a === b) return true;
|
|
6706
7160
|
if (!isWrappable(a) || !isWrappable(b)) return false;
|
|
@@ -6906,7 +7360,9 @@ function optimisticView(
|
|
|
6906
7360
|
function createOptimisticStore(first, second, third) {
|
|
6907
7361
|
const derived = typeof first === "function";
|
|
6908
7362
|
const options = derived ? third : second;
|
|
6909
|
-
|
|
7363
|
+
// Seedless derived form: the root starts as an empty record (see
|
|
7364
|
+
// createProjectionInternal).
|
|
7365
|
+
const initialValue = derived ? second || {} : first;
|
|
6910
7366
|
const keyOption = options?.key === undefined ? "id" : options.key;
|
|
6911
7367
|
const fam = {
|
|
6912
7368
|
map: new WeakMap(),
|
|
@@ -6922,7 +7378,7 @@ function createOptimisticStore(first, second, third) {
|
|
|
6922
7378
|
: row => (isWrappable(row) ? row[keyOption] : undefined)
|
|
6923
7379
|
};
|
|
6924
7380
|
const store = wrap(initialValue, null, null, fam);
|
|
6925
|
-
nameStore(store, options?.name);
|
|
7381
|
+
nameStore(store, options?.name, options?.loc);
|
|
6926
7382
|
if (fam.shallow) {
|
|
6927
7383
|
store[$TARGET].s = true;
|
|
6928
7384
|
markRawIngest(initialValue);
|
|
@@ -6931,7 +7387,8 @@ function createOptimisticStore(first, second, third) {
|
|
|
6931
7387
|
const fn = first;
|
|
6932
7388
|
let nodeOptions;
|
|
6933
7389
|
if (options?.seedLoadingValue) nodeOptions = { loadingValue: undefined };
|
|
6934
|
-
if (options?.name
|
|
7390
|
+
if (options?.name || options?.loc)
|
|
7391
|
+
nodeOptions = { ...nodeOptions, name: options.name, loc: options.loc };
|
|
6935
7392
|
const node = computed(() => {
|
|
6936
7393
|
if (!fam.node) fam.node = getOwner();
|
|
6937
7394
|
runProjectionComputed(store, fn, keyOption);
|
|
@@ -8000,6 +8457,83 @@ function deep(value) {
|
|
|
8000
8457
|
return deep$1(value);
|
|
8001
8458
|
}
|
|
8002
8459
|
|
|
8460
|
+
// The mark core: a declared not-final state on a node that reads pending
|
|
8461
|
+
// (`isPending` → true) on the node and everything derived from it, without
|
|
8462
|
+
// holding anything or entangling a reader. Two declarers share it:
|
|
8463
|
+
// `affects()` (affects.ts — a mark scoped to a transaction, with a store
|
|
8464
|
+
// half) and `createDeferred` (deferred.ts — a mark whose lifetime is the
|
|
8465
|
+
// node's own flight). Split out so the second does not ship the first.
|
|
8466
|
+
//
|
|
8467
|
+
// Representation: a count on the marked node (`_x._marks`). Coverage of the
|
|
8468
|
+
// derivations is pull-derived at probe time — `isPending`'s read walks the
|
|
8469
|
+
// probed node's dependencies for a marked one (verdict.ts → `_marked`) — so
|
|
8470
|
+
// nothing is stored downstream and a mid-window recompute strands nothing.
|
|
8471
|
+
// The push half is the verdict readers' re-derivation when a node's marks
|
|
8472
|
+
// start and end (`repoll`): the only notification a mark makes.
|
|
8473
|
+
/** Live marks, every declarer — the gate on the probe's walk. */
|
|
8474
|
+
let active = 0;
|
|
8475
|
+
/** The verdict readers downstream of `node` — through derivations, stopping
|
|
8476
|
+
* at effects — re-derive: the mark's one notification (a probe's answer
|
|
8477
|
+
* changed with no value notifying anyone). Truth readers (`until`, the
|
|
8478
|
+
* `refresh` waiter) too, which a lagging flight parked (deferred.ts) — but
|
|
8479
|
+
* not through a derived store, whose leaves change only once its derive
|
|
8480
|
+
* re-runs: they re-derive at that write. */
|
|
8481
|
+
function repoll(node, seen = new Set(), readers = CONFIG_VERDICT | CONFIG_AUTHORITATIVE) {
|
|
8482
|
+
for (let s = node._subs; s !== null; s = s._nextSub) {
|
|
8483
|
+
const sub = s._sub;
|
|
8484
|
+
if (seen.has(sub)) continue;
|
|
8485
|
+
seen.add(sub);
|
|
8486
|
+
if (sub._config & readers) enqueueSub(sub);
|
|
8487
|
+
if (!sub._type) repoll(sub, seen, readers);
|
|
8488
|
+
// A derived store's derive: its leaves' readers derive from it too
|
|
8489
|
+
// (store/projection.ts; a live target's property, has and keys nodes).
|
|
8490
|
+
const fam = sub.fam;
|
|
8491
|
+
if (fam)
|
|
8492
|
+
for (const t of fam.live)
|
|
8493
|
+
for (const o of [t.n, t.h, { k: t.k, d: t.dk }])
|
|
8494
|
+
if (o) for (const k of Reflect.ownKeys(o)) o[k] && repoll(o[k], seen, CONFIG_VERDICT);
|
|
8495
|
+
}
|
|
8496
|
+
}
|
|
8497
|
+
/** A node is covered by a live mark iff it carries one, or derives — through
|
|
8498
|
+
* its current dependencies — from one that does. A store slot derives from
|
|
8499
|
+
* its family's derive (a derived store's leaves are not the derive's
|
|
8500
|
+
* dependents, store/projection.ts), so coverage hops there. A real error
|
|
8501
|
+
* outranks an inherited mark (A24 (c)): coverage does not flow through an
|
|
8502
|
+
* errored node (a direct mark on one still reads pending). Mid-recompute,
|
|
8503
|
+
* only the validated prefix of the dependency list is this pass's. */
|
|
8504
|
+
function marked(el, seen) {
|
|
8505
|
+
if (el._x !== null && el._x._marks !== 0) return true;
|
|
8506
|
+
const c = el;
|
|
8507
|
+
if (c._statusFlags & STATUS_ERROR || seen.has(el)) return false;
|
|
8508
|
+
seen.add(el);
|
|
8509
|
+
if (c._config & CONFIG_SLOT_NODE) {
|
|
8510
|
+
const derive = c._host?.fam?.node;
|
|
8511
|
+
return derive != null && marked(derive, seen);
|
|
8512
|
+
}
|
|
8513
|
+
const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
|
|
8514
|
+
if (tail !== null)
|
|
8515
|
+
for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
|
|
8516
|
+
if (marked(d._dep, seen)) return true;
|
|
8517
|
+
if (d === tail) break;
|
|
8518
|
+
}
|
|
8519
|
+
return false;
|
|
8520
|
+
}
|
|
8521
|
+
/** One mark on `node`. The declarer repolls once its bookkeeping is done. */
|
|
8522
|
+
function mark(node) {
|
|
8523
|
+
ext(node)._marks++;
|
|
8524
|
+
active++;
|
|
8525
|
+
}
|
|
8526
|
+
/** Drops one mark from `node`: true when it was the last — the node's
|
|
8527
|
+
* verdict readers have re-derived. */
|
|
8528
|
+
function unmark(node) {
|
|
8529
|
+
active--;
|
|
8530
|
+
if (--node._x._marks !== 0) return false;
|
|
8531
|
+
repoll(node);
|
|
8532
|
+
return true;
|
|
8533
|
+
}
|
|
8534
|
+
GlobalQueue._marked = el => active !== 0 && marked(el, new Set());
|
|
8535
|
+
GlobalQueue._mark = mark;
|
|
8536
|
+
|
|
8003
8537
|
/**
|
|
8004
8538
|
* Store — the store half of `affects()` (plan sec. 29, plan sec. 37; A24 (4), #2882,
|
|
8005
8539
|
* #2893, #2904).
|
|
@@ -8128,7 +8662,7 @@ function releaseMarkScope(carrier) {
|
|
|
8128
8662
|
const entry = scopes.get(carrier);
|
|
8129
8663
|
if (entry === undefined) return;
|
|
8130
8664
|
scopes.delete(carrier);
|
|
8131
|
-
|
|
8665
|
+
releaseMarksHook(entry._inherited);
|
|
8132
8666
|
}
|
|
8133
8667
|
|
|
8134
8668
|
// `affects()` — the declaration verb of the pending model (A24 (4), ruled
|
|
@@ -8144,62 +8678,23 @@ function releaseMarkScope(carrier) {
|
|
|
8144
8678
|
// data joins nothing through it. A mark is never a re-ask, so a declared
|
|
8145
8679
|
// reload's own `refresh()` cannot silence it; a real error outranks it.
|
|
8146
8680
|
//
|
|
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.
|
|
8681
|
+
// Representation: the mark core (affects-marks.ts) — a count on the marked
|
|
8682
|
+
// node, coverage pull-derived at probe time, readers repolled at registration
|
|
8683
|
+
// and release. Here: each mark's scope (the transaction's `_marks`, or the
|
|
8684
|
+
// ambient list) and the store half.
|
|
8154
8685
|
//
|
|
8155
8686
|
// Installed on `GlobalQueue` when imported: a program that never declares a
|
|
8156
8687
|
// mark carries none of this.
|
|
8157
|
-
/** Live marks, every scope — the gate on the probe's walk. */
|
|
8158
|
-
let active = 0;
|
|
8159
8688
|
/** Marks declared outside a transaction: released at the next seam. */
|
|
8160
8689
|
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
8690
|
/** Releases one scope's marks (a landed transaction's, the ambient list at
|
|
8192
8691
|
* the seam): a node whose last mark drops re-derives its verdict readers. */
|
|
8193
8692
|
function release$1(nodes) {
|
|
8194
8693
|
for (let i = 0; i < nodes.length; i++) {
|
|
8195
8694
|
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
|
-
}
|
|
8695
|
+
// A store mark's carrier: its scope (and the marks the nodes born in
|
|
8696
|
+
// its window inherited) goes with the last mark.
|
|
8697
|
+
if (unmark(n)) releaseMarkScope(n);
|
|
8203
8698
|
}
|
|
8204
8699
|
nodes.length = 0;
|
|
8205
8700
|
}
|
|
@@ -8215,10 +8710,6 @@ function register(node) {
|
|
|
8215
8710
|
}
|
|
8216
8711
|
repoll(node);
|
|
8217
8712
|
}
|
|
8218
|
-
function mark(node) {
|
|
8219
|
-
ext(node)._marks++;
|
|
8220
|
-
active++;
|
|
8221
|
-
}
|
|
8222
8713
|
function affects(target, key) {
|
|
8223
8714
|
if (arguments.length > 2)
|
|
8224
8715
|
invalid(
|
|
@@ -8256,10 +8747,7 @@ function invalid(detail) {
|
|
|
8256
8747
|
emitDiagnostic({ code: "INVALID_AFFECTS_TARGET", kind: "write", severity: "error", message });
|
|
8257
8748
|
throw new Error(message);
|
|
8258
8749
|
}
|
|
8259
|
-
|
|
8260
|
-
GlobalQueue._mark = mark;
|
|
8261
|
-
GlobalQueue._releaseMarks = release$1;
|
|
8262
|
-
GlobalQueue._releaseAmbientMarks = parked => {
|
|
8750
|
+
installMarkHooks(release$1, parked => {
|
|
8263
8751
|
if (ambient.length === 0) return;
|
|
8264
8752
|
// The flush parked: the declaration's window is the frame's — the marks
|
|
8265
8753
|
// are its transaction's ("nothing async below ⇒ no window").
|
|
@@ -8282,7 +8770,7 @@ GlobalQueue._releaseAmbientMarks = parked => {
|
|
|
8282
8770
|
}
|
|
8283
8771
|
ambient.length = kept;
|
|
8284
8772
|
if (done.length !== 0) release$1(done);
|
|
8285
|
-
};
|
|
8773
|
+
});
|
|
8286
8774
|
function inFlight(n) {
|
|
8287
8775
|
return (
|
|
8288
8776
|
(n._statusFlags & STATUS_PENDING) !== 0 ||
|
|
@@ -8290,6 +8778,174 @@ function inFlight(n) {
|
|
|
8290
8778
|
);
|
|
8291
8779
|
}
|
|
8292
8780
|
|
|
8781
|
+
// `createDeferred` — an async memo that may lag, but never leads (D-40,
|
|
8782
|
+
// docs/create-deferred.md).
|
|
8783
|
+
//
|
|
8784
|
+
// Once the node has a committed value, its own non-final work is invisible
|
|
8785
|
+
// to the graph: its readers keep the committed value and hold nothing, its
|
|
8786
|
+
// landings are changes of their own, and only the verdict (`isPending`) and
|
|
8787
|
+
// the truth readers (`until`, `refresh`'s waiter) see the flight.
|
|
8788
|
+
//
|
|
8789
|
+
// Built from parts the engine already has, so core pays nothing for it:
|
|
8790
|
+
//
|
|
8791
|
+
// - **Read side: the loading window** (`_loading`, async.ts). Every pass of
|
|
8792
|
+
// a node with a committed value opens the window before it asks, so a
|
|
8793
|
+
// flight serves the committed value instead of throwing, and an unready
|
|
8794
|
+
// source parks the node instead of pending it. `CONFIG_REDERIVE` makes a
|
|
8795
|
+
// source going pending a question for the node's pass (which parks) rather
|
|
8796
|
+
// than a status pushed through it to its readers.
|
|
8797
|
+
// - **Verdict: a mark** (affects-marks.ts) whose lifetime is the flight —
|
|
8798
|
+
// started when a pass asks a new question (a `refresh()` re-ask is quiet, F-9),
|
|
8799
|
+
// dropped at the landing, the rejection, a sync answer or the node's death.
|
|
8800
|
+
// - **Never leads:** a flight that lands while an input it read is held
|
|
8801
|
+
// derived from that hold's world, so it lands into the hold (`holdNode`;
|
|
8802
|
+
// `setSignal` stages it there). An input that changed after the pass
|
|
8803
|
+
// would have superseded the flight, so a held input at the landing is one
|
|
8804
|
+
// the pass read held.
|
|
8805
|
+
// - **Truth readers** wait the flight out (`GlobalQueue._lagging`, signals.ts
|
|
8806
|
+
// `watch`): `await refresh(d)` delivers the landing, `until` sees only
|
|
8807
|
+
// settled values.
|
|
8808
|
+
/** Deferred nodes with a flight up. */
|
|
8809
|
+
const lagging = new Set();
|
|
8810
|
+
/** The deferred node with a flight up that `c`'s value derives from, if
|
|
8811
|
+
* any: `c` itself, or one reached through its current dependencies. */
|
|
8812
|
+
function lagOf(c, seen) {
|
|
8813
|
+
if (lagging.has(c)) return c;
|
|
8814
|
+
if (seen.has(c)) return;
|
|
8815
|
+
seen.add(c);
|
|
8816
|
+
if (c._config & CONFIG_SLOT_NODE) return lagOf(c._host?.fam?.node ?? c, seen);
|
|
8817
|
+
const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
|
|
8818
|
+
if (tail !== null)
|
|
8819
|
+
for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
|
|
8820
|
+
const l = lagOf(d._dep, seen);
|
|
8821
|
+
if (l) return l;
|
|
8822
|
+
if (d === tail) break;
|
|
8823
|
+
}
|
|
8824
|
+
}
|
|
8825
|
+
GlobalQueue._lagging = c => (lagging.size !== 0 ? lagOf(c, new Set()) : undefined);
|
|
8826
|
+
/**
|
|
8827
|
+
* Creates an async memo that may lag, but never leads. Before its first
|
|
8828
|
+
* answer it is an ordinary async memo: readers suspend to the nearest
|
|
8829
|
+
* `<Loading>`. After it, a refetch holds nothing: readers keep the last
|
|
8830
|
+
* committed value until the new one lands, and the rest of the screen moves
|
|
8831
|
+
* on without it. `isPending` reports the refetch, on the node and on what
|
|
8832
|
+
* derives from it. Use it for independent widgets (dashboards, feeds,
|
|
8833
|
+
* search results) where the previous answer with a "refreshing" indicator is
|
|
8834
|
+
* better than waiting with everything else.
|
|
8835
|
+
*
|
|
8836
|
+
* - It never shows a value ahead of a held change: a refetch that read a
|
|
8837
|
+
* held value lands with that hold.
|
|
8838
|
+
* - A rejected refetch errors like any memo; the stale value never masks it.
|
|
8839
|
+
* - `refresh(d)` is a quiet re-ask, and `await refresh(d)` and `until`
|
|
8840
|
+
* wait for the landing rather than the stale value.
|
|
8841
|
+
* - Combine with `loadingValue` for a node that never suspends at all.
|
|
8842
|
+
*
|
|
8843
|
+
* @example
|
|
8844
|
+
* ```tsx
|
|
8845
|
+
* const rows = createDeferred(() => fetchRows(store.period));
|
|
8846
|
+
*
|
|
8847
|
+
* <section class={{ stale: isPending(() => rows()) }}>
|
|
8848
|
+
* <For each={rows()}>{row => <Row row={row} />}</For>
|
|
8849
|
+
* </section>
|
|
8850
|
+
* ```
|
|
8851
|
+
*/
|
|
8852
|
+
function createDeferred(fn, options) {
|
|
8853
|
+
let node;
|
|
8854
|
+
/** The flight in force: a promise, a stream, or the error an unready
|
|
8855
|
+
* source parked the node with. */
|
|
8856
|
+
let flight = null;
|
|
8857
|
+
let marked = false;
|
|
8858
|
+
let answered = false;
|
|
8859
|
+
/** The flight `r` is over. An async answer (`held`) arriving while an
|
|
8860
|
+
* input is held lands into that hold first. */
|
|
8861
|
+
const land = (r, held) => {
|
|
8862
|
+
if (r === null || flight !== r) return;
|
|
8863
|
+
flight = null;
|
|
8864
|
+
lagging.delete(node);
|
|
8865
|
+
if (held)
|
|
8866
|
+
for (let d = node._deps; d !== null; d = d._nextDep)
|
|
8867
|
+
if (d._dep._config & CONFIG_HELD) {
|
|
8868
|
+
holdNode(node, txOf(d._dep));
|
|
8869
|
+
break;
|
|
8870
|
+
}
|
|
8871
|
+
if (marked) {
|
|
8872
|
+
marked = false;
|
|
8873
|
+
unmark(node);
|
|
8874
|
+
schedule();
|
|
8875
|
+
}
|
|
8876
|
+
};
|
|
8877
|
+
const ask = r => {
|
|
8878
|
+
flight = r;
|
|
8879
|
+
lagging.add(node);
|
|
8880
|
+
if (!marked && !(node._flags & REACTIVE_REASK)) {
|
|
8881
|
+
marked = true;
|
|
8882
|
+
mark(node);
|
|
8883
|
+
repoll(node);
|
|
8884
|
+
}
|
|
8885
|
+
};
|
|
8886
|
+
/** Registers the flight and serves the pass's value: the committed one,
|
|
8887
|
+
* or over a held landing the landing itself, which a lagging question
|
|
8888
|
+
* does not replace (D-40) — restaged as the committed value, the hold's
|
|
8889
|
+
* readers would re-derive against the old input. */
|
|
8890
|
+
const serve = r => {
|
|
8891
|
+
const v = handleAsync(node, r);
|
|
8892
|
+
return node._config & CONFIG_HELD && node._pendingValue !== NOT_PENDING
|
|
8893
|
+
? node._pendingValue
|
|
8894
|
+
: v;
|
|
8895
|
+
};
|
|
8896
|
+
const end = () => node._flags & REACTIVE_DISPOSED && land(flight);
|
|
8897
|
+
return accessor(
|
|
8898
|
+
computed(prev => {
|
|
8899
|
+
node = getOwner();
|
|
8900
|
+
node._config |= CONFIG_REDERIVE;
|
|
8901
|
+
cleanup(end);
|
|
8902
|
+
// Answered: the node has a real committed answer — not a first load,
|
|
8903
|
+
// not a `loadingValue` window still open (both are loading, F-9).
|
|
8904
|
+
// Sticky, and read before this wrapper ever opens the window itself.
|
|
8905
|
+
const open = (answered ||= !(node._statusFlags & STATUS_UNINITIALIZED) && !node._loading);
|
|
8906
|
+
if (open) ((node._loading = true), (node._config |= CONFIG_LAGS));
|
|
8907
|
+
let r;
|
|
8908
|
+
try {
|
|
8909
|
+
r = fn(prev);
|
|
8910
|
+
} catch (e) {
|
|
8911
|
+
// The wrapping form: an unready source parks the node in the window,
|
|
8912
|
+
// and the wait is its flight until the source's settle re-runs it.
|
|
8913
|
+
if (open && e instanceof NotReadyError) ask(e);
|
|
8914
|
+
throw e;
|
|
8915
|
+
}
|
|
8916
|
+
if (open && typeof r === "object" && r !== null) {
|
|
8917
|
+
if (isThenable(r)) {
|
|
8918
|
+
ask(r);
|
|
8919
|
+
const done = () => land(r, true);
|
|
8920
|
+
r.then(done, done);
|
|
8921
|
+
return serve(r);
|
|
8922
|
+
}
|
|
8923
|
+
const iterate = r[Symbol.asyncIterator];
|
|
8924
|
+
if (iterate) {
|
|
8925
|
+
// A stream's flight ends at its first yield (or its end).
|
|
8926
|
+
const it = iterate.call(r);
|
|
8927
|
+
let first = true;
|
|
8928
|
+
const done = () => land(s, true);
|
|
8929
|
+
const s = {
|
|
8930
|
+
// Registered ahead of the engine's own reaction to the same
|
|
8931
|
+
// promise, as for a plain flight: verdict and value in one flush.
|
|
8932
|
+
next: v => {
|
|
8933
|
+
const p = it.next(v);
|
|
8934
|
+
if (first) ((first = false), p.then(done, done));
|
|
8935
|
+
return p;
|
|
8936
|
+
},
|
|
8937
|
+
return: v => it.return?.(v) ?? Promise.resolve({ done: true, value: v })
|
|
8938
|
+
};
|
|
8939
|
+
ask(s);
|
|
8940
|
+
return serve({ [Symbol.asyncIterator]: () => s });
|
|
8941
|
+
}
|
|
8942
|
+
}
|
|
8943
|
+
land(flight);
|
|
8944
|
+
return r;
|
|
8945
|
+
}, options)
|
|
8946
|
+
);
|
|
8947
|
+
}
|
|
8948
|
+
|
|
8293
8949
|
// CARVE 4: `flatten` lived in boundaries.ts but is the general children
|
|
8294
8950
|
// resolver the renderers (`web`, `universal`, `solid`'s `children()`) walk
|
|
8295
8951
|
// through — not a boundary feature. Moved verbatim so the boundary carve
|
|
@@ -8381,7 +9037,7 @@ function flattenArray(children, results = [], options) {
|
|
|
8381
9037
|
* the root holds the frame as for any reader. The tree's own status takes
|
|
8382
9038
|
* the same route from the output's pass (`read(tree)` throws it).
|
|
8383
9039
|
*
|
|
8384
|
-
* The hold (`blocked`, scheduler.ts) asks `
|
|
9040
|
+
* The hold (`blocked`, scheduler.ts) asks `hiddenBy`: a reader behind a
|
|
8385
9041
|
* fallback — any boundary on its chain whose last pass chose the fallback —
|
|
8386
9042
|
* is not on screen and holds nothing. An error boundary flipping to its
|
|
8387
9043
|
* fallback is the same as a loading one resetting: every reader under it
|
|
@@ -8416,6 +9072,11 @@ const boundaryOf = node => node._context[BOUNDARY];
|
|
|
8416
9072
|
/** Catches what reaches it: an error boundary always; a loading one until it
|
|
8417
9073
|
* has shown content, or while re-armed. */
|
|
8418
9074
|
const isCollecting = b => b._type === STATUS_ERROR || !b._initialized || b._armed;
|
|
9075
|
+
/** A reader `b` waits on: recorded for its `on`, and `b` visited at the seam. */
|
|
9076
|
+
function record(b, node) {
|
|
9077
|
+
b._readers.add(node);
|
|
9078
|
+
collecting.add(b);
|
|
9079
|
+
}
|
|
8419
9080
|
/** The reader goes to the fallback: recorded, and the output re-derives
|
|
8420
9081
|
* (a no-op while the output's own pass is running — its post-check
|
|
8421
9082
|
* decides). */
|
|
@@ -8432,8 +9093,7 @@ function caught(b, node, error) {
|
|
|
8432
9093
|
setSignal(b._error, caught);
|
|
8433
9094
|
reportClientError(caught, b._owner, node);
|
|
8434
9095
|
}
|
|
8435
|
-
b
|
|
8436
|
-
collecting.add(b);
|
|
9096
|
+
record(b, node);
|
|
8437
9097
|
if (b._armed) flip(b);
|
|
8438
9098
|
// Already showing the fallback: one more reader to wait on changes
|
|
8439
9099
|
// nothing on screen. The fallback is instantiated once per show — a later
|
|
@@ -8449,8 +9109,9 @@ function caught(b, node, error) {
|
|
|
8449
9109
|
function flip(b) {
|
|
8450
9110
|
b._initialized = false;
|
|
8451
9111
|
b._lane = b._armLane;
|
|
8452
|
-
|
|
8453
|
-
|
|
9112
|
+
const o = b._output;
|
|
9113
|
+
if (!b._lane && o._config & CONFIG_HELD && txOf(o) !== flushTransaction) unhold(o);
|
|
9114
|
+
if (b._lane !== null && b._output !== null) laneStageHook(b._output, b._lane, true, false);
|
|
8454
9115
|
}
|
|
8455
9116
|
function redraw(b) {
|
|
8456
9117
|
if (b._output !== null && !(b._output._flags & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
|
|
@@ -8458,7 +9119,7 @@ function redraw(b) {
|
|
|
8458
9119
|
schedule();
|
|
8459
9120
|
}
|
|
8460
9121
|
}
|
|
8461
|
-
/**
|
|
9122
|
+
/** `catchHook` (scheduler.ts): status from a frame reader, nearest boundary first.
|
|
8462
9123
|
* A loading boundary on the way records a pending reader whether or not it
|
|
8463
9124
|
* catches it (its `on` may collect it later). */
|
|
8464
9125
|
function catchStatus(node, flags, error) {
|
|
@@ -8471,7 +9132,7 @@ function catchStatus(node, flags, error) {
|
|
|
8471
9132
|
// lands with its run, not a round after). Others still waiting: nothing
|
|
8472
9133
|
// on screen changes, the fallback stays as it is.
|
|
8473
9134
|
if (collecting.size !== 0)
|
|
8474
|
-
for (let b = boundaryOf(node); b
|
|
9135
|
+
for (let b = boundaryOf(node); b; b = b._parent)
|
|
8475
9136
|
if (
|
|
8476
9137
|
!(node._statusFlags & unsettled(b)) &&
|
|
8477
9138
|
b._readers.delete(node) &&
|
|
@@ -8482,28 +9143,29 @@ function catchStatus(node, flags, error) {
|
|
|
8482
9143
|
}
|
|
8483
9144
|
return false;
|
|
8484
9145
|
}
|
|
8485
|
-
for (let b = boundaryOf(node); b
|
|
9146
|
+
for (let b = boundaryOf(node); b; b = b._parent) {
|
|
8486
9147
|
if (!(b._type & flags)) continue;
|
|
8487
9148
|
if (isCollecting(b)) {
|
|
8488
9149
|
caught(b, node, error);
|
|
8489
|
-
return true;
|
|
9150
|
+
return b._armed ? CAUGHT_REARMED : true;
|
|
8490
9151
|
}
|
|
8491
|
-
b
|
|
8492
|
-
collecting.add(b);
|
|
9152
|
+
record(b, node);
|
|
8493
9153
|
}
|
|
8494
9154
|
return false;
|
|
8495
9155
|
}
|
|
8496
|
-
/** The nearest boundary
|
|
9156
|
+
/** The nearest loading boundary that would catch `node` was re-armed this
|
|
9157
|
+
* flush. */
|
|
9158
|
+
function armedOver(node) {
|
|
9159
|
+
for (let b = boundaryOf(node); b; b = b._parent)
|
|
9160
|
+
if (b._type & STATUS_PENDING && isCollecting(b)) return b._armed;
|
|
9161
|
+
}
|
|
9162
|
+
/** The nearest boundary on `r`'s chain showing its fallback, if any
|
|
9163
|
+
* (`hiddenHook`: `r` is behind a fallback). */
|
|
8497
9164
|
function hiddenBy(r) {
|
|
8498
|
-
for (let b = boundaryOf(r); b
|
|
8499
|
-
if (b._fallback) return b;
|
|
9165
|
+
for (let b = boundaryOf(r); b; b = b._parent) if (b._fallback) return b;
|
|
8500
9166
|
return undefined;
|
|
8501
9167
|
}
|
|
8502
|
-
/**
|
|
8503
|
-
function hidden(r) {
|
|
8504
|
-
return hiddenBy(r) !== undefined;
|
|
8505
|
-
}
|
|
8506
|
-
/** GlobalQueue._heldRun: a queued run behind a fallback waits for the reveal
|
|
9168
|
+
/** `heldRunHook` (scheduler.ts): a queued run behind a fallback waits for the reveal
|
|
8507
9169
|
* (maintainer, 2026-10-02: the queue is held, the synchronous first render
|
|
8508
9170
|
* goes through). A user effect's, which would read a DOM that is not
|
|
8509
9171
|
* attached ("many depend on reading the DOM, something that can't happen
|
|
@@ -8548,7 +9210,10 @@ function prune(b, pass) {
|
|
|
8548
9210
|
for (const r of b._readers) {
|
|
8549
9211
|
if (r._flags & REACTIVE_DISPOSED) b._readers.delete(r);
|
|
8550
9212
|
else if (!(r._statusFlags & mask)) {
|
|
8551
|
-
if (
|
|
9213
|
+
if (
|
|
9214
|
+
(r !== b._tree || b._armed) &&
|
|
9215
|
+
(r._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD
|
|
9216
|
+
) {
|
|
8552
9217
|
// Landed, held: the content is that transaction's — the output's
|
|
8553
9218
|
// pass enters it and the reveal lands with the reader's run. The
|
|
8554
9219
|
// seam, past its park decision, waits for the commit instead. (A
|
|
@@ -8578,7 +9243,7 @@ function ready(b) {
|
|
|
8578
9243
|
prune(b, false) === 0)
|
|
8579
9244
|
);
|
|
8580
9245
|
}
|
|
8581
|
-
/**
|
|
9246
|
+
/** `boundarySeamHook` (scheduler.ts), end of the seam: readers that settled, died,
|
|
8582
9247
|
* or landed and committed are dropped; a fallback with none left reveals
|
|
8583
9248
|
* next round. An arm resolves: nothing collected, nothing happened. */
|
|
8584
9249
|
function boundarySeam() {
|
|
@@ -8591,6 +9256,18 @@ function boundarySeam() {
|
|
|
8591
9256
|
// A parked frame's boundary (zombie) keeps its state for its revival.
|
|
8592
9257
|
if (flags & REACTIVE_ZOMBIE) continue;
|
|
8593
9258
|
prune(b, false);
|
|
9259
|
+
const o = b._output;
|
|
9260
|
+
if (
|
|
9261
|
+
b._armed &&
|
|
9262
|
+
!b._initialized &&
|
|
9263
|
+
o._config & CONFIG_HELD &&
|
|
9264
|
+
[...b._readers].every(
|
|
9265
|
+
r => !(r._statusFlags & STATUS_PENDING) && r._config & CONFIG_HELD && txOf(r) === txOf(o)
|
|
9266
|
+
)
|
|
9267
|
+
) {
|
|
9268
|
+
b._readers.clear();
|
|
9269
|
+
b._initialized = true;
|
|
9270
|
+
}
|
|
8594
9271
|
if (b._armed && !b._initialized && !b._ahead) reportOutsideHold(b);
|
|
8595
9272
|
b._armed = false;
|
|
8596
9273
|
if (b._readers.size === 0) {
|
|
@@ -8617,15 +9294,17 @@ function reportOutsideHold(b) {
|
|
|
8617
9294
|
if (outside(s._sub, false)) return true;
|
|
8618
9295
|
return false;
|
|
8619
9296
|
}
|
|
8620
|
-
if (
|
|
8621
|
-
for (let o = boundaryOf(reader); o
|
|
8622
|
-
if (o === b) return false;
|
|
9297
|
+
if (hiddenBy(reader)) return false;
|
|
9298
|
+
for (let o = boundaryOf(reader); o; o = o._parent) if (o === b) return false;
|
|
8623
9299
|
return true;
|
|
8624
9300
|
};
|
|
8625
9301
|
for (const r of b._readers) {
|
|
8626
9302
|
const sources = r._x?._pendingSources;
|
|
8627
9303
|
if (sources === undefined) continue;
|
|
8628
9304
|
for (const source of sources) {
|
|
9305
|
+
// Only the flight the change itself started (D-16): an outside reader
|
|
9306
|
+
// of an earlier write's flight holds nothing the change waits on.
|
|
9307
|
+
if (source._time !== clock) continue;
|
|
8629
9308
|
for (let s = source._subs; s !== null; s = s._nextSub) {
|
|
8630
9309
|
if (!outside(s._sub, true)) continue;
|
|
8631
9310
|
const name = source._name;
|
|
@@ -8650,15 +9329,6 @@ function reportOutsideHold(b) {
|
|
|
8650
9329
|
}
|
|
8651
9330
|
}
|
|
8652
9331
|
}
|
|
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
9332
|
/** `on` changed after the mount: fresh again if anything under it is
|
|
8663
9333
|
* pending now; collecting for the rest of the flush either way. */
|
|
8664
9334
|
function arm(b) {
|
|
@@ -8682,20 +9352,32 @@ function arm(b) {
|
|
|
8682
9352
|
return;
|
|
8683
9353
|
}
|
|
8684
9354
|
b._armed = true;
|
|
8685
|
-
// A verdict read in `on` (`isPending
|
|
8686
|
-
//
|
|
9355
|
+
// A verdict read in `on` (`isPending`, or a memo of one — #3528's
|
|
9356
|
+
// `createMemo(() => isPending(m2))`: the mark carries, core.ts `read`)
|
|
9357
|
+
// whose frame has no transaction yet: the swap is still display-ahead — the frame gets one now (it lands
|
|
8687
9358
|
// at this very seam if nothing holds it), and the swap is its verdict
|
|
8688
9359
|
// lane's work, shown ahead of the frame's own runs (#3528).
|
|
8689
9360
|
let lane = passLane;
|
|
8690
|
-
if (lane === null && globalQueue._running &&
|
|
9361
|
+
if (lane === null && globalQueue._running && getOwner()._config & CONFIG_VERDICT) {
|
|
8691
9362
|
joinFuture(null);
|
|
8692
9363
|
lane = GlobalQueue._verdictLane(flushTransaction);
|
|
8693
9364
|
}
|
|
8694
9365
|
b._armLane = lane;
|
|
8695
9366
|
b._ahead = lane !== null;
|
|
8696
9367
|
collecting.add(b);
|
|
9368
|
+
const n = b._readers.size;
|
|
9369
|
+
heldUnder(b._owner, flushTransaction, r => record(b, r));
|
|
8697
9370
|
for (const r of b._readers) {
|
|
8698
|
-
if (
|
|
9371
|
+
if (
|
|
9372
|
+
b._readers.size !== n ||
|
|
9373
|
+
(r._statusFlags & STATUS_PENDING && !(r._flags & REACTIVE_DISPOSED))
|
|
9374
|
+
) {
|
|
9375
|
+
// What it collects was forwarded through it to the boundaries above
|
|
9376
|
+
// too (`catchStatus` records a reader in every boundary on the way).
|
|
9377
|
+
// It is this boundary's alone now (B5, the nearest catcher): an outer
|
|
9378
|
+
// re-armed by the same tick does not wait for it (D-14, U-4).
|
|
9379
|
+
for (let a = b._parent; a !== null; a = a._parent)
|
|
9380
|
+
for (const o of b._readers) a._readers.delete(o);
|
|
8699
9381
|
flip(b);
|
|
8700
9382
|
redraw(b);
|
|
8701
9383
|
return;
|
|
@@ -8765,7 +9447,7 @@ function createBoundary(type, fn, show, onFn) {
|
|
|
8765
9447
|
} catch (e) {
|
|
8766
9448
|
if (!(e instanceof NotReadyError)) {
|
|
8767
9449
|
if (!globalQueue.notify(getOwner(), STATUS_ERROR, STATUS_ERROR, e)) {
|
|
8768
|
-
|
|
9450
|
+
if (true) haltReactivityAt(e, getOwner());
|
|
8769
9451
|
throw e;
|
|
8770
9452
|
}
|
|
8771
9453
|
return;
|
|
@@ -8828,8 +9510,7 @@ function createBoundary(type, fn, show, onFn) {
|
|
|
8828
9510
|
}
|
|
8829
9511
|
// Forwarded: a loading boundary showing content keeps it and
|
|
8830
9512
|
// holds the frame like any reader (recorded for its `on`).
|
|
8831
|
-
b
|
|
8832
|
-
collecting.add(b);
|
|
9513
|
+
record(b, tree);
|
|
8833
9514
|
}
|
|
8834
9515
|
// A pending passes an error boundary by (A6); an error, a loading
|
|
8835
9516
|
// one; a loading boundary showing content forwards its pending.
|
|
@@ -8869,8 +9550,7 @@ function createBoundary(type, fn, show, onFn) {
|
|
|
8869
9550
|
}
|
|
8870
9551
|
// Forwarded: a loading boundary showing content keeps it and
|
|
8871
9552
|
// holds the frame like any reader (recorded for its `on`).
|
|
8872
|
-
b
|
|
8873
|
-
collecting.add(b);
|
|
9553
|
+
record(b, tree);
|
|
8874
9554
|
}
|
|
8875
9555
|
// A pending passes an error boundary by (A6).
|
|
8876
9556
|
throw e;
|
|
@@ -9031,12 +9711,17 @@ function createErrorBoundary(fn, fallback) {
|
|
|
9031
9711
|
}
|
|
9032
9712
|
// Installed at module evaluation — present exactly when something imports a
|
|
9033
9713
|
// boundary. An app without one pays the three null checks and nothing else.
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9714
|
+
installBoundaryHooks(
|
|
9715
|
+
catchStatus,
|
|
9716
|
+
(node, held) =>
|
|
9717
|
+
!held &&
|
|
9718
|
+
(node._statusFlags & STATUS_UNINITIALIZED
|
|
9719
|
+
? catchStatus(node, STATUS_PENDING)
|
|
9720
|
+
: armedOver(node)),
|
|
9721
|
+
hiddenBy,
|
|
9722
|
+
boundarySeam,
|
|
9723
|
+
heldRun
|
|
9724
|
+
);
|
|
9040
9725
|
|
|
9041
9726
|
/**
|
|
9042
9727
|
* Reveal order — `createRevealOrder` (`<Reveal>`): the order in which a group
|
|
@@ -9279,6 +9964,7 @@ export {
|
|
|
9279
9964
|
affects,
|
|
9280
9965
|
configureClientErrors,
|
|
9281
9966
|
createContext,
|
|
9967
|
+
createDeferred,
|
|
9282
9968
|
createEffect,
|
|
9283
9969
|
createErrorBoundary,
|
|
9284
9970
|
createLoadingBoundary,
|
|
@@ -9324,6 +10010,9 @@ export {
|
|
|
9324
10010
|
resolvedTable,
|
|
9325
10011
|
runWithOwner,
|
|
9326
10012
|
setContext,
|
|
10013
|
+
setReadWatch,
|
|
10014
|
+
setSignalIdentities,
|
|
10015
|
+
signalName,
|
|
9327
10016
|
snapshot,
|
|
9328
10017
|
sourceGet,
|
|
9329
10018
|
sourceHas,
|
|
@@ -9332,6 +10021,7 @@ export {
|
|
|
9332
10021
|
storeHasFamily,
|
|
9333
10022
|
storeHasOptimisticFamily,
|
|
9334
10023
|
storeIsShallow,
|
|
10024
|
+
storeName,
|
|
9335
10025
|
storePath,
|
|
9336
10026
|
until,
|
|
9337
10027
|
untrack,
|