@solidjs/signals 2.0.0-rc.19 → 2.0.0-rc.20
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 +553 -264
- package/dist/dev.attribution.js +18 -18
- package/dist/dev.js +1030 -439
- package/dist/observe/affects.js +12 -2
- package/dist/observe/boundaries.js +168 -115
- package/dist/observe/core/async.js +57 -37
- package/dist/observe/core/attribution.js +6 -6
- package/dist/observe/core/constants.js +9 -1
- package/dist/observe/core/copies.js +10 -0
- package/dist/observe/core/core.js +261 -181
- package/dist/observe/core/effect.js +32 -27
- package/dist/observe/core/graph.js +7 -7
- package/dist/observe/core/heap.js +29 -29
- package/dist/observe/core/lanes.js +301 -101
- package/dist/observe/core/owner.js +13 -13
- package/dist/observe/core/scheduler.js +124 -69
- package/dist/observe/core/truth.js +9 -7
- package/dist/observe/core/verdict.js +160 -98
- package/dist/observe/deferred.js +1 -1
- package/dist/observe/index.js +2 -0
- package/dist/observe/map.js +120 -116
- package/dist/observe/reveal.js +48 -48
- package/dist/observe/signals.js +5 -5
- package/dist/observe/store/affects.js +2 -2
- package/dist/observe/store/optimistic.js +272 -176
- package/dist/observe/store/projection.js +21 -10
- package/dist/observe/store/reconcile.js +8 -2
- package/dist/observe/store/store.js +91 -55
- package/dist/observe/store/types.js +11 -1
- package/dist/observe/store/utils.js +1 -1
- package/dist/prod/affects.js +12 -2
- package/dist/prod/boundaries.js +164 -109
- package/dist/prod/core/async.js +201 -181
- package/dist/prod/core/constants.js +9 -1
- package/dist/prod/core/copies.js +10 -0
- package/dist/prod/core/core.js +244 -164
- package/dist/prod/core/dev.js +108 -3
- package/dist/prod/core/effect.js +28 -23
- package/dist/prod/core/graph.js +9 -9
- package/dist/prod/core/heap.js +16 -16
- package/dist/prod/core/identities.js +1 -1
- package/dist/prod/core/lanes.js +307 -95
- package/dist/prod/core/owner.js +22 -22
- package/dist/prod/core/scheduler.js +266 -211
- package/dist/prod/core/truth.js +9 -7
- package/dist/prod/core/verdict.js +161 -99
- package/dist/prod/deferred.js +1 -1
- package/dist/prod/index.js +2 -0
- package/dist/prod/map.js +78 -74
- package/dist/prod/reveal.js +34 -34
- package/dist/prod/signals.js +3 -3
- package/dist/prod/store/affects.js +15 -15
- package/dist/prod/store/optimistic.js +185 -89
- package/dist/prod/store/projection.js +49 -38
- package/dist/prod/store/reconcile.js +8 -2
- package/dist/prod/store/store.js +99 -63
- package/dist/prod/store/types.js +11 -1
- package/dist/types/affects.d.ts +3 -2
- package/dist/types/boundaries.d.ts +14 -2
- package/dist/types/core/async.d.ts +2 -1
- package/dist/types/core/constants.d.ts +8 -0
- package/dist/types/core/copies.d.ts +54 -0
- package/dist/types/core/core.d.ts +15 -0
- package/dist/types/core/dev.d.ts +1 -1
- package/dist/types/core/lanes.d.ts +8 -1
- package/dist/types/core/scheduler.d.ts +36 -4
- package/dist/types/core/truth.d.ts +3 -1
- package/dist/types/core/types.d.ts +6 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/store/hooks.d.ts +15 -1
- package/dist/types/store/store.d.ts +2 -1
- package/dist/types/store/target.d.ts +5 -0
- package/dist/types/store/types.d.ts +7 -0
- package/package.json +1 -1
package/dist/dev.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
C as CONFIG_GUESS,
|
|
3
3
|
N as NOT_PENDING,
|
|
4
|
+
S as STATUS_UNINITIALIZED,
|
|
4
5
|
a as NotReadyError,
|
|
5
6
|
b as CONFIG_AUTHORITATIVE,
|
|
6
7
|
e as enqueueSub,
|
|
@@ -8,186 +9,198 @@ import {
|
|
|
8
9
|
s as schedule,
|
|
9
10
|
q as question,
|
|
10
11
|
n as nextQuestion,
|
|
12
|
+
d as armGuessing,
|
|
11
13
|
i as installLaneHooks,
|
|
12
14
|
G as GlobalQueue,
|
|
13
15
|
r as resolveTx,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
u as REACTIVE_LANE_READ,
|
|
16
|
+
f as newTransaction,
|
|
17
|
+
l as list,
|
|
18
|
+
g as staleReader,
|
|
19
|
+
h as removePendingSource,
|
|
20
|
+
R as REACTIVE_DIRTY,
|
|
21
|
+
j as REACTIVE_CHECK,
|
|
22
|
+
k as REACTIVE_IN_HEAP,
|
|
23
|
+
m as releaseFlightTeardown,
|
|
24
|
+
o as handleAsync,
|
|
25
|
+
p as notifyStatus,
|
|
26
|
+
t as STATUS_PENDING,
|
|
27
|
+
u as CONFIG_SLOT_NODE,
|
|
27
28
|
v as ext,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
29
|
+
w as txOf,
|
|
30
|
+
x as commitPendingNode,
|
|
31
|
+
y as ownFlights,
|
|
32
|
+
z as stagedReaders,
|
|
33
|
+
A as REACTIVE_STAGED_READ,
|
|
34
|
+
B as REACTIVE_DISPOSED,
|
|
35
|
+
D as CONFIG_VERDICT,
|
|
36
|
+
E as REACTIVE_PROBE_UNANSWERED,
|
|
37
|
+
F as blocked,
|
|
38
|
+
H as bornInto,
|
|
39
|
+
I as REACTIVE_LANE_DIRTY,
|
|
40
|
+
J as isThenable,
|
|
41
|
+
K as CONFIG_HELD,
|
|
42
|
+
L as clock,
|
|
43
|
+
M as insertSubs,
|
|
44
|
+
O as laneDirty,
|
|
45
|
+
P as REACTIVE_LANE_READ,
|
|
46
|
+
Q as flushTransaction,
|
|
47
|
+
T as REACTIVE_RECOMPUTING_DEPS,
|
|
48
|
+
U as queuePendingNode,
|
|
49
|
+
V as STATUS_ERROR,
|
|
50
|
+
W as tracking,
|
|
51
|
+
X as CONFIG_CHILDREN_FORBIDDEN,
|
|
52
|
+
Y as passLane,
|
|
53
|
+
Z as EFFECT_RENDER,
|
|
54
|
+
_ as setPassLane,
|
|
55
|
+
$ as sameLane,
|
|
56
|
+
a0 as REACTIVE_SCREEN_READ,
|
|
57
|
+
a1 as REACTIVE_FRAME_READ,
|
|
58
|
+
a2 as attrHooks,
|
|
59
|
+
a3 as CONFIG_INPUTS_PUBLISHED,
|
|
60
|
+
a4 as releaseQueues,
|
|
61
|
+
a5 as holdNode,
|
|
62
|
+
a6 as reruns,
|
|
63
|
+
a7 as CONFIG_STAGED,
|
|
64
|
+
a8 as merge$1,
|
|
65
|
+
a9 as creatorReads,
|
|
66
|
+
aa as REACTIVE_JOINED,
|
|
67
|
+
ab as stagedRead,
|
|
68
|
+
ac as setVerdict,
|
|
69
|
+
ad as context,
|
|
70
|
+
ae as installVerdictHooks,
|
|
71
|
+
af as creatorPass,
|
|
72
|
+
ag as CONFIG_ASYNC,
|
|
73
|
+
ah as REACTIVE_PROBED,
|
|
74
|
+
ai as REACTIVE_WINDOW,
|
|
75
|
+
aj as verdict,
|
|
76
|
+
ak as prepareComputed,
|
|
77
|
+
al as link,
|
|
78
|
+
am as pullComputed,
|
|
79
|
+
an as strictRead,
|
|
80
|
+
ao as strictReadActive,
|
|
81
|
+
ap as warnStrictReadUntracked,
|
|
82
|
+
aq as globalQueue,
|
|
83
|
+
ar as joinFuture,
|
|
84
|
+
as as joinPass,
|
|
85
|
+
at as markLateLinker,
|
|
86
|
+
au as spectating,
|
|
87
|
+
av as unflushedValue,
|
|
88
|
+
aw as CONFIG_STAGED_ERROR,
|
|
89
|
+
ax as REACTIVE_REASK,
|
|
90
|
+
ay as enterAction,
|
|
91
|
+
az as setEffectCallback,
|
|
92
|
+
aA as exitAction,
|
|
93
|
+
aB as actionDepth,
|
|
94
|
+
aC as flush,
|
|
95
|
+
aD as setQuestion,
|
|
96
|
+
aE as emitDiagnostic,
|
|
97
|
+
aF as enterCallback,
|
|
98
|
+
aG as exitCallback,
|
|
99
|
+
aH as getOwner,
|
|
100
|
+
aI as inEffectCallback,
|
|
101
|
+
aJ as effectCallbackEvent,
|
|
102
|
+
aK as setExternalHooks,
|
|
103
|
+
aL as signal,
|
|
104
|
+
aM as setSignal,
|
|
105
|
+
aN as cleanup,
|
|
106
|
+
aO as read,
|
|
107
|
+
aP as DEV$1,
|
|
108
|
+
aQ as CONFIG_TRANSPARENT,
|
|
109
|
+
aR as NoOwnerError,
|
|
110
|
+
aS as ContextNotFoundError,
|
|
111
|
+
aT as createEffectNode,
|
|
112
|
+
aU as recompute,
|
|
113
|
+
aV as EFFECT_USER,
|
|
114
|
+
aW as reportDiagnostic,
|
|
115
|
+
aX as computed,
|
|
116
|
+
aY as staleValues,
|
|
117
|
+
aZ as CONFIG_AUTO_DISPOSE,
|
|
118
|
+
a_ as EFFECT_TRACKED,
|
|
119
|
+
a$ as heldRunHook,
|
|
120
|
+
b0 as unwrapStatusError,
|
|
121
|
+
b1 as setStrictRead,
|
|
122
|
+
b2 as queueEvent,
|
|
123
|
+
b3 as StatusError,
|
|
124
|
+
b4 as trimStaleDeps,
|
|
125
|
+
b5 as haltReactivityAt,
|
|
126
|
+
b6 as strictReadEvent,
|
|
127
|
+
b7 as setTrackedQueueCallback,
|
|
128
|
+
b8 as setEffectStatusNotify,
|
|
129
|
+
b9 as _hitUnhandledAsync,
|
|
130
|
+
ba as resetUnhandledAsync,
|
|
131
|
+
bb as ROOT_ERROR_HOOK,
|
|
132
|
+
bc as registerGraph,
|
|
133
|
+
bd as runWithOwner,
|
|
134
|
+
be as setMemo,
|
|
135
|
+
bf as $REFRESH,
|
|
136
|
+
bg as markRefresh,
|
|
137
|
+
bh as createRoot,
|
|
138
|
+
bi as getObserver,
|
|
139
|
+
bj as dispose,
|
|
140
|
+
bk as TimeoutError,
|
|
141
|
+
bl as untrack,
|
|
142
|
+
bm as dirtyQueue,
|
|
143
|
+
bn as CONFIG_REDERIVE,
|
|
144
|
+
bo as passTx,
|
|
145
|
+
bp as CONFIG_FRESH_READ,
|
|
146
|
+
bq as createOwner,
|
|
147
|
+
br as writeHook,
|
|
148
|
+
bs as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
|
|
149
|
+
bt as ownedScopeWriteMessage,
|
|
150
|
+
bu as isEqual,
|
|
151
|
+
bv as CONFIG_MANUAL_WRITE,
|
|
152
|
+
bw as checkForeignRead,
|
|
153
|
+
bx as snapshotCaptureActive,
|
|
154
|
+
by as captureWriteSnapshot,
|
|
155
|
+
bz as notePromotedWrite,
|
|
156
|
+
bA as slotSignal,
|
|
157
|
+
bB as stagedScreen,
|
|
158
|
+
bC as devRead,
|
|
159
|
+
bD as CONFIG_PROMOTED,
|
|
160
|
+
bE as setSlotUnobserved,
|
|
161
|
+
bF as installStoreHooks,
|
|
162
|
+
bG as joinPassTx,
|
|
163
|
+
bH as asyncTailFlights,
|
|
164
|
+
bI as untrackDepth,
|
|
165
|
+
bJ as checkPostAwaitRead,
|
|
166
|
+
bK as disposalDepth,
|
|
167
|
+
bL as insertIntoHeap,
|
|
168
|
+
bM as isDisposed,
|
|
169
|
+
bN as addPendingSource,
|
|
170
|
+
bO as propagateStatus,
|
|
171
|
+
bP as forEachDependent,
|
|
172
|
+
bQ as settlePendingSource,
|
|
173
|
+
bR as joinPassAt,
|
|
174
|
+
bS as notifyEpoch,
|
|
175
|
+
bT as SUPPORTS_PROXY,
|
|
176
|
+
bU as CONFIG_KEY_BAG,
|
|
177
|
+
bV as releaseMarksHook,
|
|
178
|
+
bW as installMarkHooks,
|
|
179
|
+
bX as CONFIG_LAGS,
|
|
180
|
+
bY as onSupersede,
|
|
181
|
+
bZ as haltReactivity,
|
|
182
|
+
b_ as CONFIG_FRAME,
|
|
183
|
+
b$ as heldUnder,
|
|
184
|
+
c0 as unhold,
|
|
185
|
+
c1 as laneStageHook,
|
|
186
|
+
c2 as REACTIVE_ZOMBIE,
|
|
187
|
+
c3 as installBoundaryHooks,
|
|
188
|
+
c4 as CAUGHT_REARMED,
|
|
189
|
+
c5 as OBSERVE$1
|
|
179
190
|
} from "./dev-shared.js";
|
|
180
191
|
export {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
c6 as checkForeignOwnerless,
|
|
193
|
+
c7 as clearSnapshots,
|
|
194
|
+
c8 as enforceLoadingBoundary,
|
|
195
|
+
c9 as getNextChildId,
|
|
196
|
+
ca as markSnapshotScope,
|
|
197
|
+
cb as peekNextChildId,
|
|
198
|
+
cc as releaseSnapshotScope,
|
|
199
|
+
cd as resetErrorHalt,
|
|
200
|
+
ce as runAsSolidCopy,
|
|
201
|
+
cf as setConsoleFooter,
|
|
202
|
+
cg as setSnapshotCapture,
|
|
203
|
+
ch as setWriteHook
|
|
191
204
|
} from "./dev-shared.js";
|
|
192
205
|
|
|
193
206
|
// Truth reads (`until`, `refresh`; CONFIG_AUTHORITATIVE) over lane nodes.
|
|
@@ -196,11 +209,13 @@ export {
|
|
|
196
209
|
// which exists once something installs lanes (`createOptimistic`, verdicts).
|
|
197
210
|
/** A lane node's current value: its guess, or the latest it derived — the
|
|
198
211
|
* staging a shown lane holds for its next reveal, else its slot; the
|
|
199
|
-
* committed value while it has none (its first lane pass in flight)
|
|
212
|
+
* committed value while it has none (its first lane pass in flight) —
|
|
213
|
+
* NOT_PENDING for a node with no committed value either (born in the lane,
|
|
214
|
+
* uninitialized: its reads throw NotReady, A7; #4363). */
|
|
200
215
|
function laneValueOf(el) {
|
|
201
216
|
if (!(el._config & CONFIG_GUESS) && el._pendingValue !== NOT_PENDING) return el._pendingValue;
|
|
202
217
|
const v = el._x._lane;
|
|
203
|
-
return v !== NOT_PENDING ? v : el._value;
|
|
218
|
+
return v !== NOT_PENDING || el._statusFlags & STATUS_UNINITIALIZED ? v : el._value;
|
|
204
219
|
}
|
|
205
220
|
/** Truth readers (`until`, `refresh`; CONFIG_AUTHORITATIVE) — pay-for-use:
|
|
206
221
|
* the first one installs these on GlobalQueue (signals.ts `watch`).
|
|
@@ -272,10 +287,15 @@ function newLane(parent) {
|
|
|
272
287
|
return l;
|
|
273
288
|
}
|
|
274
289
|
/** What the screen shows for a lane's node: the lane's value once the lane
|
|
275
|
-
* has revealed, the committed truth before
|
|
290
|
+
* has revealed, the committed truth before — NOT_PENDING when there is none
|
|
291
|
+
* (uninitialized, as `laneValueOf`): its read throws NotReady. */
|
|
276
292
|
function display(el) {
|
|
277
293
|
const v = el._x._lane;
|
|
278
|
-
return el._x._transaction._shown && v !== NOT_PENDING
|
|
294
|
+
return el._x._transaction._shown && v !== NOT_PENDING
|
|
295
|
+
? v
|
|
296
|
+
: el._statusFlags & STATUS_UNINITIALIZED
|
|
297
|
+
? NOT_PENDING
|
|
298
|
+
: el._value;
|
|
279
299
|
}
|
|
280
300
|
/** Guesses written since the last seam (`[node, value, question, …]`).
|
|
281
301
|
* Applied at the seam, once the frame's verdict is known — the parent is
|
|
@@ -288,9 +308,10 @@ const pendingGuesses = [];
|
|
|
288
308
|
* lane's value or the committed one — what an optimistic updater composes
|
|
289
309
|
* on (a store setter's draft too, store/optimistic.ts). */
|
|
290
310
|
function guessedValueOf(n) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
311
|
+
let g = pendingGuessOf(n);
|
|
312
|
+
return g !== NOT_PENDING || (n._config & CONFIG_OVERRIDE && (g = laneValueOf(n)) !== NOT_PENDING)
|
|
313
|
+
? g
|
|
314
|
+
: n._value;
|
|
294
315
|
}
|
|
295
316
|
/** The unflushed guess to `n`, else NOT_PENDING. */
|
|
296
317
|
function pendingGuessOf(n) {
|
|
@@ -298,6 +319,33 @@ function pendingGuessOf(n) {
|
|
|
298
319
|
if (pendingGuesses[i] === n) return pendingGuesses[i + 1];
|
|
299
320
|
return NOT_PENDING;
|
|
300
321
|
}
|
|
322
|
+
/** This module's copy of scheduler.ts `guessing`: its readers here stay
|
|
323
|
+
* foldable when a bundler splits lanes.ts from scheduler.ts into separate
|
|
324
|
+
* chunks (a cross-chunk binding is not folded; the compiled live page). */
|
|
325
|
+
let guessed = false;
|
|
326
|
+
/** Guesses another action's lane took over (#4336): the node, its guess,
|
|
327
|
+
* the guess's lane, its question, the lane that took it. */
|
|
328
|
+
const kept = [];
|
|
329
|
+
/** Lanes a revert dissolved: the guesses they took come back at their
|
|
330
|
+
* parent's landing (`endLanes`). */
|
|
331
|
+
const reverted = /* @__PURE__ */ new WeakSet();
|
|
332
|
+
/** Lane `l` takes `n`'s guess (another action's), kept to come back if `l`
|
|
333
|
+
* reverts. Its readers saw the guess: they re-derive at the lane's reveal
|
|
334
|
+
* (and, a flight's, hold the lane while they would show it). `d`: a guess
|
|
335
|
+
* `l` takes without writing it (a store target's other slots) — the node
|
|
336
|
+
* shows its truth in `l`'s world until the guess comes back. */
|
|
337
|
+
function take(n, l, d) {
|
|
338
|
+
const x = n._x;
|
|
339
|
+
kept.push(n, x._lane, x._transaction, x._q, l);
|
|
340
|
+
rerun(n, l);
|
|
341
|
+
n._config &= d ? -10241 : ~CONFIG_GUESS;
|
|
342
|
+
if (d) ((x._transaction = null), (x._lane = NOT_PENDING));
|
|
343
|
+
else list(n, l);
|
|
344
|
+
}
|
|
345
|
+
/** `n`'s readers are stale readers of `t` (re-derived at its landing). */
|
|
346
|
+
function rerun(n, t) {
|
|
347
|
+
for (let s = n._subs; s !== null; s = s._nextSub) staleReader(s._sub, t);
|
|
348
|
+
}
|
|
301
349
|
function optimisticWrite(n, v) {
|
|
302
350
|
// What the user sees: an unflushed guess to the same node, else the lane's
|
|
303
351
|
// value or the committed one. The updater composes on it; guessing it
|
|
@@ -308,6 +356,8 @@ function optimisticWrite(n, v) {
|
|
|
308
356
|
if (typeof v === "function") v = v(prev);
|
|
309
357
|
if (n._equals && n._equals(prev, v) && !(n._config & CONFIG_GUESS)) return v;
|
|
310
358
|
pendingGuesses.push(n, v, question || nextQuestion());
|
|
359
|
+
armGuessing();
|
|
360
|
+
guessed = true;
|
|
311
361
|
schedule();
|
|
312
362
|
return v;
|
|
313
363
|
}
|
|
@@ -325,8 +375,8 @@ function optimisticWrite(n, v) {
|
|
|
325
375
|
* installs the resolver). A17: the guess stands in for the flight. */
|
|
326
376
|
function inFlight$1(n) {
|
|
327
377
|
return (
|
|
328
|
-
|
|
329
|
-
(
|
|
378
|
+
n._statusFlags & STATUS_PENDING ||
|
|
379
|
+
(n._config & CONFIG_SLOT_NODE && GlobalQueue._slotFlight?.(n))
|
|
330
380
|
);
|
|
331
381
|
}
|
|
332
382
|
function applyGuesses(parent) {
|
|
@@ -393,24 +443,33 @@ function applyGuesses(parent) {
|
|
|
393
443
|
* through an ancestor's guess stays its own (the screen plus its own
|
|
394
444
|
* guesses). */
|
|
395
445
|
function enterLane(l, c) {
|
|
396
|
-
c._flags |= REACTIVE_LANE_READ
|
|
397
|
-
|
|
446
|
+
const f = (c._flags |= REACTIVE_LANE_READ),
|
|
447
|
+
p = passLane;
|
|
398
448
|
if (p === null) {
|
|
449
|
+
const verdict = l._parent._verdict === l;
|
|
450
|
+
// A verdict lane's value (a memo of `latest` / `isPending`, F-6) read by
|
|
451
|
+
// a derivation that already read the frame's staging or joined its
|
|
452
|
+
// hold, or by any pass a pass that did is creating: the verdict read
|
|
453
|
+
// does not break it out — it stays the frame's, as the same read inline
|
|
454
|
+
// does (verdict.ts `markVerdictReader`, `route`; D-25, D-26). An
|
|
455
|
+
// effect's own reads do not count (its `_type`, 1–3, carries neither bit).
|
|
456
|
+
if (!(verdict && creatorReads(c, c._type || f))) {
|
|
457
|
+
setPassLane(l);
|
|
458
|
+
// The pass read a held write before its first lane read (it joined the
|
|
459
|
+
// frame's future as mainline would): lane work sees the screen — the
|
|
460
|
+
// seam re-derives it on the committed world next round (`stagedReaders`,
|
|
461
|
+
// the park's repair), as it does a lane pass that read a staging. Not a
|
|
462
|
+
// pass whose held reads were all of nodes born held (no staged read,
|
|
463
|
+
// `stagedRead`): it has no committed world to re-derive on (#3969).
|
|
464
|
+
// (A pass not joined: a held write beside a guess, #4336; a leaf of a
|
|
465
|
+
// verdict lane — its plain reads are the screen's, D-26 `[s(), L()]`.)
|
|
466
|
+
if (f & REACTIVE_STAGED_READ && (guessed || verdict || f & REACTIVE_JOINED)) stagedRead(c);
|
|
467
|
+
}
|
|
468
|
+
} else if (sameLane(p, l)) setPassLane(l);
|
|
469
|
+
else if (!under(p, l)) {
|
|
470
|
+
if (!under(l, p)) linkLanes(p, l);
|
|
399
471
|
setPassLane(l);
|
|
400
|
-
// The pass read a held write before its first lane read (it joined the
|
|
401
|
-
// frame's future as mainline would): lane work sees the screen — the
|
|
402
|
-
// seam re-derives it on the committed world next round (`stagedReaders`,
|
|
403
|
-
// the park's repair), as it does a lane pass that read a staging. Not a
|
|
404
|
-
// pass whose held reads were all of nodes born held (no staged read,
|
|
405
|
-
// `stagedRead`): it has no committed world to re-derive on (#3969).
|
|
406
|
-
if (c._flags & REACTIVE_JOINED && c._flags & REACTIVE_STAGED_READ) stagedRead(c);
|
|
407
|
-
return;
|
|
408
472
|
}
|
|
409
|
-
if (sameLane(p, l)) return setPassLane(l);
|
|
410
|
-
if (under(p, l)) return;
|
|
411
|
-
if (under(l, p)) return setPassLane(l);
|
|
412
|
-
linkLanes(p, l);
|
|
413
|
-
setPassLane(l);
|
|
414
473
|
}
|
|
415
474
|
/** Is `l` nested under `a` (an ancestor lane)? */
|
|
416
475
|
function under(l, a) {
|
|
@@ -481,10 +540,21 @@ function laneStage(el, l, create, errored) {
|
|
|
481
540
|
const x = el._x;
|
|
482
541
|
if (x !== null) {
|
|
483
542
|
const tx = x._transaction;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
543
|
+
if (tx?._lane) {
|
|
544
|
+
// Cleared before equality, so a return to the committed value
|
|
545
|
+
// notifies nobody. The seam wakes it (#3892) — always for a pass
|
|
546
|
+
// outside any transaction's flush: its value is the screen's now
|
|
547
|
+
// (#4367). While a transaction's flush runs the pass and the lane's
|
|
548
|
+
// frame is still held, what the screen shows stays the screen until
|
|
549
|
+
// the landing, and `_value` takes it (as `dissolveLane` keeps a shown
|
|
550
|
+
// lane's), so this pass's answer — the frame's — is a change against
|
|
551
|
+
// what is seen and reaches the screen with the frame's commit. (Only
|
|
552
|
+
// a lane is ever `_shown`.)
|
|
553
|
+
if (tx._shown)
|
|
554
|
+
if (!flushTransaction || !blocked(resolveTx(tx._parent))) el._flags |= LEFT_SHOWN;
|
|
555
|
+
else el._value = display(el);
|
|
556
|
+
x._transaction = null;
|
|
557
|
+
}
|
|
488
558
|
x._lane = NOT_PENDING;
|
|
489
559
|
}
|
|
490
560
|
el._config &= -1050881;
|
|
@@ -502,7 +572,19 @@ function laneStage(el, l, create, errored) {
|
|
|
502
572
|
// of the lane from here (its value the lane's, its fate the lane's), no
|
|
503
573
|
// longer a written guess. A pass with no answer (pending, errored)
|
|
504
574
|
// leaves the guess.
|
|
505
|
-
|
|
575
|
+
// Nor a pass of another action's guess: the re-ask is this lane's new
|
|
576
|
+
// question, which the guess (over the old one) does not answer — the
|
|
577
|
+
// lane waits for its own answer (F-1, F-6, #4336). (A guess of the
|
|
578
|
+
// lane's own action, D-33's entangled slot, still stands in.)
|
|
579
|
+
// (Read behind `guessed`: a bundle without optimism drops the reads.)
|
|
580
|
+
if (
|
|
581
|
+
guessed &&
|
|
582
|
+
el._config & CONFIG_GUESS &&
|
|
583
|
+
el._x._transaction?._lane &&
|
|
584
|
+
resolveTx(el._x._transaction._parent) !== resolveTx(l._parent)
|
|
585
|
+
)
|
|
586
|
+
take(el, l);
|
|
587
|
+
else if (!errored) el._config &= ~CONFIG_GUESS;
|
|
506
588
|
}
|
|
507
589
|
// A leaf has no lane value to seat it (`recompute`): one left waiting on
|
|
508
590
|
// the lane's flight runs its next pass as the lane's — the landing that
|
|
@@ -535,9 +617,14 @@ function laneStage(el, l, create, errored) {
|
|
|
535
617
|
* at the parent's landing — while the display keeps what it shows until
|
|
536
618
|
* then. A confirmation is silent (A17): the truth stays staged beneath the
|
|
537
619
|
* guess for the parent's commit, the guess stays the value; only a reader
|
|
538
|
-
* of the truth (CONFIG_AUTHORITATIVE) is told its view changed.
|
|
539
|
-
|
|
540
|
-
*
|
|
620
|
+
* of the truth (CONFIG_AUTHORITATIVE) is told its view changed. Either way
|
|
621
|
+
* the parent takes this node, not the tick that carried the truth (D-33):
|
|
622
|
+
* one landing answering two actions' guesses (a store re-ask over two
|
|
623
|
+
* rows) must not tie them together; a pass that derives from the held
|
|
624
|
+
* truth joins the parent on its own (`joinPass`). */
|
|
625
|
+
/** The correction round is replacing a guess with the value it covered (no
|
|
626
|
+
* landing beneath it): observe's "reverted"; the lane gives back the
|
|
627
|
+
* guesses it took (#4336, `endLanes`). */
|
|
541
628
|
let reverting = false;
|
|
542
629
|
function supersede(n, value, changed) {
|
|
543
630
|
const l = txOf(n);
|
|
@@ -557,6 +644,7 @@ function supersede(n, value, changed) {
|
|
|
557
644
|
if (l._shown && attrHooks !== null)
|
|
558
645
|
attrHooks.optimisticReverted(n, n._x._lane, value, reverting ? "reverted" : "superseded");
|
|
559
646
|
dissolveLane(l, parent, n);
|
|
647
|
+
if (guessed && reverting) reverted.add(l);
|
|
560
648
|
// What the screen showed stays the screen until the parent lands (A18
|
|
561
649
|
// (c): display and untracked reads keep the override until the commit).
|
|
562
650
|
if (l._shown) n._value = n._x._lane;
|
|
@@ -570,7 +658,6 @@ function supersede(n, value, changed) {
|
|
|
570
658
|
GlobalQueue._confirmed?.(l);
|
|
571
659
|
}
|
|
572
660
|
n._pendingValue = value;
|
|
573
|
-
joinFuture(parent);
|
|
574
661
|
}
|
|
575
662
|
/** A lane's end, two ways. `into === null`: the parent landed — the lane's
|
|
576
663
|
* values are the truth now: a guess lands the truth staged beneath it or
|
|
@@ -593,6 +680,8 @@ function dissolveLane(l, into, except) {
|
|
|
593
680
|
l._verdict = null;
|
|
594
681
|
if (lanes.indexOf(v) !== -1) dissolveLane(v, null);
|
|
595
682
|
}
|
|
683
|
+
// First loads this dissolution leaves loading (below).
|
|
684
|
+
const loading = [];
|
|
596
685
|
for (let i = 0; i < l._nodes.length; i++) {
|
|
597
686
|
const n = l._nodes[i];
|
|
598
687
|
const x = n._x;
|
|
@@ -628,8 +717,20 @@ function dissolveLane(l, into, except) {
|
|
|
628
717
|
// lane's answer beside inputs that are the truth now: a fresh reader
|
|
629
718
|
// of its flight observes it (#3648, #3651; #3305's commit beneath a
|
|
630
719
|
// flight).
|
|
720
|
+
// Not a first load the lane produced nothing for whose own flight is
|
|
721
|
+
// up, nor one pending on such a load kept here (an identity memo of
|
|
722
|
+
// it, a derived store's derive): committing its empty `_value` would
|
|
723
|
+
// initialize it as `undefined`, which `latest()` would serve and
|
|
724
|
+
// `isPending()` call pending. It stays loading until its flight
|
|
725
|
+
// answers (D-22, A19 exc. 1; async-gen 3289 #929, 91501 #504, #156).
|
|
631
726
|
x._transaction = null;
|
|
632
|
-
if (!effect && !guess)
|
|
727
|
+
if (!effect && !guess)
|
|
728
|
+
slot === NOT_PENDING &&
|
|
729
|
+
(n._statusFlags & (STATUS_UNINITIALIZED | STATUS_PENDING)) ===
|
|
730
|
+
(STATUS_UNINITIALIZED | STATUS_PENDING) &&
|
|
731
|
+
(x._inFlight || loading.some(q => x._pendingSources?.has(q)))
|
|
732
|
+
? loading.push(n)
|
|
733
|
+
: (n._pendingValue = latest);
|
|
633
734
|
commitPendingNode(n);
|
|
634
735
|
if (!effect) {
|
|
635
736
|
if (
|
|
@@ -639,6 +740,14 @@ function dissolveLane(l, into, except) {
|
|
|
639
740
|
n._config |= CONFIG_INPUTS_PUBLISHED;
|
|
640
741
|
if (shown !== NOT_PENDING && (!n._equals || !n._equals(shown, n._value))) insertSubs(n);
|
|
641
742
|
}
|
|
743
|
+
// A stale reader's own error — its pass (a memo's or a render
|
|
744
|
+
// effect's) threw over the committed value of what the parent held
|
|
745
|
+
// (A10), served as the screen — is no answer of the world landing now:
|
|
746
|
+
// it re-derives on it (A15; D-39, the landing is the new answer). The
|
|
747
|
+
// landing's `reruns` skips an errored reader: its error is an errored
|
|
748
|
+
// source's (#3945).
|
|
749
|
+
if (n._flags & REACTIVE_FRAME_READ && n._statusFlags & STATUS_ERROR && x._error.source === n)
|
|
750
|
+
enqueueSub(n);
|
|
642
751
|
} else if (guess) {
|
|
643
752
|
// A guess of a dissolving lane: its truth — a landing held beneath it,
|
|
644
753
|
// else the value it covered — is the parent's to land, and its readers
|
|
@@ -758,14 +867,19 @@ function laneRead(c, el) {
|
|
|
758
867
|
)
|
|
759
868
|
return el._pendingValue;
|
|
760
869
|
if (l._held || pending) {
|
|
761
|
-
if (!guess && !l._shown && status & STATUS_UNINITIALIZED) throw new NotReadyError(null);
|
|
762
870
|
// Re-derived at the reveal; once per pass (the pass may also be a stale
|
|
763
871
|
// reader of a transaction, REACTIVE_FRAME_READ — both reruns apply).
|
|
872
|
+
// Before the throw below: a leaf that waits on a lane that has never
|
|
873
|
+
// shown has no flight to settle it — only the reveal re-derives it,
|
|
874
|
+
// and its pending holds its frame until then (#4338).
|
|
764
875
|
if (!(c._flags & REACTIVE_SCREEN_READ)) {
|
|
765
876
|
c._flags |= REACTIVE_FRAME_READ | REACTIVE_SCREEN_READ;
|
|
766
877
|
l._reruns.push(c);
|
|
767
878
|
}
|
|
768
|
-
|
|
879
|
+
// Nothing on screen yet (uninitialized: `display` is NOT_PENDING).
|
|
880
|
+
const v = display(el);
|
|
881
|
+
if (v === NOT_PENDING) throw new NotReadyError(null);
|
|
882
|
+
return v;
|
|
769
883
|
}
|
|
770
884
|
// A verdict reader reads ahead of the seam's verdict: re-derived on the
|
|
771
885
|
// screen if the lane holds (#4284).
|
|
@@ -837,7 +951,7 @@ function laneSeam(l, leaked) {
|
|
|
837
951
|
}
|
|
838
952
|
l._held = false;
|
|
839
953
|
l._shown = true;
|
|
840
|
-
reruns(l);
|
|
954
|
+
if (reruns(l)) revealing.push(resolveTx(l._parent));
|
|
841
955
|
for (let i = 0; i < l._nodes.length; i++) {
|
|
842
956
|
const n = l._nodes[i];
|
|
843
957
|
if (n._flags & LEFT_SHOWN) {
|
|
@@ -861,6 +975,31 @@ function laneSeam(l, leaked) {
|
|
|
861
975
|
/** The parent landed: its lanes end, nested first. (None is blocked: a
|
|
862
976
|
* blocked lane blocks its parent.) */
|
|
863
977
|
function endLanes(u) {
|
|
978
|
+
// A revert landing (#4336): the guesses its lane took from another action
|
|
979
|
+
// that is still out come back in this frame, over the truth it lands —
|
|
980
|
+
// its readers re-derive with the landing (one frame, no bare truth
|
|
981
|
+
// between). Taken by a later lane since, a node stays that lane's.
|
|
982
|
+
if (guessed)
|
|
983
|
+
for (let i = kept.length - 5; i >= 0; i -= 5)
|
|
984
|
+
if (resolveTx(kept[i + 4]._parent) === u) {
|
|
985
|
+
let [n, v, g, q, t] = kept.splice(i, 5);
|
|
986
|
+
// (A store slot's node now: the one taken, or a read's since.)
|
|
987
|
+
if (
|
|
988
|
+
reverted.has(t) &&
|
|
989
|
+
lanes.includes(g) &&
|
|
990
|
+
(!(n._config & CONFIG_SLOT_NODE) || (n = GlobalQueue._slotTake(n, true)))
|
|
991
|
+
) {
|
|
992
|
+
const x = ext(n);
|
|
993
|
+
if (x._transaction === null || txOf(n) === u) {
|
|
994
|
+
commitPendingNode(n);
|
|
995
|
+
x._lane = v;
|
|
996
|
+
x._q = q;
|
|
997
|
+
n._config |= CONFIG_OVERRIDE | CONFIG_GUESS;
|
|
998
|
+
list(n, g);
|
|
999
|
+
rerun(n, u);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
864
1003
|
for (let k = lanes.length - 1; k >= 0; k--) {
|
|
865
1004
|
const l = lanes[k];
|
|
866
1005
|
if (resolveTx(l._parent) !== u) continue;
|
|
@@ -876,8 +1015,14 @@ function endLanes(u) {
|
|
|
876
1015
|
function lanesBlocked(t) {
|
|
877
1016
|
return (t._lane && linkBlocked(t)) || nestedBlocked(t);
|
|
878
1017
|
}
|
|
1018
|
+
/** Parents whose lane revealed this seam with readers to re-derive there
|
|
1019
|
+
* (`laneSeam`): the lane shows ahead of its parent (F-6), so the parent lands
|
|
1020
|
+
* a round later — after those readers have shown the lane's answer — even
|
|
1021
|
+
* when nothing else holds it (#4393). */
|
|
1022
|
+
const revealing = [];
|
|
879
1023
|
/** The lanes under `t` (its own; a link group's are each other's). */
|
|
880
1024
|
function nestedBlocked(t) {
|
|
1025
|
+
if (revealing.indexOf(t) !== -1) return true;
|
|
881
1026
|
for (let i = 0; i < lanes.length; i++) {
|
|
882
1027
|
const l = lanes[i];
|
|
883
1028
|
if (resolveTx(l._parent) !== t) continue;
|
|
@@ -891,7 +1036,9 @@ function nestedBlocked(t) {
|
|
|
891
1036
|
* latest: its staging once the lane has shown (promoted at the next
|
|
892
1037
|
* reveal), its slot before; the lane's members re-derive as its work. */
|
|
893
1038
|
function laneWrite(el, v) {
|
|
894
|
-
|
|
1039
|
+
// (A store slot's guess its derive's re-ask in another action's lane
|
|
1040
|
+
// writes is that lane's derivation from here — store `_slotTake`, #4336.)
|
|
1041
|
+
if (el._config & CONFIG_GUESS && !(guessed && GlobalQueue._slotTake?.(el))) {
|
|
895
1042
|
// Provenance (A18, #3331; Q-D, plan sec. 39): a write asking an OLDER
|
|
896
1043
|
// question than the guess's — another action's landing beneath it —
|
|
897
1044
|
// is not its answer: held beneath (the truth for the commit; the
|
|
@@ -969,29 +1116,79 @@ const applyGuessesAt = parent => {
|
|
|
969
1116
|
judged = lanes.length;
|
|
970
1117
|
if (pendingGuesses.length !== 0) applyGuesses(parent);
|
|
971
1118
|
};
|
|
972
|
-
|
|
1119
|
+
/** `t` is parked by `r`'s pass alone: each node keeping it — `t`'s own, or
|
|
1120
|
+
* a lane's under it — is `r` itself (its flight, in the verdict lane) or a
|
|
1121
|
+
* render effect pending on `r` only: the frame observing the lane work that
|
|
1122
|
+
* read its staging. Anything else (an open action, a guess's flight,
|
|
1123
|
+
* another flight) holds it on its own. */
|
|
1124
|
+
function heldBy(t, r) {
|
|
1125
|
+
if (t._open !== 0 || (t._lane && linkBlocked(t))) return false;
|
|
1126
|
+
for (let k = -1, u = t; u; u = lanes[++k])
|
|
1127
|
+
if (u === t || resolveTx(u._parent) === t) {
|
|
1128
|
+
if (u !== t && guessFlights(u)) return false;
|
|
1129
|
+
for (let i = 0; i < u._nodes.length; i++) {
|
|
1130
|
+
const n = u._nodes[i];
|
|
1131
|
+
if (
|
|
1132
|
+
n === r ||
|
|
1133
|
+
!(n._statusFlags & STATUS_PENDING) ||
|
|
1134
|
+
(n._x._transaction !== null && txOf(n) !== u)
|
|
1135
|
+
)
|
|
1136
|
+
continue;
|
|
1137
|
+
const s = n._x._pendingSources;
|
|
1138
|
+
if (n._type !== EFFECT_RENDER || s?.size !== 1 || !s.has(r)) return false;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return true;
|
|
1142
|
+
}
|
|
1143
|
+
const laneSeams = t => {
|
|
973
1144
|
// Passes that read a write the parked frame holds as the screen
|
|
974
1145
|
// (`stagedReaders`: lane work, a verdict reader before the verdict): they
|
|
975
1146
|
// re-derive on the committed world next round (and register as stale
|
|
976
1147
|
// readers of the transaction there); a lane's runs wait that round — a
|
|
977
1148
|
// lane sees the screen plus its own guesses.
|
|
1149
|
+
if (revealing.length) revealing.length = 0;
|
|
978
1150
|
const leaked = [];
|
|
979
|
-
if (
|
|
980
|
-
for (let i = 0; i <
|
|
981
|
-
const r =
|
|
982
|
-
|
|
1151
|
+
if (t !== null)
|
|
1152
|
+
for (let i = 0; i < stagedReaders.length; i++) {
|
|
1153
|
+
const r = stagedReaders[i],
|
|
1154
|
+
// A pass no transaction holds that read the frame's staging (an
|
|
1155
|
+
// effect, a `[s(), latest(s)]` reader watched before the frame had
|
|
1156
|
+
// a transaction): judged by the frame's — it re-derives only if the
|
|
1157
|
+
// frame stays parked; landing here, what it read is the screen (one
|
|
1158
|
+
// run, D-22).
|
|
1159
|
+
l = r._x?._transaction || (r._flags & REACTIVE_STAGED_READ ? t : null);
|
|
983
1160
|
// A verdict reader a later read of the same pass routed into a verdict
|
|
984
1161
|
// lane answered for itself: it re-derives at the holder's landing, not
|
|
985
1162
|
// now (one run — #3322, #3540). Other verdict-lane work re-derives only
|
|
986
1163
|
// if the frame stays parked: a transaction landing at this seam makes
|
|
987
|
-
// what it read the screen (one run).
|
|
1164
|
+
// what it read the screen (one run). The frame is the transaction that
|
|
1165
|
+
// holds what it read (`t`, this flush's — not always the lane's parent:
|
|
1166
|
+
// a landing that reached a frame held elsewhere joined that one). Nor
|
|
1167
|
+
// when only this pass keeps it parked (`heldBy`): the frame lands with
|
|
1168
|
+
// the pass's answer, so what the pass read is the screen when anything
|
|
1169
|
+
// shows it — re-derived on the committed world, it would ask a question
|
|
1170
|
+
// already asked, over inputs the landing replaces. A reader a
|
|
1171
|
+
// transaction holds that read none of its stagings (a verdict reader
|
|
1172
|
+
// that probed only a flight, held as the frame's pending observer or
|
|
1173
|
+
// its staged derivation) has nothing to re-derive: its re-run
|
|
1174
|
+
// re-enters that transaction and parks it again, every round — it
|
|
1175
|
+
// runs at the landing (#4349). One that read a staging re-derives only
|
|
1176
|
+
// if its frame stays parked: landing at this seam, what it read is the
|
|
1177
|
+
// screen (one run, D-22). Not one born into that frame (a held pass's
|
|
1178
|
+
// child, ruling A): it has no screen, its reads are the transaction's
|
|
1179
|
+
// either way — it runs again at the landing, not now.
|
|
988
1180
|
if (
|
|
989
1181
|
r._flags & REACTIVE_DISPOSED ||
|
|
990
|
-
(l
|
|
991
|
-
l._parent?._verdict === l &&
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1182
|
+
(l?._lane
|
|
1183
|
+
? l._parent?._verdict === l &&
|
|
1184
|
+
(r._config & CONFIG_VERDICT
|
|
1185
|
+
? !(r._flags & REACTIVE_PROBE_UNANSWERED)
|
|
1186
|
+
: !blocked(t) || heldBy(t, r))
|
|
1187
|
+
: l &&
|
|
1188
|
+
!(
|
|
1189
|
+
r._flags & REACTIVE_PROBE_UNANSWERED ||
|
|
1190
|
+
(r._flags & REACTIVE_STAGED_READ && blocked(l) && !bornInto(r, l))
|
|
1191
|
+
))
|
|
995
1192
|
)
|
|
996
1193
|
continue;
|
|
997
1194
|
// A leaked effect re-derives seated in its lane from its first read: it
|
|
@@ -1000,12 +1197,48 @@ const laneSeams = leaks => {
|
|
|
1000
1197
|
// the staging, then re-enters the lane at its guess read and leaks
|
|
1001
1198
|
// again — every round (#4014). A computed is already seated by its
|
|
1002
1199
|
// override, and a written guess's own pass stays the frame's.
|
|
1003
|
-
if (l
|
|
1200
|
+
if (l?._lane && r._type) r._flags |= REACTIVE_LANE_DIRTY;
|
|
1201
|
+
// R5 for an async reader: its frame pass asked the frame's question
|
|
1202
|
+
// (F-6's lane gets its own pass now). That flight is the frame's
|
|
1203
|
+
// answer: the landing adopts it rather than asking it again (F-9: a
|
|
1204
|
+
// re-ask of the same question is no new question; `reruns`). A frame
|
|
1205
|
+
// landing at this seam has nothing to re-derive: the pass was the
|
|
1206
|
+
// landing's, its flight the screen's answer. Sync or async, the
|
|
1207
|
+
// reader's lane pass is still to come: the verdict lane it joins does
|
|
1208
|
+
// not reveal this seam (F-6: the lane's readers show its answer
|
|
1209
|
+
// together, D-23: atomic with the value) — revealed now, it showed a
|
|
1210
|
+
// verdict beside the frame pass's value, before the pass that answers
|
|
1211
|
+
// for the lane, and a reader of the re-derived memo read it outside
|
|
1212
|
+
// the shown lane and waited for the frame (#4393).
|
|
1213
|
+
else if (!l?._lane && r._flags & REACTIVE_PROBE_UNANSWERED) {
|
|
1214
|
+
if (isThenable(r._x?._inFlight)) {
|
|
1215
|
+
if (!blocked(t)) continue;
|
|
1216
|
+
r._x._parked = r._x._inFlight;
|
|
1217
|
+
} else if (blocked(t))
|
|
1218
|
+
// A sync reader's lane pass re-derives the async memos over it (a
|
|
1219
|
+
// memo over a verdict memo is a verdict reader, D-25 #4050; D-22:
|
|
1220
|
+
// `latest()` extracted into a memo reads as inline). The flight
|
|
1221
|
+
// each asked over its frame staging, up and held by `t`, is the
|
|
1222
|
+
// frame's answer too: the landing adopts it (F-9).
|
|
1223
|
+
for (let s = r._subs, n; s; s = s._nextSub)
|
|
1224
|
+
if (
|
|
1225
|
+
isThenable((n = s._sub)._x?._inFlight) &&
|
|
1226
|
+
n._x._pendingSources?.has(n) &&
|
|
1227
|
+
n._config & CONFIG_HELD &&
|
|
1228
|
+
txOf(n) === t
|
|
1229
|
+
)
|
|
1230
|
+
n._x._parked = n._x._inFlight;
|
|
1231
|
+
if (t?._verdict && leaked.indexOf(t._verdict) === -1) leaked.push(t._verdict);
|
|
1232
|
+
}
|
|
1004
1233
|
enqueueSub(r);
|
|
1005
|
-
if (l
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1234
|
+
if (l?._lane && leaked.indexOf(l) === -1) leaked.push(l);
|
|
1235
|
+
}
|
|
1236
|
+
// A verdict lane born at this seam is judged with it: it exists only once
|
|
1237
|
+
// a pass routed into it — the park seam's zombie verdict readers re-run
|
|
1238
|
+
// as its work so that this seam reveals them (#3444).
|
|
1239
|
+
for (let k = lanes.length - 1; k >= 0; k--)
|
|
1240
|
+
if (k < judged || lanes[k]._parent?._verdict === lanes[k])
|
|
1241
|
+
laneSeam(lanes[k], leaked.indexOf(lanes[k]) !== -1);
|
|
1009
1242
|
// A verdict reader that entered a lane this seam held: its run waits with
|
|
1010
1243
|
// the lane, and the verdict beside it would freeze — it re-derives next
|
|
1011
1244
|
// round from the frame's seat, a stale reader of the lane (#4284).
|
|
@@ -1015,6 +1248,36 @@ const laneSeams = leaks => {
|
|
|
1015
1248
|
enqueueSub(r);
|
|
1016
1249
|
}
|
|
1017
1250
|
};
|
|
1251
|
+
/** The landing's re-derive of a reader whose frame flight the seam parked
|
|
1252
|
+
* (R5): the flight asked the question the landing would — adopted, it is
|
|
1253
|
+
* asked once (F-9). Not over a pass already queued: that one asks anew.
|
|
1254
|
+
* Registered as if this pass had returned it — the node pending on it, its
|
|
1255
|
+
* landing the answer — through a promise of its own: the frame pass's
|
|
1256
|
+
* registration still listens to the flight, and must not land it a second
|
|
1257
|
+
* time. */
|
|
1258
|
+
const adoptParked = r => {
|
|
1259
|
+
const parked = r._x?._parked;
|
|
1260
|
+
if (!parked || r._flags & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP)) return false;
|
|
1261
|
+
r._x._parked = undefined;
|
|
1262
|
+
releaseFlightTeardown(r);
|
|
1263
|
+
try {
|
|
1264
|
+
handleAsync(r, parked.then());
|
|
1265
|
+
} catch {}
|
|
1266
|
+
r._x._blocked = true;
|
|
1267
|
+
notifyStatus(r, STATUS_PENDING, new NotReadyError(r));
|
|
1268
|
+
return true;
|
|
1269
|
+
};
|
|
1270
|
+
/** A15: lane work or a verdict reader was served `el`'s committed value
|
|
1271
|
+
* (core.ts `frameRead`), so its pass does not wait on `el`'s flight: what
|
|
1272
|
+
* `el`'s pending propagated onto it before the pass (its origins, `el`
|
|
1273
|
+
* included) is not this pass's — the landing re-derives it (`reruns`).
|
|
1274
|
+
* Kept, a pass that went pending on another source never woke when that
|
|
1275
|
+
* source settled (#4397). Only a reader something made pending has anything
|
|
1276
|
+
* to drop. */
|
|
1277
|
+
const laneServed = (c, el) => {
|
|
1278
|
+
const from = el._x?._pendingSources;
|
|
1279
|
+
if (from && c._x?._pendingSources) for (const s of from) removePendingSource(c, s);
|
|
1280
|
+
};
|
|
1018
1281
|
/** Render effects that read a verdict and entered a lane before its seam (#4284). */
|
|
1019
1282
|
const entered = [];
|
|
1020
1283
|
installLaneHooks(
|
|
@@ -1026,7 +1289,9 @@ installLaneHooks(
|
|
|
1026
1289
|
laneSeams,
|
|
1027
1290
|
() => lanes.length !== 0 && laneCorrections(),
|
|
1028
1291
|
endLanes,
|
|
1029
|
-
lanesBlocked
|
|
1292
|
+
lanesBlocked,
|
|
1293
|
+
adoptParked,
|
|
1294
|
+
laneServed
|
|
1030
1295
|
);
|
|
1031
1296
|
GlobalQueue._laneGuesses = t => {
|
|
1032
1297
|
const out = [];
|
|
@@ -1067,7 +1332,7 @@ function watchVerdict(c, unanswered = false) {
|
|
|
1067
1332
|
* does not rescue it: its result shows at the landing, re-derived). A lane
|
|
1068
1333
|
* read, for `recompute`'s tail (REACTIVE_LANE_READ). */
|
|
1069
1334
|
function route(c, t) {
|
|
1070
|
-
if (passLane
|
|
1335
|
+
if (!passLane && !(c._flags & (REACTIVE_JOINED | REACTIVE_STAGED_READ)))
|
|
1071
1336
|
setPassLane(verdictLane(t));
|
|
1072
1337
|
c._flags |= REACTIVE_LANE_READ;
|
|
1073
1338
|
}
|
|
@@ -1134,40 +1399,41 @@ function verdictRead(c, t, plain) {
|
|
|
1134
1399
|
* watches the seam and runs again if the flush parks (a plain sync write
|
|
1135
1400
|
* never glitches it true).
|
|
1136
1401
|
*
|
|
1137
|
-
* `latest` has its answer either way — the proposal —
|
|
1138
|
-
* membership
|
|
1139
|
-
* (
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1145
|
-
*
|
|
1402
|
+
* `latest` has its answer either way — the proposal — but its reader's
|
|
1403
|
+
* membership is the seam's too: the frame opens no transaction for it
|
|
1404
|
+
* (#4365 — one opened for the read alone made a plain read after the window
|
|
1405
|
+
* take it for a hold, `stagedScreen`, and serve the screen beside the
|
|
1406
|
+
* staging in a frame nothing held). The reader watches the seam like a
|
|
1407
|
+
* probe before anything joined: its pass is the frame's, and runs again in
|
|
1408
|
+
* the verdict lane if the flush parks. Once the flush has joined a
|
|
1409
|
+
* transaction, the reader is its verdict lane's at once; a pass that also
|
|
1410
|
+
* read the frame's own stagings plainly (REACTIVE_STAGED_READ) stays the
|
|
1411
|
+
* frame's (`route` does not move it). */
|
|
1146
1412
|
function provisionalVerdict(c, notFinal) {
|
|
1147
|
-
if (
|
|
1148
|
-
|
|
1413
|
+
if (flushTransaction) {
|
|
1414
|
+
if (probing && notFinal) probeFound = true;
|
|
1149
1415
|
verdictRead(c, flushTransaction);
|
|
1150
|
-
|
|
1151
|
-
}
|
|
1152
|
-
if (flushTransaction !== null) {
|
|
1153
|
-
if (notFinal) probeFound = true;
|
|
1154
|
-
verdictRead(c, flushTransaction);
|
|
1155
|
-
if (c._flags & REACTIVE_STAGED_READ) watchVerdict(c);
|
|
1416
|
+
if (probing && c._flags & REACTIVE_STAGED_READ) watchVerdict(c);
|
|
1156
1417
|
return;
|
|
1157
1418
|
}
|
|
1158
1419
|
// Before anything joined, a staging that would not be final held has no
|
|
1159
1420
|
// answer yet: whatever the seam decides, the pass runs again — a later
|
|
1160
1421
|
// plain read of the same pass may route it into the verdict lane
|
|
1161
1422
|
// (`observeFlight`), which the seam must not take as this probe's answer.
|
|
1162
|
-
|
|
1423
|
+
// `latest` opens no transaction for its answer either (#4365): the plain
|
|
1424
|
+
// read of a staging after it took one opened for it for a hold and paired
|
|
1425
|
+
// the staging with the screen, in a frame nothing held. Its pass is the
|
|
1426
|
+
// frame's — whole — and re-derives once if the flush parks (unanswered:
|
|
1427
|
+
// its membership is the seam's, not the transaction it joins).
|
|
1428
|
+
watchVerdict(c, !probing || notFinal);
|
|
1163
1429
|
}
|
|
1164
1430
|
/** A19 exc. 2 / A24: a flight that re-asks the question already answered
|
|
1165
1431
|
* (a `refresh()`, a poll) is quiet; a node pending through others is quiet
|
|
1166
1432
|
* when every flight it waits on is. */
|
|
1167
1433
|
function quietPending(el) {
|
|
1168
1434
|
const x = el._x;
|
|
1169
|
-
if (x
|
|
1170
|
-
if (x._pendingSources
|
|
1435
|
+
if (!x) return false;
|
|
1436
|
+
if (x._pendingSources) {
|
|
1171
1437
|
for (const source of x._pendingSources) if (!source._x?._reask) return false;
|
|
1172
1438
|
return true;
|
|
1173
1439
|
}
|
|
@@ -1199,9 +1465,17 @@ function heldNotFinal(owner) {
|
|
|
1199
1465
|
function pendingVerdict(c, el) {
|
|
1200
1466
|
if (el._statusFlags & STATUS_UNINITIALIZED) return;
|
|
1201
1467
|
if (probing && !quietPending(el)) probeFound = true;
|
|
1202
|
-
if (c
|
|
1468
|
+
if (c && tracking) {
|
|
1203
1469
|
if (el._config & CONFIG_HELD) verdictRead(c, txOf(el));
|
|
1204
|
-
else if (globalQueue._running)
|
|
1470
|
+
else if (globalQueue._running) {
|
|
1471
|
+
// `latest` of a flight serves the committed value, the frame's and the
|
|
1472
|
+
// lane's alike (D-22): nothing is the seam's, so the reader is the
|
|
1473
|
+
// verdict lane's now, as a plain read of a flight makes it
|
|
1474
|
+
// (`observeFlight`). Its frame pass re-derived in the lane (R5) asked
|
|
1475
|
+
// its question again (F-9, #4397). A staging's is the seam's (#4365).
|
|
1476
|
+
if (!probing) joinFuture(null);
|
|
1477
|
+
provisionalVerdict(c, false); // decided above
|
|
1478
|
+
}
|
|
1205
1479
|
}
|
|
1206
1480
|
}
|
|
1207
1481
|
/** The pass entering a verdict window is a verdict reader (CONFIG_VERDICT):
|
|
@@ -1210,11 +1484,21 @@ function pendingVerdict(c, el) {
|
|
|
1210
1484
|
* a router's link claim probing inside the computation that creates the
|
|
1211
1485
|
* anchor). A throw still reaches the pass through `untrack`. */
|
|
1212
1486
|
function markVerdictReader(window) {
|
|
1213
|
-
|
|
1214
|
-
if (c
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1487
|
+
const c = creatorPass(context);
|
|
1488
|
+
if (c && tracking) {
|
|
1489
|
+
// A pass run inside passes still running — its creator's, theirs — one
|
|
1490
|
+
// of which read the future or a staging (a branch a held or staged
|
|
1491
|
+
// condition opened, and whatever that branch creates) has no screen: the
|
|
1492
|
+
// branch exists only in that world. It reads that world as any
|
|
1493
|
+
// derivation of it does (`bornInto` once held) and stays its pass
|
|
1494
|
+
// (`route`); a verdict reader from its next pass. #4341: a memo born in
|
|
1495
|
+
// a newly opened <Match> read the screen, where its branch was closed.
|
|
1496
|
+
// Not such a pass: a verdict reader now.
|
|
1497
|
+
c._config &= ~CONFIG_ASYNC;
|
|
1498
|
+
c._flags |=
|
|
1499
|
+
REACTIVE_PROBED |
|
|
1500
|
+
REACTIVE_WINDOW |
|
|
1501
|
+
(creatorReads(c, 0) || ((c._config |= CONFIG_VERDICT), 0));
|
|
1218
1502
|
// Observe tier: which windows this pass entered (1 `isPending`, 2
|
|
1219
1503
|
// `latest`) — the hold census names the affordance (attribution.ts).
|
|
1220
1504
|
c._devWindows = (c._devWindows ?? 0) | window;
|
|
@@ -1305,7 +1589,7 @@ function staged(el) {
|
|
|
1305
1589
|
function heldLatest(el, c) {
|
|
1306
1590
|
const u = unflushedValue(el);
|
|
1307
1591
|
if (u === NOT_PENDING) return staged(el);
|
|
1308
|
-
if (c
|
|
1592
|
+
if (c) markLateLinker(c);
|
|
1309
1593
|
return u;
|
|
1310
1594
|
}
|
|
1311
1595
|
/** The verdict windows' read — `latest(fn)` and `isPending(fn)` — in one
|
|
@@ -1318,7 +1602,9 @@ function heldLatest(el, c) {
|
|
|
1318
1602
|
* differs (A24, A18 provenance). A derivation: `latest` sees the lane's
|
|
1319
1603
|
* latest (a landing a blocked lane holds has answered its question — the
|
|
1320
1604
|
* checkout's shipping text updates independently of the tax in flight),
|
|
1321
|
-
* `isPending` the screen; not final while its own flight is up
|
|
1605
|
+
* `isPending` the screen; not final while its own flight is up. A
|
|
1606
|
+
* derivation's first load in flight (nothing produced yet — an async memo
|
|
1607
|
+
* over a verdict memo, F-6) reads as the flight below: loading (D-22);
|
|
1322
1608
|
* - a held node: the reader breaks out as the holder's verdict lane's and
|
|
1323
1609
|
* re-derives at the landing; `latest` serves the proposal (`heldLatest`),
|
|
1324
1610
|
* `isPending` answers `heldNotFinal`;
|
|
@@ -1331,49 +1617,70 @@ function heldLatest(el, c) {
|
|
|
1331
1617
|
* - an errored derivation throws for every reader (#2897). */
|
|
1332
1618
|
function verdictValue(el, c) {
|
|
1333
1619
|
const owner = el;
|
|
1334
|
-
const owned = c
|
|
1620
|
+
const owned = !!c && !(c._config & CONFIG_CHILDREN_FORBIDDEN);
|
|
1335
1621
|
const tracked = owned && tracking;
|
|
1336
|
-
|
|
1622
|
+
// A31: the pulled memo's pass is its own, not the probe's — it reads
|
|
1623
|
+
// outside the window, and the probe's read below answers for what the pass
|
|
1624
|
+
// produced. So is a lazy or dormant memo's wake (an unowned memo with no
|
|
1625
|
+
// reader left, #4366): woken inside the window, it read its sources as the
|
|
1626
|
+
// probe does, the committed value under a landing. (A signal has no
|
|
1627
|
+
// `_flags`: the wake is a no-op for it.)
|
|
1628
|
+
setVerdict(null);
|
|
1629
|
+
prepareComputed(owner);
|
|
1630
|
+
if (c && tracking) {
|
|
1337
1631
|
link(el, c);
|
|
1338
|
-
if (
|
|
1339
|
-
// A31: the pulled memo's pass is its own, not the probe's — it reads
|
|
1340
|
-
// outside the window, and the probe's read below answers for what
|
|
1341
|
-
// the pass produced.
|
|
1342
|
-
setVerdict(null);
|
|
1343
|
-
pullComputed(owner, c);
|
|
1344
|
-
setVerdict(verdictValue);
|
|
1345
|
-
}
|
|
1632
|
+
if (el._fn) pullComputed(owner, c);
|
|
1346
1633
|
}
|
|
1634
|
+
setVerdict(verdictValue);
|
|
1347
1635
|
// A live `affects()` mark on the node or a dependency (affects.ts): not
|
|
1348
1636
|
// final by declaration — the value itself is read as below. The verdict is
|
|
1349
1637
|
// display-ahead like any: in a transaction's flush (the declaring action's
|
|
1350
1638
|
// body) the reader is its verdict lane's, not a staging the frame parks.
|
|
1351
|
-
if (probing && GlobalQueue._marked
|
|
1639
|
+
if (probing && GlobalQueue._marked?.(owner)) {
|
|
1352
1640
|
probeFound = true;
|
|
1353
|
-
if (tracked && flushTransaction
|
|
1641
|
+
if (tracked && flushTransaction) verdictRead(c, flushTransaction);
|
|
1354
1642
|
}
|
|
1355
1643
|
if (
|
|
1356
1644
|
el._config & CONFIG_OVERRIDE &&
|
|
1357
1645
|
!(owner._statusFlags & STATUS_ERROR && !(el._config & CONFIG_GUESS))
|
|
1358
1646
|
) {
|
|
1359
|
-
const l = txOf(el)
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1647
|
+
const l = txOf(el),
|
|
1648
|
+
flying = (owner._statusFlags & STATUS_PENDING) !== 0 && !quietPending(owner),
|
|
1649
|
+
guess = el._config & CONFIG_GUESS,
|
|
1650
|
+
// NOT_PENDING: a node born in the lane with nothing in it yet has no
|
|
1651
|
+
// value in either view — loading, not pending, and no verdict to read
|
|
1652
|
+
// ahead: it is read as any uninitialized node, NotReady below (A7, A19
|
|
1653
|
+
// exc. 1; #4363), its reader not moved into the verdict lane.
|
|
1654
|
+
v = guess ? 0 : latestActive && !flying ? laneValueOf(el) : display(el);
|
|
1655
|
+
if (v !== NOT_PENDING) {
|
|
1656
|
+
if (tracked) verdictRead(c, l._parent ?? l);
|
|
1657
|
+
if (guess) {
|
|
1658
|
+
if (
|
|
1659
|
+
probing &&
|
|
1660
|
+
(flying ||
|
|
1661
|
+
(el._pendingValue !== NOT_PENDING &&
|
|
1662
|
+
(!el._equals || !el._equals(el._x._lane, el._pendingValue))))
|
|
1663
|
+
)
|
|
1664
|
+
probeFound = true;
|
|
1665
|
+
// A lane a correction folded under (D-34): `latest()` shows the hold's
|
|
1666
|
+
// world whole — the truth beneath each guess, else what it covered.
|
|
1667
|
+
if (latestActive && l.folded) return l.folded(null, el);
|
|
1668
|
+
return el._x._lane;
|
|
1669
|
+
}
|
|
1670
|
+
// A first load in flight with nothing produced yet: loading, for
|
|
1671
|
+
// `latest` (D-22) as for `isPending` (A19 exc. 1) — unless an error
|
|
1672
|
+
// answered it first: its re-ask is pending, the error its display
|
|
1673
|
+
// (D-41, #4375).
|
|
1363
1674
|
if (
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1675
|
+
flying &&
|
|
1676
|
+
v === el._value &&
|
|
1677
|
+
owner._statusFlags & STATUS_UNINITIALIZED &&
|
|
1678
|
+
(latestActive || !owner._x._shownError)
|
|
1368
1679
|
)
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
if (latestActive && l.folded) return l.folded(null, el);
|
|
1373
|
-
return el._x._lane;
|
|
1680
|
+
throw owner._x._error;
|
|
1681
|
+
if (probing && flying) probeFound = true;
|
|
1682
|
+
return v;
|
|
1374
1683
|
}
|
|
1375
|
-
if (probing && flying) probeFound = true;
|
|
1376
|
-
return latestActive && !flying ? laneValueOf(el) : display(el);
|
|
1377
1684
|
}
|
|
1378
1685
|
// Dev strict-read scopes (a component body, an effect callback) warn on a
|
|
1379
1686
|
// verdict read as on any untracked read; the pending throw they add for a
|
|
@@ -1398,9 +1705,8 @@ function verdictValue(el, c) {
|
|
|
1398
1705
|
tracked &&
|
|
1399
1706
|
!probing &&
|
|
1400
1707
|
c._type === EFFECT_RENDER &&
|
|
1401
|
-
passLane
|
|
1402
|
-
t._verdict
|
|
1403
|
-
t._verdict._held &&
|
|
1708
|
+
!passLane &&
|
|
1709
|
+
t._verdict?._held &&
|
|
1404
1710
|
flushTransaction !== t
|
|
1405
1711
|
) {
|
|
1406
1712
|
staleReader(c, t._verdict);
|
|
@@ -1412,7 +1718,7 @@ function verdictValue(el, c) {
|
|
|
1412
1718
|
// An action body reading another transaction's proposal derives
|
|
1413
1719
|
// from it: the two settle as one (A15; posture C, 2026-09-15). An
|
|
1414
1720
|
// untracked read elsewhere has no transaction to entangle.
|
|
1415
|
-
if (!tracked && flushTransaction
|
|
1721
|
+
if (!tracked && flushTransaction && !globalQueue._running) joinFuture(txOf(el));
|
|
1416
1722
|
return heldLatest(el, tracked ? c : null);
|
|
1417
1723
|
}
|
|
1418
1724
|
return el._value;
|
|
@@ -1441,7 +1747,7 @@ function verdictValue(el, c) {
|
|
|
1441
1747
|
// reader derived from the flushed world runs again in the carrying
|
|
1442
1748
|
// flush.
|
|
1443
1749
|
if (globalQueue._running) return staged(el);
|
|
1444
|
-
if (c
|
|
1750
|
+
if (c) markLateLinker(c);
|
|
1445
1751
|
}
|
|
1446
1752
|
return el._value;
|
|
1447
1753
|
}
|
|
@@ -1453,16 +1759,51 @@ function verdictValue(el, c) {
|
|
|
1453
1759
|
// As `read()`: an untracked read of a pending node still re-runs its
|
|
1454
1760
|
// reader when the node settles (a projection's pull reads its firewall
|
|
1455
1761
|
// untracked, store/store.ts).
|
|
1456
|
-
if (c
|
|
1762
|
+
if (c && !tracking && !spectating && el !== c) link(el, c);
|
|
1457
1763
|
throw owner._x?._error;
|
|
1458
1764
|
}
|
|
1459
|
-
if (
|
|
1765
|
+
if (el._fn && owner._statusFlags & STATUS_ERROR) throw owner._x._error;
|
|
1460
1766
|
return el._value;
|
|
1461
1767
|
}
|
|
1768
|
+
/** D-25: a node whose fetch a hold is waiting on is that hold's work, not a
|
|
1769
|
+
* verdict reader — a pass that re-runs it reads plainly — and a memo's
|
|
1770
|
+
* posture is what it reads: one that reads a verdict reader's value is a
|
|
1771
|
+
* verdict reader itself (#4050). An async memo with no verdict window of its
|
|
1772
|
+
* own (REACTIVE_WINDOW: it read a verdict memo, it called neither `latest`
|
|
1773
|
+
* nor `isPending`) is a verdict reader between its flights, and the hold's
|
|
1774
|
+
* work while one is up or its hold re-runs it.
|
|
1775
|
+
*
|
|
1776
|
+
* At a pass's head (core.ts `recompute`): a pending node's pass — its
|
|
1777
|
+
* flight is the transition's own work (#3884) — and a held one's re-run of
|
|
1778
|
+
* such a memo read plainly: the write that re-ran it is its hold's, and
|
|
1779
|
+
* served the screen it re-asked a question it had answered (A10, #4397).
|
|
1780
|
+
* Only a memo that never asked: D-25's "a verdict posture outlives the pass
|
|
1781
|
+
* that asked" is a pass that called `latest`/`isPending`, which clears
|
|
1782
|
+
* CONFIG_ASYNC and stays a verdict reader whoever dirties it.
|
|
1783
|
+
*
|
|
1784
|
+
* On `source` going pending (`propagateStatus`): such a memo waits on it
|
|
1785
|
+
* like any derivation (F-9) instead of re-deriving — what it read changed
|
|
1786
|
+
* no value, so its re-run re-asked the question it was answering. Unless
|
|
1787
|
+
* its own flight is up outside a lane on a new question of the source's:
|
|
1788
|
+
* its re-run supersedes a flight asked of a value the source has moved past
|
|
1789
|
+
* (re-asked by a `refresh()`, the source's question is the same one). */
|
|
1790
|
+
function holdWork(n, source) {
|
|
1791
|
+
// CONFIG_ASYNC records an async memo with no window of its own: its last
|
|
1792
|
+
// answering pass registered a flight (`handleAsync`) and called neither
|
|
1793
|
+
// `latest` nor `isPending`. A window (`markVerdictReader`), a sync answer or
|
|
1794
|
+
// an error clears it; a pass left pending keeps the last answer's.
|
|
1795
|
+
const own = n._config & CONFIG_ASYNC;
|
|
1796
|
+
return (
|
|
1797
|
+
own &&
|
|
1798
|
+
(!(n._statusFlags & STATUS_PENDING) ||
|
|
1799
|
+
n._x._transaction?._lane ||
|
|
1800
|
+
source._flags & REACTIVE_REASK)
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1462
1803
|
// Installed at module evaluation — present exactly when something imports
|
|
1463
1804
|
// `latest` or `isPending`. (`read` dispatches to `verdictValue` through
|
|
1464
1805
|
// `verdict`, set while a window is open.)
|
|
1465
|
-
installVerdictHooks(observeFlight);
|
|
1806
|
+
installVerdictHooks(observeFlight, holdWork);
|
|
1466
1807
|
/** A store read inside an untracked `isPending` probe found a live mark on
|
|
1467
1808
|
* a record with no node to carry it (store/affects.ts): not final. */
|
|
1468
1809
|
GlobalQueue._witnessMark = () => {
|
|
@@ -1906,7 +2247,8 @@ function effect(compute, effect, error, options) {
|
|
|
1906
2247
|
!node._x?._transaction?._lane &&
|
|
1907
2248
|
(node._type === EFFECT_USER || options?.schedule
|
|
1908
2249
|
? globalQueue.enqueue(node._type, runEffect.bind(null, node))
|
|
1909
|
-
:
|
|
2250
|
+
: // Type 0: the synchronous first render, not a queued run (D-19).
|
|
2251
|
+
runEffect(node, 0));
|
|
1910
2252
|
if (!node._parent) {
|
|
1911
2253
|
const message =
|
|
1912
2254
|
"[NO_OWNER_EFFECT] Effects created outside a reactive context will never be disposed";
|
|
@@ -1945,11 +2287,12 @@ function notifyEffectStatus() {
|
|
|
1945
2287
|
return;
|
|
1946
2288
|
}
|
|
1947
2289
|
// Dev locates the halt at the node that threw: the StatusError's
|
|
1948
|
-
// source, upstream of this effect when a computed failed.
|
|
2290
|
+
// source, upstream of this effect when a computed failed. The rethrow is
|
|
2291
|
+
// the user's error, not the wrapper: the wrapper's `source` is a node, and
|
|
2292
|
+
// a reporter that serializes the throw walks the reactive graph.
|
|
1949
2293
|
escalate(
|
|
1950
2294
|
this,
|
|
1951
2295
|
unwrapStatusError(actualError),
|
|
1952
|
-
actualError,
|
|
1953
2296
|
actualError instanceof StatusError ? actualError.source : this
|
|
1954
2297
|
);
|
|
1955
2298
|
} else if (this._type === EFFECT_RENDER) {
|
|
@@ -1981,11 +2324,11 @@ function notifyEffectStatus() {
|
|
|
1981
2324
|
}
|
|
1982
2325
|
}
|
|
1983
2326
|
/** An effect's error no boundary takes halts the system (boundary-or-halt):
|
|
1984
|
-
* halted with `cause
|
|
1985
|
-
function escalate(node, cause,
|
|
2327
|
+
* halted with `cause` and rethrown; dev locates it at `subject`. */
|
|
2328
|
+
function escalate(node, cause, subject = node) {
|
|
1986
2329
|
if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
|
|
1987
2330
|
haltReactivityAt(cause, subject);
|
|
1988
|
-
throw
|
|
2331
|
+
throw cause;
|
|
1989
2332
|
}
|
|
1990
2333
|
}
|
|
1991
2334
|
function runEffect(node, type) {
|
|
@@ -1995,10 +2338,12 @@ function runEffect(node, type) {
|
|
|
1995
2338
|
// effect's update too (maintainer, 2026-10-02: "we held the queue but let
|
|
1996
2339
|
// the sync renders through… we definitely shouldn't be showing portals
|
|
1997
2340
|
// early") — a Portal's or head-tag's render effect writes outside the
|
|
1998
|
-
// hidden subtree. The synchronous first render on creation
|
|
1999
|
-
// run and goes through
|
|
2000
|
-
//
|
|
2001
|
-
|
|
2341
|
+
// hidden subtree. The synchronous first render on creation (`type` 0) is
|
|
2342
|
+
// not a queued run and goes through, whichever boundary above is on its
|
|
2343
|
+
// fallback then — an outer one about to reveal in this flush included
|
|
2344
|
+
// (#4343). `_modified` stays set; the boundary re-queues the run
|
|
2345
|
+
// (`release`).
|
|
2346
|
+
if (type && heldRunHook?.(node)) return;
|
|
2002
2347
|
// Error arm (#2840), user effects only: a compute-phase error that is still
|
|
2003
2348
|
// the node's settled state at effect time runs the bundle's error handler in
|
|
2004
2349
|
// this same imperative, writable scope. Unwrap the StatusError used for
|
|
@@ -2334,6 +2679,15 @@ let writeOverride = false;
|
|
|
2334
2679
|
function setWriteOverride(value) {
|
|
2335
2680
|
writeOverride = value;
|
|
2336
2681
|
}
|
|
2682
|
+
/** The list driver's view of a shallow store (map.ts): the backing a read of
|
|
2683
|
+
* every slot serves this reader, or `undefined` when the list is no shallow
|
|
2684
|
+
* store or a slot read could differ from it. Installed by store.ts's top
|
|
2685
|
+
* level, so a `<For>` in an app without stores carries none of it; it lives
|
|
2686
|
+
* beside `$TRACK` so map.ts reaches no other store module. */
|
|
2687
|
+
let shallowView = null;
|
|
2688
|
+
function installShallowView(view) {
|
|
2689
|
+
shallowView = view;
|
|
2690
|
+
}
|
|
2337
2691
|
|
|
2338
2692
|
/**
|
|
2339
2693
|
* Deferred hydration, dependency edges for stores. Only the frames client
|
|
@@ -3439,9 +3793,12 @@ function updateKeyedMap() {
|
|
|
3439
3793
|
// vanishing. Set per pass: the first pass runs inside `computed()`, before
|
|
3440
3794
|
// the node exists to the caller.
|
|
3441
3795
|
this._owner._parentComputed = getOwner();
|
|
3442
|
-
|
|
3443
|
-
newLen = newItems.length;
|
|
3796
|
+
let newItems = this._list() || [];
|
|
3444
3797
|
newItems[$TRACK]; // top level tracking (a store array's container node)
|
|
3798
|
+
// A shallow store array diffs its backing: its slots are raw records, and
|
|
3799
|
+
// a trap per slot read was most of a one-row write on a 1k-row list.
|
|
3800
|
+
newItems = shallowView?.(newItems[$TARGET]) ?? newItems;
|
|
3801
|
+
const newLen = newItems.length;
|
|
3445
3802
|
runWithOwner(this._owner, () => {
|
|
3446
3803
|
let i,
|
|
3447
3804
|
j,
|
|
@@ -4199,6 +4556,7 @@ const storePark = t => {
|
|
|
4199
4556
|
const target = foldList[i];
|
|
4200
4557
|
if (target.k === null && target.pb !== null) holdNode(getContainerNode(target), t);
|
|
4201
4558
|
}
|
|
4559
|
+
optHooks?.park(t);
|
|
4202
4560
|
};
|
|
4203
4561
|
function getDeepNode(target) {
|
|
4204
4562
|
let dk = target.dk;
|
|
@@ -4274,17 +4632,26 @@ function cloneRaw(source, t) {
|
|
|
4274
4632
|
clone[$OWNER] = t;
|
|
4275
4633
|
return clone;
|
|
4276
4634
|
}
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4635
|
+
// A plain array with no accessors and no named keys beyond `length`
|
|
4636
|
+
// (`kc` counts own keys) clones by `slice()`: the descriptor path
|
|
4637
|
+
// allocates one descriptor per element, O(n) per first draft write (10k
|
|
4638
|
+
// rows: ~10 ms per push).
|
|
4639
|
+
let clone;
|
|
4640
|
+
if (!t.a && t.kc <= source.length + 1 && Object.getPrototypeOf(source) === Array.prototype)
|
|
4641
|
+
clone = source.slice();
|
|
4642
|
+
else {
|
|
4643
|
+
const descs = Object.getOwnPropertyDescriptors(source);
|
|
4644
|
+
for (const key of Reflect.ownKeys(descs)) {
|
|
4645
|
+
const d = descs[key];
|
|
4646
|
+
if (key === "length" && Array.isArray(source)) continue;
|
|
4647
|
+
d.configurable = true;
|
|
4648
|
+
if (!d.get && !d.set) d.writable = true;
|
|
4649
|
+
else t.a = true;
|
|
4650
|
+
}
|
|
4651
|
+
clone = Array.isArray(source)
|
|
4652
|
+
? Object.defineProperties([], descs)
|
|
4653
|
+
: Object.create(Object.getPrototypeOf(source), descs);
|
|
4654
|
+
}
|
|
4288
4655
|
clone[$OWNER] = t;
|
|
4289
4656
|
return clone;
|
|
4290
4657
|
}
|
|
@@ -4783,13 +5150,18 @@ function notifyWrites(t) {
|
|
|
4783
5150
|
// Optimistic channel: a user's writes on an optimistic family become
|
|
4784
5151
|
// guesses on the nodes (lanes); the draft is discarded, the committed
|
|
4785
5152
|
// backing never touched.
|
|
4786
|
-
if (optHooks &&
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
5153
|
+
if (optHooks && t.fam?.opt === true) {
|
|
5154
|
+
if (userWrite) {
|
|
5155
|
+
// The draft is discarded first: the diff reads the target (its nodes
|
|
5156
|
+
// are born from the committed frame, not the draft).
|
|
5157
|
+
t.pb = null;
|
|
5158
|
+
t.wk = null;
|
|
5159
|
+
t.pb = optHooks.writes(t, pb);
|
|
5160
|
+
return;
|
|
5161
|
+
}
|
|
5162
|
+
// The derive's draft: new rows under guessed rows are paired with them
|
|
5163
|
+
// first (D-34, #4329).
|
|
5164
|
+
optHooks.pair(t, pb);
|
|
4793
5165
|
}
|
|
4794
5166
|
const old = t.v;
|
|
4795
5167
|
// Devtools mutation hook: full-key diff (dev-only cost) so unobserved
|
|
@@ -4966,7 +5338,10 @@ function notifyKeyDiff(node, key, old, neu, probe = true) {
|
|
|
4966
5338
|
} else {
|
|
4967
5339
|
const ov = old[key];
|
|
4968
5340
|
const nv = neu[key];
|
|
4969
|
-
|
|
5341
|
+
// A live chained `old` (a store proxy, §7b) already reads the inner
|
|
5342
|
+
// store's next frame, not what the node was last told: written, the
|
|
5343
|
+
// node's own equality judging it (#4361).
|
|
5344
|
+
if (old[$TARGET] || (!isEqual(ov, nv) && !targetsEqual(ov, nv)))
|
|
4970
5345
|
setSignal(node, typeof nv === "function" ? () => nv : nv);
|
|
4971
5346
|
}
|
|
4972
5347
|
}
|
|
@@ -4992,7 +5367,8 @@ function notifyKeyValue(node, key, ov, nv, old, neu) {
|
|
|
4992
5367
|
* unrelated `a.value` stayed stale until the action settled). A real
|
|
4993
5368
|
* change still writes, and on a held node still proposes. A live chained
|
|
4994
5369
|
* `old` (a store proxy, §7b) reflects the inner store, not what the node
|
|
4995
|
-
* was last told — written unconditionally, as before
|
|
5370
|
+
* was last told — written unconditionally, as before; its container too
|
|
5371
|
+
* (#4361).
|
|
4996
5372
|
*
|
|
4997
5373
|
* The container is told of an arrangement change when it has structural
|
|
4998
5374
|
* subscribers OR carries an arrangement guess (S4): for a guessed
|
|
@@ -5004,17 +5380,18 @@ function notifyKeyValue(node, key, ov, nv, old, neu) {
|
|
|
5004
5380
|
* lane, by accident of being unconditional. */
|
|
5005
5381
|
function notifyFoldTail(t, old, neu) {
|
|
5006
5382
|
const has = t.h;
|
|
5383
|
+
const live = old[$TARGET];
|
|
5007
5384
|
if (has !== null) {
|
|
5008
|
-
const live = old[$TARGET];
|
|
5009
5385
|
for (const key of Reflect.ownKeys(has))
|
|
5010
5386
|
if (live || key in old !== key in neu) setSignal(has[key], key in neu);
|
|
5011
5387
|
}
|
|
5012
5388
|
const k = t.k;
|
|
5013
5389
|
if (k !== null && (k._subs !== null || k._config & CONFIG_OVERRIDE)) {
|
|
5014
5390
|
const changed =
|
|
5015
|
-
|
|
5391
|
+
live ||
|
|
5392
|
+
(Array.isArray(neu) && Array.isArray(old)
|
|
5016
5393
|
? arrayStructureChanged(old, neu)
|
|
5017
|
-
: membershipChanged(old, neu);
|
|
5394
|
+
: membershipChanged(old, neu));
|
|
5018
5395
|
if (changed) notifyContainer(k, neu);
|
|
5019
5396
|
}
|
|
5020
5397
|
}
|
|
@@ -5302,16 +5679,27 @@ function changedBetween(target, v, pb, key, shape) {
|
|
|
5302
5679
|
}
|
|
5303
5680
|
/** Value of a key with a leaf: core's — `read()` links a tracked reader and,
|
|
5304
5681
|
* tracked or not, applies the hold and A28 rules a signal's read does. A
|
|
5305
|
-
* projection's unheld staging is ahead for a handler (`familyAhead`)
|
|
5682
|
+
* projection's unheld staging is ahead for a handler (`familyAhead`), and is
|
|
5683
|
+
* the only value while its derive has never committed: the committed side
|
|
5684
|
+
* is the seed, never observable (A25) — a reader core serves the screen (a
|
|
5685
|
+
* verdict reader, #4355) is served the staging, as for a memo born staged
|
|
5686
|
+
* (core `serve`). */
|
|
5306
5687
|
function nodeValue(node, backing, target) {
|
|
5307
|
-
let v
|
|
5688
|
+
let v;
|
|
5308
5689
|
if (
|
|
5309
5690
|
node._pendingValue !== NOT_PENDING &&
|
|
5310
5691
|
!(node._config & CONFIG_HELD) &&
|
|
5311
|
-
|
|
5312
|
-
target.fam
|
|
5313
|
-
)
|
|
5692
|
+
target.fam !== null &&
|
|
5693
|
+
(context === null || target.fam.node?._statusFlags & STATUS_UNINITIALIZED)
|
|
5694
|
+
) {
|
|
5695
|
+
// A pass over a derive that has never committed: the staging is its
|
|
5696
|
+
// first answer, not a change — linked and served with no verdict (a
|
|
5697
|
+
// first landing is loading, not pending: A19 exc. 1, F-9, as for a memo
|
|
5698
|
+
// born staged; core's verdict would call the leaf's staging pending).
|
|
5699
|
+
devRead(node); // read()'s dev diagnostics, which this read skips
|
|
5700
|
+
if (context !== null && tracking) link(node, context);
|
|
5314
5701
|
v = node._pendingValue;
|
|
5702
|
+
} else v = read(node);
|
|
5315
5703
|
return v === FORCE ? backing : v;
|
|
5316
5704
|
}
|
|
5317
5705
|
/** Serve a present data key: link the leaf for a tracked reader (creating it
|
|
@@ -5398,15 +5786,9 @@ function pullFamily(target) {
|
|
|
5398
5786
|
// (committed plus the guesses) and does not suspend on the derive; a
|
|
5399
5787
|
// verdict window still asks it (`isPending` is true while the flight is
|
|
5400
5788
|
// up). The family's, not the target's: the edit stands in for the one
|
|
5401
|
-
// flight the family has
|
|
5402
|
-
|
|
5403
|
-
if (
|
|
5404
|
-
verdict === null &&
|
|
5405
|
-
fw._statusFlags & STATUS_PENDING &&
|
|
5406
|
-
overlaid !== undefined &&
|
|
5407
|
-
overlaid.size !== 0
|
|
5408
|
-
)
|
|
5409
|
-
return;
|
|
5789
|
+
// flight the family has — not over a new question they do not answer
|
|
5790
|
+
// (optimistic.ts `stands`, #4336).
|
|
5791
|
+
if (verdict === null && fw._statusFlags & STATUS_PENDING && optHooks?.stands(target)) return;
|
|
5410
5792
|
read(fw);
|
|
5411
5793
|
} else untrack(() => read(fw));
|
|
5412
5794
|
}
|
|
@@ -5531,6 +5913,7 @@ function warnWriteOutsideSetter(target, key, operation) {
|
|
|
5531
5913
|
}
|
|
5532
5914
|
const traps = {
|
|
5533
5915
|
get(target, key, receiver) {
|
|
5916
|
+
if (getObserver() === null) checkForeignRead();
|
|
5534
5917
|
// One typeof gates every brand-symbol compare off the hot string path.
|
|
5535
5918
|
if (typeof key !== "string") {
|
|
5536
5919
|
if (key === $TARGET) return target;
|
|
@@ -5679,7 +6062,8 @@ const traps = {
|
|
|
5679
6062
|
// Reading a currently-absent own key subscribes to it (R12) — for any
|
|
5680
6063
|
// target OUTSIDE its own draft scope, even mid-setter (#3037, above).
|
|
5681
6064
|
if (v === undefined && !inDraft(target)) {
|
|
5682
|
-
|
|
6065
|
+
// (`nodeValue` reads — and links — the node it creates.)
|
|
6066
|
+
if (getObserver() !== null) getNode(target, key, accProbe);
|
|
5683
6067
|
const node = target.n?.[key];
|
|
5684
6068
|
if (node) {
|
|
5685
6069
|
const nv = nodeValue(node, undefined, target);
|
|
@@ -6009,6 +6393,17 @@ function createStore$1(initialValue, options) {
|
|
|
6009
6393
|
const setter = fn => storeSetter(proxy, fn);
|
|
6010
6394
|
return [proxy, setter];
|
|
6011
6395
|
}
|
|
6396
|
+
/** The list driver's slot view of a shallow store (types.ts `shallowView`):
|
|
6397
|
+
* one `readSource` for the container instead of a trap per slot. A shallow
|
|
6398
|
+
* store's slots hold raw records, so the backing the container read serves
|
|
6399
|
+
* is what each slot read would — except where a slot's own leaf can answer
|
|
6400
|
+
* differently: an optimistic family's guesses, a chained backing, accessor
|
|
6401
|
+
* keys, or a read inside a draft or write scope. Those keep the trap. */
|
|
6402
|
+
installShallowView(t => {
|
|
6403
|
+
// Any open write scope keeps the trap (it covers `inDraft`).
|
|
6404
|
+
if (t?.s && !(t.ch || t.a || writeScopes || writeOverride || t.fam?.opt))
|
|
6405
|
+
return (t.fam && pullFamily(t), readSource(t));
|
|
6406
|
+
});
|
|
6012
6407
|
/** True when `proxy` is a SHALLOW store (children served verbatim, slots
|
|
6013
6408
|
* replaced by reference — #2932). The list driver uses this to choose the
|
|
6014
6409
|
* slot-patch channel (collected row bodies) over per-record registration. */
|
|
@@ -6358,7 +6753,10 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
|
|
|
6358
6753
|
if (shallow) markRawIngest(incoming);
|
|
6359
6754
|
// The deep witness is bumped once per adoption (none to bump: done).
|
|
6360
6755
|
let bumped = t.dk === null;
|
|
6361
|
-
|
|
6756
|
+
// A live chained base (a store proxy, §7b) reads the inner store's
|
|
6757
|
+
// current frame, not what the nodes were last told — nothing to diff
|
|
6758
|
+
// against: the fold writes every node (#4361).
|
|
6759
|
+
if (Array.isArray(prevView) !== nextArr || old[$TARGET]) {
|
|
6362
6760
|
notifyFold(t, old, incoming);
|
|
6363
6761
|
return;
|
|
6364
6762
|
}
|
|
@@ -6476,7 +6874,10 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
|
|
|
6476
6874
|
let nodesHit = 0;
|
|
6477
6875
|
for (const k in incoming) {
|
|
6478
6876
|
const nv = incoming[k];
|
|
6479
|
-
|
|
6877
|
+
let ov = old[k];
|
|
6878
|
+
// A key under a guess is judged against the guess too (D-34): a truth
|
|
6879
|
+
// equal to the value the guess covered still answers it.
|
|
6880
|
+
if (optHooks && ov === nv && prevView !== old) ov = prevView[k];
|
|
6480
6881
|
const isObj = nv !== null && typeof nv === "object";
|
|
6481
6882
|
if (
|
|
6482
6883
|
ov === nv &&
|
|
@@ -6676,6 +7077,7 @@ function wakeFamily(fam) {
|
|
|
6676
7077
|
// reader the landing leaves unchanged does not re-run to clear it (#4305).
|
|
6677
7078
|
const error = new NotReadyError(fw);
|
|
6678
7079
|
fam.woke = true;
|
|
7080
|
+
optHooks?.ask(fam);
|
|
6679
7081
|
forEachFamilyNode(fam, n => {
|
|
6680
7082
|
if (n._subs === null) return;
|
|
6681
7083
|
// The leaf carries the derive's pending as a propagated copy, as a memo
|
|
@@ -6711,6 +7113,10 @@ function settleFamily(fam) {
|
|
|
6711
7113
|
forEachFamilyNode(fam, n => {
|
|
6712
7114
|
if (n._subs !== null) settlePendingSource(n, fw);
|
|
6713
7115
|
});
|
|
7116
|
+
// The landing is the derive's change for a reader parked on the derive
|
|
7117
|
+
// itself, whatever status the landing pass began from (an errored derive
|
|
7118
|
+
// re-asked: a reader that met the error has no leaf to learn it from).
|
|
7119
|
+
insertSubs(fw);
|
|
6714
7120
|
}
|
|
6715
7121
|
/** A source's status reached the derive by propagation (core
|
|
6716
7122
|
* `propagateStatus`), its pass not re-run: the family learns it as from the
|
|
@@ -6771,7 +7177,7 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
|
|
|
6771
7177
|
draft,
|
|
6772
7178
|
s => {
|
|
6773
7179
|
result = fn(shadow ?? s);
|
|
6774
|
-
const commit = v => {
|
|
7180
|
+
const commit = (v, wake = 1) => {
|
|
6775
7181
|
// Shadow run: commit a detached snapshot, never the shadow itself
|
|
6776
7182
|
// (adoption takes the value by identity — handing it the live
|
|
6777
7183
|
// shadow would fuse the draft to the observable store).
|
|
@@ -6794,8 +7200,11 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
|
|
|
6794
7200
|
// A commit is the derive's change for the readers that observed its
|
|
6795
7201
|
// flight (linked by `pullFamily`; a settled pull does not link), as
|
|
6796
7202
|
// a memo's landing is for its readers: one that caught the NotReady
|
|
6797
|
-
// (a fallback value, not pending) re-runs too (#4070).
|
|
6798
|
-
|
|
7203
|
+
// (a fallback value, not pending) re-runs too (#4070). A pass of a
|
|
7204
|
+
// settled derive has none (`wake` 0): a link left is a stale one a
|
|
7205
|
+
// held frame keeps (A30), from a reader that re-ran since. Woken by
|
|
7206
|
+
// every pass, a reader feeding a key the pass peeks at looped (#4342).
|
|
7207
|
+
if (wake) insertSubs(owner);
|
|
6799
7208
|
};
|
|
6800
7209
|
const sync = handleAsync(owner, result, commit, (error, pending) => {
|
|
6801
7210
|
// The flight rejected: the leaves' readers learn it from here —
|
|
@@ -6808,7 +7217,8 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
|
|
|
6808
7217
|
if (pending) wakeFamily(fam);
|
|
6809
7218
|
else errorFamily(fam);
|
|
6810
7219
|
});
|
|
6811
|
-
|
|
7220
|
+
// (The status is the one before this pass: `recompute` sets it after.)
|
|
7221
|
+
if (!owner._loading) commit(sync, owner._statusFlags & (STATUS_PENDING | STATUS_ERROR));
|
|
6812
7222
|
},
|
|
6813
7223
|
false
|
|
6814
7224
|
);
|
|
@@ -7027,21 +7437,23 @@ function arrangement(t, a, b) {
|
|
|
7027
7437
|
const av = a instanceof LaneView;
|
|
7028
7438
|
const bv = b instanceof LaneView;
|
|
7029
7439
|
if (!av && !bv) return false;
|
|
7030
|
-
return arrangementEqual(av ? a.rows : a, bv ? b.rows : b, t.fam
|
|
7440
|
+
return arrangementEqual(av ? a.rows : a, bv ? b.rows : b, t.fam);
|
|
7031
7441
|
}
|
|
7032
|
-
function rowSame(a, b,
|
|
7442
|
+
function rowSame(a, b, fam) {
|
|
7033
7443
|
if (isEqual(a, b) || targetsEqual(a, b)) return true;
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
//
|
|
7038
|
-
//
|
|
7039
|
-
//
|
|
7040
|
-
//
|
|
7041
|
-
//
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7444
|
+
const keyFn = fam.key;
|
|
7445
|
+
if (!keyFn || !isWrappable(a) || !isWrappable(b)) return false;
|
|
7446
|
+
// A row the store already serves is its proxy's row: a reader (a list)
|
|
7447
|
+
// holds that proxy, so another object at the index is another row, by key
|
|
7448
|
+
// or contents alike — a guess's fresh object wraps a new proxy. A landing's
|
|
7449
|
+
// object has been adopted by the row it pairs with, so it serves that
|
|
7450
|
+
// row's proxy. Only rows no proxy serves yet pair by key, keyless rows by
|
|
7451
|
+
// position — reconcile's fallback (#3898).
|
|
7452
|
+
const ta = lookupTarget((a = unwrapValue(a)), fam);
|
|
7453
|
+
const tb = lookupTarget((b = unwrapValue(b)), fam);
|
|
7454
|
+
if (ta || tb) return ta === tb;
|
|
7455
|
+
// (Two keyless rows: `undefined` is the same key.)
|
|
7456
|
+
return sameKey(keyFn(a), keyFn(b));
|
|
7045
7457
|
}
|
|
7046
7458
|
/** Whether reconcile would write anything into `a` from `b`: wrappable
|
|
7047
7459
|
* pairs descend, leaves compare as its writes do. A key mismatch is a
|
|
@@ -7066,11 +7478,11 @@ GlobalQueue._echoes = (a, b) => {
|
|
|
7066
7478
|
return false;
|
|
7067
7479
|
}
|
|
7068
7480
|
};
|
|
7069
|
-
function arrangementEqual(a, b,
|
|
7481
|
+
function arrangementEqual(a, b, fam) {
|
|
7070
7482
|
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
7071
7483
|
if (Array.isArray(a)) {
|
|
7072
7484
|
if (a.length !== b.length) return false;
|
|
7073
|
-
for (let i = 0; i < a.length; i++) if (!rowSame(a[i], b[i],
|
|
7485
|
+
for (let i = 0; i < a.length; i++) if (!rowSame(a[i], b[i], fam)) return false;
|
|
7074
7486
|
return true;
|
|
7075
7487
|
}
|
|
7076
7488
|
const ak = Reflect.ownKeys(a).filter(k => k !== $OWNER);
|
|
@@ -7130,6 +7542,33 @@ function reconcileDraft(draft, incoming, keyFn) {
|
|
|
7130
7542
|
}
|
|
7131
7543
|
for (const k of Object.keys(draft)) if (!(k in incoming)) delete draft[k];
|
|
7132
7544
|
}
|
|
7545
|
+
/** Core asks (lanes `laneWrite`): the family's derive, re-asking in a lane of
|
|
7546
|
+
* another action than the one that guessed this slot, writes it — that
|
|
7547
|
+
* lane's new question, which the guess (over the old one) does not answer:
|
|
7548
|
+
* the slot is the lane's derivation from here (F-6, #4336; lanes
|
|
7549
|
+
* `laneStage` for a node's own pass). */
|
|
7550
|
+
GlobalQueue._slotTake = (n, back) => {
|
|
7551
|
+
const t = n._host;
|
|
7552
|
+
if (back) {
|
|
7553
|
+
// The guess comes back (lanes `endLanes`): on the slot's node now — the
|
|
7554
|
+
// node itself, or the one a read made after it was released.
|
|
7555
|
+
const k = n._key;
|
|
7556
|
+
markOverlaid(t);
|
|
7557
|
+
return k === $TRACK
|
|
7558
|
+
? getContainerNode(t)
|
|
7559
|
+
: n._equals === isEqual
|
|
7560
|
+
? getHasNode(t, k)
|
|
7561
|
+
: getNode(t, k);
|
|
7562
|
+
}
|
|
7563
|
+
const d = t?.fam?.node?._x?._transaction;
|
|
7564
|
+
const g = txOf(n);
|
|
7565
|
+
if (!d?._lane || sameAction(d, g)) return false;
|
|
7566
|
+
// The re-ask takes the target's guess whole: the slot is the lane's
|
|
7567
|
+
// derivation; its other guessed slots show their truth in the lane's
|
|
7568
|
+
// world (lanes `take`).
|
|
7569
|
+
for (const m of nodesOf(t)) if (m?._config & CONFIG_GUESS && txOf(m) === g) take(m, d, m !== n);
|
|
7570
|
+
return true;
|
|
7571
|
+
};
|
|
7133
7572
|
/** Q-D (plan sec. 39): an OLDER question's truth landed beneath an arrangement
|
|
7134
7573
|
* guess (`laneWrite` held it — another action's landing is not this guess's
|
|
7135
7574
|
* answer). The guess is re-based over it: order from the guess, rows from
|
|
@@ -7272,7 +7711,30 @@ function laneSet(node, value, lane) {
|
|
|
7272
7711
|
laneDirty(node, lane);
|
|
7273
7712
|
}
|
|
7274
7713
|
}
|
|
7714
|
+
/** D-34 for a per-key guess (#4329): the derive's draft wrote new objects
|
|
7715
|
+
* over a target holding guessed rows (`d.length = 0; d.push(...rows)`, or
|
|
7716
|
+
* `d.issues = rows`). The container on the way down to each guessed row
|
|
7717
|
+
* reconciles the new object at its slot — in a keyed list, the one with
|
|
7718
|
+
* the row's key — exactly as a returned landing would: rows match by key,
|
|
7719
|
+
* a guessed row keeps its target, and its leaves are judged against their
|
|
7720
|
+
* guesses. Rows nothing guessed are left as the draft wrote them. */
|
|
7721
|
+
function pairRows(t, pb) {
|
|
7722
|
+
const keyFn = t.fam.key;
|
|
7723
|
+
for (let c of t.fam.overlaid ?? []) {
|
|
7724
|
+
// The guessed row, or its ancestor directly under `t` (none: skip).
|
|
7725
|
+
while (c.u !== t) if ((c = c.u) === null) break;
|
|
7726
|
+
if (c === null) continue;
|
|
7727
|
+
const k = keyFn?.(c.v);
|
|
7728
|
+
const nv = Array.isArray(pb)
|
|
7729
|
+
? k !== undefined && pb.find(r => isWrappable(r) && sameKey(keyFn(r), k))
|
|
7730
|
+
: pb[c.pk];
|
|
7731
|
+
// A new object only: not `c`'s own, nor one another row serves
|
|
7732
|
+
// (pairing it would alias the two).
|
|
7733
|
+
if (isWrappable(nv) && !lookupTarget(nv, t.fam)) reconcileState(nv, c.px, keyFn, true);
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
7275
7736
|
installOptHooks({
|
|
7737
|
+
pair: pairRows,
|
|
7276
7738
|
draft: optimisticDraft,
|
|
7277
7739
|
writes: notifyOptimisticWrites,
|
|
7278
7740
|
arrangement,
|
|
@@ -7281,8 +7743,55 @@ installOptHooks({
|
|
|
7281
7743
|
writer ? optimisticView(t, src, laneValueOf, true) : optimisticView(t, src),
|
|
7282
7744
|
has: optimisticHas,
|
|
7283
7745
|
descriptor: optimisticOwnDescriptor,
|
|
7284
|
-
sweep: sweepOverlaid
|
|
7746
|
+
sweep: sweepOverlaid,
|
|
7747
|
+
// A new question (not a `refresh()`) asked over guesses an earlier frame
|
|
7748
|
+
// opened, which answer the old one (D-34, #4336). Stamped on `fam.asked`
|
|
7749
|
+
// with this flush's `clock`, so it lapses when the flush ends: `clock + 1`
|
|
7750
|
+
// from mainline over a live guess — a frame still reading a guess at the
|
|
7751
|
+
// seam shows it beside the new input, so the flush holds with the guess's
|
|
7752
|
+
// action (`park`); `~clock` from a lane with no guess of its own, or with
|
|
7753
|
+
// no live guess left — the lane waits for its answer (F-6), as for a
|
|
7754
|
+
// guessed `createOptimistic` (lanes `laneStage`). The guess's own lane
|
|
7755
|
+
// asks nothing new and stamps nothing.
|
|
7756
|
+
ask: fam => {
|
|
7757
|
+
if (fam.node._flags & REACTIVE_REASK || !fam.overlaid?.size) return;
|
|
7758
|
+
// Stamped with the flush (`clock`): it lapses when the flush ends.
|
|
7759
|
+
let a = ~clock;
|
|
7760
|
+
for (const g of fam.overlaid) {
|
|
7761
|
+
const n = laneNodeOf(g);
|
|
7762
|
+
if (n) {
|
|
7763
|
+
if (passLane === null) a = clock + 1;
|
|
7764
|
+
else if (sameAction(txOf(n), passLane)) return;
|
|
7765
|
+
}
|
|
7766
|
+
}
|
|
7767
|
+
if ((fam.asked = a) > 0) joinFuture(null);
|
|
7768
|
+
},
|
|
7769
|
+
// Over a new question the guesses do not answer (`asked`): from mainline
|
|
7770
|
+
// only a target carrying a guess stands in — a read of one with none is an
|
|
7771
|
+
// ordinary pending read (F-2), so a keyed store's other keys wait for their
|
|
7772
|
+
// answer, not for the guess's action; in a lane with no guess of its own,
|
|
7773
|
+
// or with no live guess left, none does (F-6).
|
|
7774
|
+
stands: t => {
|
|
7775
|
+
const o = t.fam.overlaid;
|
|
7776
|
+
const a = t.fam.asked;
|
|
7777
|
+
return !!o?.size && (a !== clock + 1 || passLane !== null || o.has(t)) && a !== ~clock;
|
|
7778
|
+
},
|
|
7779
|
+
// Mainline hold (`clock + 1`): the flush joins the action of each guess a
|
|
7780
|
+
// reader still reads at the seam; a reader that moved off it (a keyed
|
|
7781
|
+
// store's other key) holds nothing.
|
|
7782
|
+
park: t => {
|
|
7783
|
+
for (const fam of overlaidFamilies)
|
|
7784
|
+
if (fam.asked === clock + 1)
|
|
7785
|
+
for (const g of fam.overlaid) {
|
|
7786
|
+
const n = laneNodeOf(g, true);
|
|
7787
|
+
if (n) merge$1(txOf(n)._parent, t);
|
|
7788
|
+
}
|
|
7789
|
+
}
|
|
7285
7790
|
});
|
|
7791
|
+
/** Two lanes of one action (their parents resolved through merges). */
|
|
7792
|
+
function sameAction(a, b) {
|
|
7793
|
+
return resolveTx(a._parent) === resolveTx(b._parent);
|
|
7794
|
+
}
|
|
7286
7795
|
function shallowKeys(o) {
|
|
7287
7796
|
const out = {};
|
|
7288
7797
|
for (const k of Reflect.ownKeys(o)) if (k !== $OWNER) out[k] = unwrapValue(o[k]);
|
|
@@ -7304,7 +7813,7 @@ function sweepOverlaid() {
|
|
|
7304
7813
|
for (const fam of overlaidFamilies) {
|
|
7305
7814
|
const set = fam.overlaid;
|
|
7306
7815
|
for (const t of set)
|
|
7307
|
-
if (!
|
|
7816
|
+
if (!laneNodeOf(t)) {
|
|
7308
7817
|
set.delete(t);
|
|
7309
7818
|
if (t.ch) rebaseChained(t);
|
|
7310
7819
|
// The container shows its backing again (a shown arrangement guess
|
|
@@ -7359,23 +7868,27 @@ function rebaseChained(t) {
|
|
|
7359
7868
|
k._notifiedAt !== notifyEpoch
|
|
7360
7869
|
) {
|
|
7361
7870
|
const rows = Array.isArray(inner) ? untrack(() => inner.map(unwrapValue)) : inner;
|
|
7362
|
-
if (!arrangementEqual(k._value.rows, rows, t.fam
|
|
7871
|
+
if (!arrangementEqual(k._value.rows, rows, t.fam)) {
|
|
7363
7872
|
insertSubs(k);
|
|
7364
7873
|
changed = true;
|
|
7365
7874
|
}
|
|
7366
7875
|
}
|
|
7367
7876
|
if (changed) schedule();
|
|
7368
7877
|
}
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
const
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7878
|
+
/** `t`'s nodes (a missing container or deep node: null). */
|
|
7879
|
+
function nodesOf(t) {
|
|
7880
|
+
const all = [t.k, t.dk];
|
|
7881
|
+
for (const r of [t.n, t.h]) if (r !== null) for (const k of Reflect.ownKeys(r)) all.push(r[k]);
|
|
7882
|
+
return all;
|
|
7883
|
+
}
|
|
7884
|
+
/** A node of `t` carrying a lane value, if any (`read`: one a reader's
|
|
7885
|
+
* last pass read). */
|
|
7886
|
+
function laneNodeOf(t, read) {
|
|
7887
|
+
for (const n of nodesOf(t))
|
|
7888
|
+
if (n?._config & CONFIG_OVERRIDE) {
|
|
7889
|
+
if (!read) return n;
|
|
7890
|
+
for (let s = n._subs; s !== null; s = s._nextSub) if (s._gen === s._sub._depGen) return n;
|
|
7891
|
+
}
|
|
7379
7892
|
}
|
|
7380
7893
|
function overlaid(t) {
|
|
7381
7894
|
const fam = t.fam;
|
|
@@ -8770,7 +9283,8 @@ function releaseMarkScope(carrier) {
|
|
|
8770
9283
|
// A mark is a declared in-flight change: the marked node — and everything
|
|
8771
9284
|
// derived from it — reads pending (`isPending` → true) from the declaration
|
|
8772
9285
|
// until the surrounding transaction lands (an ambient mark, declared outside
|
|
8773
|
-
// one, releases at the next seam
|
|
9286
|
+
// one, releases at the next seam; either, on a node whose own flight is
|
|
9287
|
+
// still up then, at the seam after that flight lands). The marked values stay readable: no reader
|
|
8774
9288
|
// suspends on a mark, a mark holds nothing and entangles nothing — it is not
|
|
8775
9289
|
// a node of its transaction (`blocked` never sees it), and a reader of marked
|
|
8776
9290
|
// data joins nothing through it. A mark is never a re-ask, so a declared
|
|
@@ -8845,30 +9359,42 @@ function invalid(detail) {
|
|
|
8845
9359
|
emitDiagnostic({ code: "INVALID_AFFECTS_TARGET", kind: "write", severity: "error", message });
|
|
8846
9360
|
throw new Error(message);
|
|
8847
9361
|
}
|
|
8848
|
-
installMarkHooks(
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
9362
|
+
installMarkHooks(
|
|
9363
|
+
nodes => {
|
|
9364
|
+
// A landing's mark on a node whose own flight is up (a declared reload's
|
|
9365
|
+
// quiet re-ask the action did not wait for) stays to the seam after
|
|
9366
|
+
// that flight lands, as an ambient one does: the reload's window is the
|
|
9367
|
+
// flight's (#4349). The rest release with the transaction.
|
|
9368
|
+
const done = [];
|
|
9369
|
+
for (let i = 0; i < nodes.length; i++) (inFlight(nodes[i]) ? ambient : done).push(nodes[i]);
|
|
9370
|
+
nodes.length = 0;
|
|
9371
|
+
release$1(done);
|
|
9372
|
+
},
|
|
9373
|
+
parked => {
|
|
9374
|
+
if (ambient.length === 0) return;
|
|
9375
|
+
// The flush parked: the declaration's window is the frame's — the marks
|
|
9376
|
+
// are its transaction's ("nothing async below ⇒ no window").
|
|
9377
|
+
if (parked !== null) {
|
|
9378
|
+
const list = (parked._marks ??= []);
|
|
9379
|
+
for (let i = 0; i < ambient.length; i++) list.push(ambient[i]);
|
|
9380
|
+
ambient.length = 0;
|
|
9381
|
+
return;
|
|
9382
|
+
}
|
|
9383
|
+
// Nothing parked, but a marked node's own flight is up (a declared
|
|
9384
|
+
// reload's quiet re-ask — the node's, or a store leaf's family derive):
|
|
9385
|
+
// the window is the flight's; the mark stays to the seam after the
|
|
9386
|
+
// landing. The rest release: verdict-only, nothing to show.
|
|
9387
|
+
let kept = 0;
|
|
9388
|
+
const done = [];
|
|
9389
|
+
for (let i = 0; i < ambient.length; i++) {
|
|
9390
|
+
const n = ambient[i];
|
|
9391
|
+
if (inFlight(n)) ambient[kept++] = n;
|
|
9392
|
+
else done.push(n);
|
|
9393
|
+
}
|
|
9394
|
+
ambient.length = kept;
|
|
9395
|
+
if (done.length !== 0) release$1(done);
|
|
8857
9396
|
}
|
|
8858
|
-
|
|
8859
|
-
// reload's quiet re-ask — the node's, or a store leaf's family derive):
|
|
8860
|
-
// the window is the flight's; the mark stays to the seam after the
|
|
8861
|
-
// landing. The rest release: verdict-only, nothing to show.
|
|
8862
|
-
let kept = 0;
|
|
8863
|
-
const done = [];
|
|
8864
|
-
for (let i = 0; i < ambient.length; i++) {
|
|
8865
|
-
const n = ambient[i];
|
|
8866
|
-
if (inFlight(n)) ambient[kept++] = n;
|
|
8867
|
-
else done.push(n);
|
|
8868
|
-
}
|
|
8869
|
-
ambient.length = kept;
|
|
8870
|
-
if (done.length !== 0) release$1(done);
|
|
8871
|
-
});
|
|
9397
|
+
);
|
|
8872
9398
|
function inFlight(n) {
|
|
8873
9399
|
return (
|
|
8874
9400
|
(n._statusFlags & STATUS_PENDING) !== 0 ||
|
|
@@ -9410,27 +9936,37 @@ const boundaryOf = node => node._context[BOUNDARY];
|
|
|
9410
9936
|
* has shown content, or while re-armed. */
|
|
9411
9937
|
const isCollecting = b => b._type === STATUS_ERROR || !b._initialized || b._armed;
|
|
9412
9938
|
/** A reader `b` waits on: recorded for its `on`, and `b` visited at the seam. */
|
|
9413
|
-
function record(b, node) {
|
|
9414
|
-
b._readers.
|
|
9939
|
+
function record(b, node, source = node) {
|
|
9940
|
+
b._readers.set(node, source);
|
|
9415
9941
|
collecting.add(b);
|
|
9416
9942
|
}
|
|
9417
9943
|
/** The reader goes to the fallback: recorded, and the output re-derives
|
|
9418
9944
|
* (a no-op while the output's own pass is running — its post-check
|
|
9419
9945
|
* decides). */
|
|
9420
|
-
function caught(b, node, error) {
|
|
9946
|
+
function caught(b, node, error, source = node) {
|
|
9421
9947
|
if (b._type === STATUS_ERROR) {
|
|
9422
9948
|
// An error boundary waits on what THREW (`StatusError.source`), not on
|
|
9423
|
-
// the reader that saw it
|
|
9424
|
-
//
|
|
9425
|
-
//
|
|
9426
|
-
//
|
|
9949
|
+
// the reader that saw it, while the error is settled only when its
|
|
9950
|
+
// source is neither errored nor in flight and the reader is not pending
|
|
9951
|
+
// on the answer that replaces it (`prune`) — or when the
|
|
9952
|
+
// reader is disposed: a source no live reader holds holds nothing
|
|
9953
|
+
// (#4373). A source that died (a memo its reader owned, kept past the
|
|
9954
|
+
// pass that disposed it) settles nothing: the reader is waited on.
|
|
9955
|
+
// `reset` re-runs the source.
|
|
9427
9956
|
error ??= node._x?._error;
|
|
9428
|
-
|
|
9957
|
+
source = error?.source ?? node;
|
|
9958
|
+
if (source._flags & REACTIVE_DISPOSED) source = node;
|
|
9429
9959
|
const caught = unwrapStatusError(error);
|
|
9430
9960
|
setSignal(b._error, caught);
|
|
9431
|
-
reportClientError(caught, b._owner,
|
|
9432
|
-
}
|
|
9433
|
-
record(b, node);
|
|
9961
|
+
reportClientError(caught, b._owner, source);
|
|
9962
|
+
}
|
|
9963
|
+
record(b, node, source);
|
|
9964
|
+
// A loading fallback behind an error fallback is hidden: the error stays
|
|
9965
|
+
// the display until what the fallback waits on answers (D-39) — the
|
|
9966
|
+
// error boundary waits on it too.
|
|
9967
|
+
// (`_type`: STATUS_PENDING 1 < STATUS_ERROR 2 — an error boundary above a
|
|
9968
|
+
// loading one.)
|
|
9969
|
+
for (let p = b; (p = p._parent); ) if (p._fallback && p._type > b._type) record(p, node);
|
|
9434
9970
|
if (b._armed) flip(b);
|
|
9435
9971
|
// Already showing the fallback: one more reader to wait on changes
|
|
9436
9972
|
// nothing on screen. The fallback is instantiated once per show — a later
|
|
@@ -9463,16 +9999,20 @@ function redraw(b) {
|
|
|
9463
9999
|
function catchStatus(node, flags, error) {
|
|
9464
10000
|
if (flags === 0) {
|
|
9465
10001
|
// A status cleared. Judged by the node's status now, against each
|
|
9466
|
-
// boundary's own rule (`
|
|
10002
|
+
// boundary's own rule (`prune`'s mask) — the error path clears pending
|
|
9467
10003
|
// first and the node is still errored then. The last reader a fallback
|
|
9468
10004
|
// waited on settling re-derives the output now, in the same heap (a
|
|
9469
10005
|
// held reader's landing has joined its transaction, so the content
|
|
9470
10006
|
// lands with its run, not a round after). Others still waiting: nothing
|
|
9471
|
-
// on screen changes, the fallback stays as it is.
|
|
10007
|
+
// on screen changes, the fallback stays as it is. `node` here is an
|
|
10008
|
+
// effect reporting its own status. An error boundary records an effect
|
|
10009
|
+
// only while it is errored, and a staged error keeps STATUS_ERROR on a
|
|
10010
|
+
// node that had it (`stageError`), so `prune`'s CONFIG_STAGED_ERROR case
|
|
10011
|
+
// cannot reach this path (#4364).
|
|
9472
10012
|
if (collecting.size !== 0)
|
|
9473
10013
|
for (let b = boundaryOf(node); b; b = b._parent)
|
|
9474
10014
|
if (
|
|
9475
|
-
!(node
|
|
10015
|
+
!(b._readers.get(node)?._statusFlags & (b._type | STATUS_PENDING)) &&
|
|
9476
10016
|
b._readers.delete(node) &&
|
|
9477
10017
|
b._readers.size === 0
|
|
9478
10018
|
) {
|
|
@@ -9541,16 +10081,28 @@ function release(b) {
|
|
|
9541
10081
|
* that landed but is held (its value staged in a transaction) is not: the
|
|
9542
10082
|
* content reveals at the commit, with its run. The tree is: the output reads
|
|
9543
10083
|
* it and enters the transaction itself (its committed value shows until the
|
|
9544
|
-
* landing).
|
|
9545
|
-
|
|
10084
|
+
* landing). The statuses that keep a reader: pending — and its error, for an
|
|
10085
|
+
* error boundary (`_type` is the status it catches) — on the reader or its
|
|
10086
|
+
* source. A reader pending again after the source that threw answered (it
|
|
10087
|
+
* re-asks through a memo that carried the error) has no new answer yet: the
|
|
10088
|
+
* error stays the display (D-39: a pending is not a value; async-gen 91501
|
|
10089
|
+
* #1831). */
|
|
9546
10090
|
function prune(b, pass) {
|
|
9547
|
-
const mask =
|
|
9548
|
-
for (const r of b._readers) {
|
|
9549
|
-
if (r._flags & REACTIVE_DISPOSED)
|
|
9550
|
-
|
|
10091
|
+
const mask = b._type | STATUS_PENDING;
|
|
10092
|
+
for (const [r, s] of b._readers) {
|
|
10093
|
+
if (!((r._flags | s._flags) & REACTIVE_DISPOSED)) {
|
|
10094
|
+
// An error staged on its source (D-41, `stageError`) is not settled:
|
|
10095
|
+
// it publishes with the commit. A reader that threw it now (a lane
|
|
10096
|
+
// pass, a reader of a node born staged) reached the boundary first
|
|
10097
|
+
// (#4364).
|
|
9551
10098
|
if (
|
|
9552
|
-
(r
|
|
9553
|
-
(
|
|
10099
|
+
(r._statusFlags | s._statusFlags) & mask ||
|
|
10100
|
+
(mask & STATUS_ERROR && s._config & CONFIG_STAGED_ERROR)
|
|
10101
|
+
)
|
|
10102
|
+
continue;
|
|
10103
|
+
if (
|
|
10104
|
+
(s !== b._tree || b._armed) &&
|
|
10105
|
+
(s._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD
|
|
9554
10106
|
) {
|
|
9555
10107
|
// Landed, held: the content is that transaction's — the output's
|
|
9556
10108
|
// pass enters it and the reveal lands with the reader's run. The
|
|
@@ -9560,10 +10112,10 @@ function prune(b, pass) {
|
|
|
9560
10112
|
// the reader is committed (#3540: what a fresh boundary catches holds
|
|
9561
10113
|
// nothing outside it).
|
|
9562
10114
|
if (!pass || !b._initialized) continue;
|
|
9563
|
-
joinFuture(txOf(
|
|
10115
|
+
joinFuture(txOf(s));
|
|
9564
10116
|
}
|
|
9565
|
-
b._readers.delete(r);
|
|
9566
10117
|
}
|
|
10118
|
+
b._readers.delete(r);
|
|
9567
10119
|
}
|
|
9568
10120
|
return b._readers.size;
|
|
9569
10121
|
}
|
|
@@ -9595,12 +10147,17 @@ function boundarySeam() {
|
|
|
9595
10147
|
if (flags & REACTIVE_ZOMBIE) continue;
|
|
9596
10148
|
prune(b, false);
|
|
9597
10149
|
const o = b._output;
|
|
10150
|
+
// A fallback staged into the very hold its readers landed in folds back
|
|
10151
|
+
// (D-16, U-9): the content is ready when that hold commits (D-15). Not
|
|
10152
|
+
// only at the re-arm's own seam: readers pending then land, held, a
|
|
10153
|
+
// flush later (#4379 — the staged fallback otherwise commits with the
|
|
10154
|
+
// hold, and a quiet refresh then caught keeps it up, F-9).
|
|
9598
10155
|
if (
|
|
9599
|
-
b._armed &&
|
|
9600
10156
|
!b._initialized &&
|
|
9601
10157
|
o._config & CONFIG_HELD &&
|
|
9602
10158
|
[...b._readers].every(
|
|
9603
|
-
|
|
10159
|
+
([, s]) =>
|
|
10160
|
+
!(s._statusFlags & STATUS_PENDING) && s._config & CONFIG_HELD && txOf(s) === txOf(o)
|
|
9604
10161
|
)
|
|
9605
10162
|
) {
|
|
9606
10163
|
b._readers.clear();
|
|
@@ -9636,7 +10193,7 @@ function reportOutsideHold(b) {
|
|
|
9636
10193
|
for (let o = boundaryOf(reader); o; o = o._parent) if (o === b) return false;
|
|
9637
10194
|
return true;
|
|
9638
10195
|
};
|
|
9639
|
-
for (const r of b._readers) {
|
|
10196
|
+
for (const r of b._readers.keys()) {
|
|
9640
10197
|
const sources = r._x?._pendingSources;
|
|
9641
10198
|
if (sources === undefined) continue;
|
|
9642
10199
|
for (const source of sources) {
|
|
@@ -9707,7 +10264,7 @@ function arm(b) {
|
|
|
9707
10264
|
collecting.add(b);
|
|
9708
10265
|
const n = b._readers.size;
|
|
9709
10266
|
heldUnder(b._owner, flushTransaction, r => record(b, r));
|
|
9710
|
-
for (const r of b._readers) {
|
|
10267
|
+
for (const [r] of b._readers) {
|
|
9711
10268
|
if (
|
|
9712
10269
|
b._readers.size !== n ||
|
|
9713
10270
|
(r._statusFlags & STATUS_PENDING && !(r._flags & REACTIVE_DISPOSED))
|
|
@@ -9717,7 +10274,9 @@ function arm(b) {
|
|
|
9717
10274
|
// It is this boundary's alone now (B5, the nearest catcher): an outer
|
|
9718
10275
|
// re-armed by the same tick does not wait for it (D-14, U-4).
|
|
9719
10276
|
for (let a = b._parent; a !== null; a = a._parent)
|
|
9720
|
-
|
|
10277
|
+
// An outer error boundary keys its readers too, but waits on their
|
|
10278
|
+
// sources: a re-armed Loading taking the pending does not settle it.
|
|
10279
|
+
if (a._type & STATUS_PENDING) for (const [o] of b._readers) a._readers.delete(o);
|
|
9721
10280
|
flip(b);
|
|
9722
10281
|
redraw(b);
|
|
9723
10282
|
return;
|
|
@@ -9728,10 +10287,32 @@ function arm(b) {
|
|
|
9728
10287
|
* names a signal — nothing to re-run). */
|
|
9729
10288
|
function reset(b) {
|
|
9730
10289
|
// A snapshot: a source failing again is caught — and re-added — mid-walk.
|
|
9731
|
-
for (const r of
|
|
10290
|
+
for (const r of new Set(b._readers.values()))
|
|
9732
10291
|
if (r._fn !== undefined && !(r._flags & REACTIVE_DISPOSED)) recompute(r);
|
|
9733
10292
|
schedule();
|
|
9734
10293
|
}
|
|
10294
|
+
/**
|
|
10295
|
+
* Dev CHECK: the `on` dependencies evaluated to a function — `<Loading
|
|
10296
|
+
* on={route}>` or `on={() => route()}` passes the accessor itself, a stable
|
|
10297
|
+
* value that is never called, so nothing is read and the boundary never
|
|
10298
|
+
* re-arms. Checked on the first run only: the shape is static.
|
|
10299
|
+
*/
|
|
10300
|
+
function reportOnAccessor(b) {
|
|
10301
|
+
reportDiagnostic(
|
|
10302
|
+
emitDiagnostic(
|
|
10303
|
+
{
|
|
10304
|
+
code: "LOADING_ON_ACCESSOR",
|
|
10305
|
+
kind: "async",
|
|
10306
|
+
severity: "warn",
|
|
10307
|
+
message:
|
|
10308
|
+
"[LOADING_ON_ACCESSOR] A Loading boundary's `on` received a function. `on` takes the " +
|
|
10309
|
+
"dependency's value — `on={route()}`, or `on={[query(), page()]}` for several — and an " +
|
|
10310
|
+
"accessor passed instead is never called, so nothing is read and the boundary never re-arms."
|
|
10311
|
+
},
|
|
10312
|
+
b._owner
|
|
10313
|
+
)
|
|
10314
|
+
);
|
|
10315
|
+
}
|
|
9735
10316
|
function createBoundary(type, fn, show, onFn) {
|
|
9736
10317
|
if (!getOwner()) {
|
|
9737
10318
|
const message =
|
|
@@ -9753,7 +10334,7 @@ function createBoundary(type, fn, show, onFn) {
|
|
|
9753
10334
|
_owner: owner,
|
|
9754
10335
|
_tree: null,
|
|
9755
10336
|
_output: null,
|
|
9756
|
-
_readers: new
|
|
10337
|
+
_readers: new Map(),
|
|
9757
10338
|
_initialized: false,
|
|
9758
10339
|
_armed: false,
|
|
9759
10340
|
_armLane: null,
|
|
@@ -9783,7 +10364,8 @@ function createBoundary(type, fn, show, onFn) {
|
|
|
9783
10364
|
runWithOwner(owner, () =>
|
|
9784
10365
|
computed(() => {
|
|
9785
10366
|
try {
|
|
9786
|
-
onFn();
|
|
10367
|
+
const deps = onFn();
|
|
10368
|
+
if (true && !mounted && typeof deps === "function") reportOnAccessor(b);
|
|
9787
10369
|
} catch (e) {
|
|
9788
10370
|
if (!(e instanceof NotReadyError)) {
|
|
9789
10371
|
if (!globalQueue.notify(getOwner(), STATUS_ERROR, STATUS_ERROR, e)) {
|
|
@@ -9954,12 +10536,14 @@ function createBoundary(type, fn, show, onFn) {
|
|
|
9954
10536
|
return accessor(output);
|
|
9955
10537
|
}
|
|
9956
10538
|
function fallback(b) {
|
|
10539
|
+
// (The output's pass.)
|
|
10540
|
+
const out = getOwner();
|
|
9957
10541
|
// The swap a display-ahead re-arm asked for: this pass is the lane's — a
|
|
9958
10542
|
// lane read by construction (REACTIVE_LANE_READ: `recompute`'s tail keeps
|
|
9959
10543
|
// the pass in the lane).
|
|
9960
10544
|
if (b._lane !== null) {
|
|
9961
10545
|
setPassLane(b._lane);
|
|
9962
|
-
|
|
10546
|
+
out._flags |= REACTIVE_LANE_READ;
|
|
9963
10547
|
b._lane = null;
|
|
9964
10548
|
}
|
|
9965
10549
|
// Observe: a loading boundary starts showing its fallback — the swap lands
|
|
@@ -9987,11 +10571,18 @@ function fallback(b) {
|
|
|
9987
10571
|
// outside the boundary, as the output is: its errors and runs are the
|
|
9988
10572
|
// enclosing boundary's — with the output's id, so what it builds gets the
|
|
9989
10573
|
// ids a build under the output would; disposed once content is on screen.
|
|
9990
|
-
if (b._fb === null)
|
|
9991
|
-
const out = getOwner();
|
|
10574
|
+
if (b._fb === null)
|
|
9992
10575
|
b._fb = runWithOwner(out._parent, () => computed(() => b._show(b), { id: out.id }));
|
|
9993
|
-
|
|
9994
|
-
|
|
10576
|
+
// The swap is structure (#3540): what the fallback reads ahead — a
|
|
10577
|
+
// verdict (`latest()`), a lane — is the fallback's display-ahead, not the
|
|
10578
|
+
// swap's: the output's pass stays where it was (F-6 stays inside the
|
|
10579
|
+
// fallback, #4337).
|
|
10580
|
+
const p = passLane;
|
|
10581
|
+
const f = out._flags;
|
|
10582
|
+
const v = read(b._fb);
|
|
10583
|
+
setPassLane(p);
|
|
10584
|
+
out._flags ^= (out._flags ^ f) & (REACTIVE_PROBED | REACTIVE_LANE_READ);
|
|
10585
|
+
return v;
|
|
9995
10586
|
}
|
|
9996
10587
|
const ERROR_SIGNAL = { ownedWrite: true, _noSnapshot: true };
|
|
9997
10588
|
/**
|