@solidjs/signals 2.0.0-rc.0 → 2.0.0-rc.2
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/README.md +1 -1
- package/dist/dev.js +4287 -2803
- package/dist/node.cjs +4563 -3756
- package/dist/prod/affects.js +16 -16
- package/dist/prod/boundaries.js +90 -90
- package/dist/prod/core/action.js +3 -3
- package/dist/prod/core/async.js +234 -178
- package/dist/prod/core/constants.js +39 -1
- package/dist/prod/core/core.js +362 -243
- package/dist/prod/core/effect.js +56 -56
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +65 -54
- package/dist/prod/core/heap.js +47 -39
- package/dist/prod/core/invariants.js +3 -2
- package/dist/prod/core/lanes.js +41 -34
- package/dist/prod/core/optimistic.js +78 -58
- package/dist/prod/core/owner.js +101 -100
- package/dist/prod/core/scheduler.js +249 -192
- package/dist/prod/core/verdict.js +185 -78
- package/dist/prod/index.js +7 -7
- package/dist/prod/map.js +108 -106
- package/dist/prod/signals.js +20 -1
- package/dist/prod/store/index.js +36 -0
- package/dist/prod/store/next/optimistic.js +385 -0
- package/dist/prod/store/next/projection.js +172 -0
- package/dist/prod/store/next/reconcile.js +331 -0
- package/dist/prod/store/next/store.js +1499 -0
- package/dist/prod/store/next/target.js +20 -0
- package/dist/prod/store/store.js +126 -882
- package/dist/prod/store/utils.js +59 -186
- package/dist/types/core/attribution-hooks.d.ts +52 -0
- package/dist/types/core/attribution.d.ts +186 -0
- package/dist/types/core/constants.d.ts +26 -0
- package/dist/types/core/core.d.ts +16 -1
- package/dist/types/core/dev.d.ts +20 -3
- package/dist/types/core/graph.d.ts +1 -0
- package/dist/types/core/lanes.d.ts +4 -16
- package/dist/types/core/scheduler.d.ts +12 -2
- package/dist/types/core/types.d.ts +85 -41
- package/dist/types/signals.d.ts +19 -0
- package/dist/types/store/index.d.ts +15 -5
- package/dist/types/store/next/optimistic.d.ts +20 -0
- package/dist/types/store/next/projection.d.ts +8 -0
- package/dist/types/store/next/reconcile.d.ts +3 -0
- package/dist/types/store/next/store.d.ts +77 -0
- package/dist/types/store/next/target.d.ts +117 -0
- package/dist/types/store/store.d.ts +26 -101
- package/dist/types/store/utils.d.ts +0 -40
- package/dist/types-cjs/core/attribution-hooks.d.cts +52 -0
- package/dist/types-cjs/core/attribution.d.cts +186 -0
- package/dist/types-cjs/core/constants.d.cts +26 -0
- package/dist/types-cjs/core/core.d.cts +16 -1
- package/dist/types-cjs/core/dev.d.cts +20 -3
- package/dist/types-cjs/core/graph.d.cts +1 -0
- package/dist/types-cjs/core/lanes.d.cts +4 -16
- package/dist/types-cjs/core/scheduler.d.cts +12 -2
- package/dist/types-cjs/core/types.d.cts +85 -41
- package/dist/types-cjs/signals.d.cts +19 -0
- package/dist/types-cjs/store/index.d.cts +15 -5
- package/dist/types-cjs/store/next/optimistic.d.cts +20 -0
- package/dist/types-cjs/store/next/projection.d.cts +8 -0
- package/dist/types-cjs/store/next/reconcile.d.cts +3 -0
- package/dist/types-cjs/store/next/store.d.cts +77 -0
- package/dist/types-cjs/store/next/target.d.cts +117 -0
- package/dist/types-cjs/store/store.d.cts +26 -101
- package/dist/types-cjs/store/utils.d.cts +0 -40
- package/package.json +2 -1
- package/dist/prod/store/optimistic.js +0 -217
- package/dist/prod/store/projection.js +0 -231
- package/dist/prod/store/reconcile.js +0 -707
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { computed, ext, setSignal, isEqual } from "../../core/core.js";
|
|
2
|
+
|
|
3
|
+
import { CONFIG_AUTO_DISPOSE, STATUS_PENDING, NOT_PENDING, unwrapOverride, CONFIG_OPTIMISTIC } from "../../core/constants.js";
|
|
4
|
+
|
|
5
|
+
import { GlobalQueue, insertSubs, schedule, globalQueue } from "../../core/scheduler.js";
|
|
6
|
+
|
|
7
|
+
import "../../core/invariants.js";
|
|
8
|
+
|
|
9
|
+
import "../../core/verdict.js";
|
|
10
|
+
|
|
11
|
+
import "../../core/effect.js";
|
|
12
|
+
|
|
13
|
+
import { installOptimisticEngine } from "../../core/optimistic.js";
|
|
14
|
+
|
|
15
|
+
import { $TARGET, markRawIngest, setNextOptimisticViewResolver, isWrappable, rawValuesUsed, isRawValue } from "../store.js";
|
|
16
|
+
|
|
17
|
+
import { runProjectionComputedNext } from "./projection.js";
|
|
18
|
+
|
|
19
|
+
import { wrapNext, runAuthoritative, storeSetterNext, hasActiveOverride, unwrapValue, getNode, getHasNode, targetsEqual, getKeySetNode, bumpDeep } from "./store.js";
|
|
20
|
+
|
|
21
|
+
import { setOptHooks } from "./target.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Store rewrite — optimistic stores (§3/§7, RUL-3): no store-side layer, no
|
|
25
|
+
* backup snapshots. Nodes in an optimistic family are ARMED core signals
|
|
26
|
+
* (`_overrideValue` slot), so every user write rides the engine's
|
|
27
|
+
* optimisticWrite — per-transaction ownership, entanglement, reverts, and
|
|
28
|
+
* flash-at-flush are inherited, not reimplemented. Membership edits live on
|
|
29
|
+
* armed presence nodes (the §6 overlay), so structural optimism reverts with
|
|
30
|
+
* the same per-transaction granularity (FINDING-2's fix by construction).
|
|
31
|
+
*
|
|
32
|
+
* Derived form = an optimistic projection: the derive's recompute and its
|
|
33
|
+
* async commits run under projectionWriteActive (authoritative landings
|
|
34
|
+
* commit silently beneath any active overrides). The transitionBlocked
|
|
35
|
+
* store-half (#2951) is installed here for next-shaped targets, chaining the
|
|
36
|
+
* legacy/engine checks.
|
|
37
|
+
*/ let blockedInstalled = false;
|
|
38
|
+
|
|
39
|
+
function installNextBlockedHalf() {
|
|
40
|
+
if (blockedInstalled) return;
|
|
41
|
+
blockedInstalled = true;
|
|
42
|
+
// Late-bind the optimistic machinery into the plain store/reconcile paths
|
|
43
|
+
// (all call sites are fam?.opt-gated, so this always runs first) and the
|
|
44
|
+
// affects witness's view resolver.
|
|
45
|
+
setOptHooks({
|
|
46
|
+
notifyOptimisticWrites: notifyOptimisticWrites,
|
|
47
|
+
optimisticView: optimisticView,
|
|
48
|
+
applyTentative: applyTentative
|
|
49
|
+
});
|
|
50
|
+
setNextOptimisticViewResolver((e, t) => optimisticView(e, t));
|
|
51
|
+
// Scheduler flush tails call _clearOptimisticStores whenever tracked
|
|
52
|
+
// stores exist; next has no layer to clear — reverts are engine-native —
|
|
53
|
+
// so the hook only empties the batch set.
|
|
54
|
+
if (!GlobalQueue.Vt) {
|
|
55
|
+
GlobalQueue.Vt = e => {
|
|
56
|
+
e.clear();
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const e = GlobalQueue.dn;
|
|
60
|
+
GlobalQueue.dn = t => {
|
|
61
|
+
for (const e of t.cn) {
|
|
62
|
+
const t = e?.[$TARGET];
|
|
63
|
+
const n = t?.fam?.node;
|
|
64
|
+
// The hold exists to keep optimistic state alive until the store's own
|
|
65
|
+
// truth lands (#2951). Once the family carries NO live overrides (a
|
|
66
|
+
// landing consumed them, or they never existed), a pending firewall is
|
|
67
|
+
// no reason to park the transaction — blocking then leaks it forever
|
|
68
|
+
// when the in-flight question is never answered (undisposed fixtures).
|
|
69
|
+
if (n != null && n.S & STATUS_PENDING && familyHasLiveOverrides(t.fam)) return true;
|
|
70
|
+
}
|
|
71
|
+
return e(t);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function familyHasLiveOverrides(e) {
|
|
76
|
+
const t = e.overlaid;
|
|
77
|
+
if (t === undefined || t.size === 0) return false;
|
|
78
|
+
for (const e of t) {
|
|
79
|
+
for (const t of [ e.n, e.h ]) {
|
|
80
|
+
if (t === null) continue;
|
|
81
|
+
for (const e of Reflect.ownKeys(t)) {
|
|
82
|
+
const n = t[e];
|
|
83
|
+
if (n.o?.De !== undefined && n.o?.De !== NOT_PENDING) return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (e.k !== null && e.k.o?.De !== undefined && e.k.o?.De !== NOT_PENDING) return true;
|
|
87
|
+
}
|
|
88
|
+
t.clear();
|
|
89
|
+
// nothing live — drop the bookkeeping
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createOptimisticStoreNext(e, t, n) {
|
|
94
|
+
// Engine first (armed nodes need optimisticWrite installed before any
|
|
95
|
+
// node exists), then the next-shape hooks.
|
|
96
|
+
installOptimisticEngine();
|
|
97
|
+
installNextBlockedHalf();
|
|
98
|
+
const i = typeof e === "function";
|
|
99
|
+
if (!i && n === undefined) n = t;
|
|
100
|
+
const o = i ? t : e;
|
|
101
|
+
const r = {
|
|
102
|
+
map: new WeakMap,
|
|
103
|
+
node: null,
|
|
104
|
+
shallow: !!n?.shallow,
|
|
105
|
+
opt: true
|
|
106
|
+
};
|
|
107
|
+
const s = wrapNext(o, null, null, r);
|
|
108
|
+
r.px = s;
|
|
109
|
+
if (r.shallow) {
|
|
110
|
+
s[$TARGET].s = true;
|
|
111
|
+
markRawIngest(o);
|
|
112
|
+
}
|
|
113
|
+
if (i) {
|
|
114
|
+
const t = e;
|
|
115
|
+
// Async commits land outside the computed's sync body — re-apply the
|
|
116
|
+
// authoritative-write posture there too. Landings consume the family's
|
|
117
|
+
// tentative overrides (RUL-2: visible landed truth replaces optimism) —
|
|
118
|
+
// both the reconcile-channel commit and per-op post-await draft writes.
|
|
119
|
+
const consume = () => consumeOverridesNext(r);
|
|
120
|
+
const wrapCommit = e => {
|
|
121
|
+
runAuthoritative(e);
|
|
122
|
+
consume();
|
|
123
|
+
};
|
|
124
|
+
let i;
|
|
125
|
+
if (n?.seedLoadingValue) i = {
|
|
126
|
+
loadingValue: undefined
|
|
127
|
+
};
|
|
128
|
+
const o = computed(() => {
|
|
129
|
+
runAuthoritative(() => runProjectionComputedNext(s, t, n?.key === undefined ? "id" : n.key, wrapCommit, consume));
|
|
130
|
+
}, i);
|
|
131
|
+
o.T &= ~CONFIG_AUTO_DISPOSE;
|
|
132
|
+
r.node = o;
|
|
133
|
+
}
|
|
134
|
+
return [ s, e => storeSetterNext(s, e) ];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---- optimistic-only store machinery (moved from next/store.ts /
|
|
138
|
+
// next/reconcile.ts so plain-store bundles tree-shake it) ----
|
|
139
|
+
/** Diff the draft against the current OPTIMISTIC VIEW (committed + active
|
|
140
|
+
* overrides — the same view the draft was seeded from) and emit engine writes
|
|
141
|
+
* for exactly the changed keys. Visible-view diffing keeps no-op writes from
|
|
142
|
+
* entangling lanes (RUL-10 / opt R38). */ function notifyOptimisticWrites(e, t) {
|
|
143
|
+
// A bare write while the store's own truth is in flight rides THAT
|
|
144
|
+
// transaction (#2951, legacy parity): entangle the firewall's transition so
|
|
145
|
+
// the override survives until the refetch settles instead of flash-reverting
|
|
146
|
+
// at plain flush end. The blocked-check store-half keeps that transaction
|
|
147
|
+
// from settling while the firewall is pending.
|
|
148
|
+
const n = e.fam?.node;
|
|
149
|
+
if (n?._e) globalQueue.initTransition(n._e);
|
|
150
|
+
const i = e.v;
|
|
151
|
+
const visible = (t, n) => {
|
|
152
|
+
const i = e.n?.[t];
|
|
153
|
+
return i !== undefined && hasActiveOverride(i) ? unwrapOverride(i.o?.De) : n;
|
|
154
|
+
};
|
|
155
|
+
const visiblePresent = t => {
|
|
156
|
+
const n = e.h?.[t];
|
|
157
|
+
return n !== undefined && hasActiveOverride(n) ? !!unwrapOverride(n.o?.De) : t in i;
|
|
158
|
+
};
|
|
159
|
+
let o = false;
|
|
160
|
+
const r = Array.isArray(t);
|
|
161
|
+
for (const n of Reflect.ownKeys(t)) {
|
|
162
|
+
if (r && n === "length") continue;
|
|
163
|
+
const s = unwrapValue(t[n]);
|
|
164
|
+
if (!visiblePresent(n)) {
|
|
165
|
+
// Optimistic add: value node + presence node + membership bump.
|
|
166
|
+
setSignal(getNode(e, n, i[n]), () => s);
|
|
167
|
+
setSignal(getHasNode(e, n, n in i), true);
|
|
168
|
+
o = true;
|
|
169
|
+
} else {
|
|
170
|
+
const t = visible(n, i[n]);
|
|
171
|
+
if (!isEqual(t, s) && !targetsEqual(t, s)) {
|
|
172
|
+
setSignal(getNode(e, n, t), () => s);
|
|
173
|
+
if (r) o = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const n of Reflect.ownKeys(i)) {
|
|
178
|
+
if (r && n === "length") continue;
|
|
179
|
+
if (n in t || !visiblePresent(n)) continue;
|
|
180
|
+
// Optimistic delete: node reads undefined, presence flips, membership bumps.
|
|
181
|
+
setSignal(getNode(e, n, i[n]), () => undefined);
|
|
182
|
+
setSignal(getHasNode(e, n, true), false);
|
|
183
|
+
o = true;
|
|
184
|
+
}
|
|
185
|
+
if (r) {
|
|
186
|
+
const n = visible("length", i.length);
|
|
187
|
+
if (n !== t.length) {
|
|
188
|
+
setSignal(getNode(e, "length", n), () => t.length);
|
|
189
|
+
o = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (o) setSignal(getKeySetNode(e), e => e + 1);
|
|
193
|
+
// Deep-witness: optimistic value writes notify deep() subscribers too
|
|
194
|
+
// (structural ones already ride the key-set bump above).
|
|
195
|
+
bumpDeep(e);
|
|
196
|
+
// Discard the draft — committed raw is untouched (revert target by
|
|
197
|
+
// construction). Register the root store for the scheduler's settle hooks
|
|
198
|
+
// and the target for landing consumption (RUL-2).
|
|
199
|
+
e.pb = null;
|
|
200
|
+
(e.fam.overlaid ??= new Set).add(e);
|
|
201
|
+
GlobalQueue.Nn?.(e.fam.px ?? e.px);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Landing consumption (RUL-2): fresh authoritative data supersedes every
|
|
206
|
+
* tentative override in the family. Mirrors legacy clearProjectionOverride —
|
|
207
|
+
* drop the override, clear lane/ownership, notify subscribers whose visible
|
|
208
|
+
* value changes (reversion effects go to regular queues via the projection
|
|
209
|
+
* write posture the caller holds).
|
|
210
|
+
*/ function consumeOverridesNext(e) {
|
|
211
|
+
const t = e.overlaid;
|
|
212
|
+
if (t === undefined || t.size === 0) return;
|
|
213
|
+
runAuthoritative(() => {
|
|
214
|
+
for (const e of t) {
|
|
215
|
+
const drop = (e, t) => {
|
|
216
|
+
if (!hasActiveOverride(e)) return;
|
|
217
|
+
const n = unwrapOverride(e.o?.De);
|
|
218
|
+
// Full legacy reset (clearOptimisticOverride parity): the landing is
|
|
219
|
+
// authoritative NOW — fold committed into the node directly instead
|
|
220
|
+
// of riding a transaction's commit (whose queues may be stashed with
|
|
221
|
+
// the transaction parked; the wake would strand until it settles).
|
|
222
|
+
ext(e).De = NOT_PENDING;
|
|
223
|
+
e.T |= CONFIG_OPTIMISTIC;
|
|
224
|
+
const i = e.o;
|
|
225
|
+
if (i) {
|
|
226
|
+
i.Et = null;
|
|
227
|
+
i.Be = undefined;
|
|
228
|
+
}
|
|
229
|
+
e.Pe = NOT_PENDING;
|
|
230
|
+
e.be = t;
|
|
231
|
+
if (!e.Ue || !e.Ue(n, t)) {
|
|
232
|
+
insertSubs(e, true);
|
|
233
|
+
schedule();
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
// Landing consumes STRUCTURAL optimism only (legacy layer parity):
|
|
237
|
+
// membership edits, array length, and the value overrides written WITH
|
|
238
|
+
// them (a key carrying an active presence override is an add/delete —
|
|
239
|
+
// classified BEFORE the adoption may have made the key exist in landed
|
|
240
|
+
// data). A pure value override on a key the landing carries stays with
|
|
241
|
+
// its owning transaction (rapid-toggle contract: a live action's edit
|
|
242
|
+
// of an existing entity rides on top of landed truth).
|
|
243
|
+
const t = Array.isArray(e.v);
|
|
244
|
+
const n = e.h;
|
|
245
|
+
let i = null;
|
|
246
|
+
if (n !== null) {
|
|
247
|
+
for (const e of Reflect.ownKeys(n)) {
|
|
248
|
+
if (hasActiveOverride(n[e])) (i ??= new Set).add(e);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const o = e.n;
|
|
252
|
+
if (o !== null) {
|
|
253
|
+
for (const n of Reflect.ownKeys(o)) {
|
|
254
|
+
const r = i?.has(n) || !(n in e.v) || t && n === "length";
|
|
255
|
+
if (!r) continue;
|
|
256
|
+
drop(o[n], t && n === "length" ? e.v.length : e.v[n]);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (n !== null) {
|
|
260
|
+
for (const t of Reflect.ownKeys(n)) drop(n[t], t in e.v);
|
|
261
|
+
}
|
|
262
|
+
if (e.k !== null && hasActiveOverride(e.k)) {
|
|
263
|
+
ext(e.k).De = NOT_PENDING;
|
|
264
|
+
e.k.T |= CONFIG_OPTIMISTIC;
|
|
265
|
+
const t = e.k.o;
|
|
266
|
+
if (t) {
|
|
267
|
+
t.Et = null;
|
|
268
|
+
t.Be = undefined;
|
|
269
|
+
}
|
|
270
|
+
insertSubs(e.k, true);
|
|
271
|
+
schedule();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
t.clear();
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Optimistic-view composition for snapshot/deep (O1: snapshot is the CURRENT
|
|
279
|
+
* view, lane values included; a fresh copy per call during pending windows —
|
|
280
|
+
* RUL-12). Returns `src` untouched when no override is active on `t`. */ function optimisticView(e, t) {
|
|
281
|
+
if (e.fam?.opt !== true) return t;
|
|
282
|
+
let n = null;
|
|
283
|
+
const ensure = () => n ??= Array.isArray(t) ? [ ...t ] : {
|
|
284
|
+
...t
|
|
285
|
+
};
|
|
286
|
+
const i = e.n;
|
|
287
|
+
if (i !== null) {
|
|
288
|
+
for (const e of Reflect.ownKeys(i)) {
|
|
289
|
+
const n = i[e];
|
|
290
|
+
if (!hasActiveOverride(n)) continue;
|
|
291
|
+
const o = unwrapOverride(n.o?.De);
|
|
292
|
+
if (e === "length" && Array.isArray(t)) {
|
|
293
|
+
if (t.length !== o) ensure().length = o;
|
|
294
|
+
} else if (!isEqual(t[e], o)) ensure()[e] = o;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const o = e.h;
|
|
298
|
+
if (o !== null) {
|
|
299
|
+
for (const e of Reflect.ownKeys(o)) {
|
|
300
|
+
const i = o[e];
|
|
301
|
+
if (!hasActiveOverride(i)) continue;
|
|
302
|
+
const r = !!unwrapOverride(i.o?.De);
|
|
303
|
+
if (!r && e in (n ?? t)) delete ensure()[e];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return n ?? t;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function applyTentative(e, t, n) {
|
|
310
|
+
const i = e.pb ?? e.v;
|
|
311
|
+
const o = optimisticView(e, i);
|
|
312
|
+
const r = e.fam.map;
|
|
313
|
+
const s = Array.isArray(t);
|
|
314
|
+
if (Array.isArray(o) !== s) return;
|
|
315
|
+
// kind change at root: flat overrides below
|
|
316
|
+
const l = [];
|
|
317
|
+
const u = s ? [ ...t ] : shallowWithSymbols(t);
|
|
318
|
+
const match = (e, t) => {
|
|
319
|
+
if (!isWrappable(e) || !isWrappable(t)) return null;
|
|
320
|
+
if (rawValuesUsed && (isRawValue(e) || isRawValue(t))) return null;
|
|
321
|
+
if (Array.isArray(e) !== Array.isArray(t)) return null;
|
|
322
|
+
if (n) {
|
|
323
|
+
const i = n(e);
|
|
324
|
+
const o = n(t);
|
|
325
|
+
if (i !== undefined && o !== undefined && i !== o) return null;
|
|
326
|
+
}
|
|
327
|
+
return r.get(unwrapValue(e)) ?? null;
|
|
328
|
+
};
|
|
329
|
+
if (s) {
|
|
330
|
+
const e = o;
|
|
331
|
+
let i = null;
|
|
332
|
+
for (let o = 0; o < t.length; o++) {
|
|
333
|
+
const r = t[o];
|
|
334
|
+
if (!isWrappable(r)) continue;
|
|
335
|
+
let s;
|
|
336
|
+
if (n) {
|
|
337
|
+
const t = n(r);
|
|
338
|
+
if (t !== undefined) {
|
|
339
|
+
if (i === null) {
|
|
340
|
+
i = new Map;
|
|
341
|
+
for (let t = 0; t < e.length; t++) {
|
|
342
|
+
const o = unwrapValue(e[t]);
|
|
343
|
+
if (isWrappable(o)) {
|
|
344
|
+
const e = n(o);
|
|
345
|
+
if (e !== undefined && !i.has(e)) i.set(e, o);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
s = i.get(t);
|
|
350
|
+
} else s = unwrapValue(e[o]);
|
|
351
|
+
} else s = unwrapValue(e[o]);
|
|
352
|
+
const c = match(s, r);
|
|
353
|
+
if (c !== null) {
|
|
354
|
+
// Keep the existing row in the slot (identity preserved); recurse.
|
|
355
|
+
u[o] = unwrapValue(s);
|
|
356
|
+
l.push([ c, r ]);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
for (const e of Reflect.ownKeys(t)) {
|
|
361
|
+
const n = unwrapValue(o[e]);
|
|
362
|
+
const i = t[e];
|
|
363
|
+
const r = match(n, i);
|
|
364
|
+
if (r !== null) {
|
|
365
|
+
u[e] = n;
|
|
366
|
+
l.push([ r, i ]);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// Flat overrides for this level (adds, removals, moved slots, length, leaf
|
|
371
|
+
// values) — preserve any live user draft backing across the call.
|
|
372
|
+
const c = e.pb;
|
|
373
|
+
e.pb = null;
|
|
374
|
+
notifyOptimisticWrites(e, u);
|
|
375
|
+
e.pb = c;
|
|
376
|
+
for (let e = 0; e < l.length; e++) applyTentative(l[e][0], unwrapValue(l[e][1]), n);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function shallowWithSymbols(e) {
|
|
380
|
+
const t = {};
|
|
381
|
+
for (const n of Reflect.ownKeys(e)) t[n] = e[n];
|
|
382
|
+
return t;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export { consumeOverridesNext, createOptimisticStoreNext, notifyOptimisticWrites, optimisticView };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { computed, suppressComputedRecompute } from "../../core/core.js";
|
|
2
|
+
|
|
3
|
+
import { getOwner } from "../../core/owner.js";
|
|
4
|
+
|
|
5
|
+
import { setProjectionWriteActive, projectionWriteActive } from "../../core/scheduler.js";
|
|
6
|
+
|
|
7
|
+
import { handleAsync } from "../../core/async.js";
|
|
8
|
+
|
|
9
|
+
import "../../core/verdict.js";
|
|
10
|
+
|
|
11
|
+
import "../../core/effect.js";
|
|
12
|
+
|
|
13
|
+
import { CONFIG_AUTO_DISPOSE } from "../../core/constants.js";
|
|
14
|
+
|
|
15
|
+
import { $TARGET, markRawIngest, STORE_VALUE, setWriteOverride } from "../store.js";
|
|
16
|
+
|
|
17
|
+
import { reconcileNextState } from "./reconcile.js";
|
|
18
|
+
|
|
19
|
+
import { wrapNext, storeSetterNext } from "./store.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Store rewrite — projections (§7/§7b): a projection is a computed store.
|
|
23
|
+
* The derive runs inside a computed whose recompute merges its output into
|
|
24
|
+
* the projection's backing through the adoption channel (replace-mode root:
|
|
25
|
+
* entity changes merge in place, the root proxy is stable for life). Children
|
|
26
|
+
* wrap into the projection's own FAMILY (writes land here, never in a source
|
|
27
|
+
* family), and every family node carries the projection computed as its
|
|
28
|
+
* firewall — reads link the derive's status and lifecycle natively. The §6c
|
|
29
|
+
* status gate in the traps makes an uninitialized async derive's seed
|
|
30
|
+
* unobservable through every read surface.
|
|
31
|
+
*
|
|
32
|
+
* Mirrors the legacy runProjectionComputed shape (shadow runs for open
|
|
33
|
+
* loading windows, handleAsync landings, commit-through-setter) on next
|
|
34
|
+
* primitives; the generic draft write-traps are reused from the legacy
|
|
35
|
+
* module unchanged.
|
|
36
|
+
*/ function createWriteTraps(e, t) {
|
|
37
|
+
// Save/restore, never hard-reset: the draft can be driven from inside an
|
|
38
|
+
// enclosing authoritative-write scope (next-store optimistic derives), and
|
|
39
|
+
// a hard `false` would clobber it mid-derive.
|
|
40
|
+
const r = {
|
|
41
|
+
get(e, t) {
|
|
42
|
+
let o;
|
|
43
|
+
const i = projectionWriteActive;
|
|
44
|
+
setWriteOverride(true);
|
|
45
|
+
setProjectionWriteActive(true);
|
|
46
|
+
try {
|
|
47
|
+
o = e[t];
|
|
48
|
+
} finally {
|
|
49
|
+
setWriteOverride(false);
|
|
50
|
+
setProjectionWriteActive(i);
|
|
51
|
+
}
|
|
52
|
+
if (t === $TARGET) return o;
|
|
53
|
+
return typeof o === "object" && o !== null ? new Proxy(o, r) : o;
|
|
54
|
+
},
|
|
55
|
+
has(e, t) {
|
|
56
|
+
let r;
|
|
57
|
+
const o = projectionWriteActive;
|
|
58
|
+
setWriteOverride(true);
|
|
59
|
+
setProjectionWriteActive(true);
|
|
60
|
+
try {
|
|
61
|
+
r = t in e;
|
|
62
|
+
} finally {
|
|
63
|
+
setWriteOverride(false);
|
|
64
|
+
setProjectionWriteActive(o);
|
|
65
|
+
}
|
|
66
|
+
return r;
|
|
67
|
+
},
|
|
68
|
+
set(r, o, i) {
|
|
69
|
+
if (e && !e()) return true;
|
|
70
|
+
const n = projectionWriteActive;
|
|
71
|
+
setWriteOverride(true);
|
|
72
|
+
setProjectionWriteActive(true);
|
|
73
|
+
try {
|
|
74
|
+
r[o] = i;
|
|
75
|
+
t?.();
|
|
76
|
+
} finally {
|
|
77
|
+
setWriteOverride(false);
|
|
78
|
+
setProjectionWriteActive(n);
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
},
|
|
82
|
+
deleteProperty(r, o) {
|
|
83
|
+
if (e && !e()) return true;
|
|
84
|
+
const i = projectionWriteActive;
|
|
85
|
+
setWriteOverride(true);
|
|
86
|
+
setProjectionWriteActive(true);
|
|
87
|
+
try {
|
|
88
|
+
delete r[o];
|
|
89
|
+
t?.();
|
|
90
|
+
} finally {
|
|
91
|
+
setWriteOverride(false);
|
|
92
|
+
setProjectionWriteActive(i);
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return r;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function createProjectionNextInternal(e, t, r) {
|
|
101
|
+
const o = {
|
|
102
|
+
map: new WeakMap,
|
|
103
|
+
node: null,
|
|
104
|
+
shallow: !!r?.shallow
|
|
105
|
+
};
|
|
106
|
+
const i = wrapNext(t, null, null, o);
|
|
107
|
+
if (o.shallow) {
|
|
108
|
+
// Shallow projection: the root is the only wrapped level — slot values
|
|
109
|
+
// serve raw, ingests sticky raw-mark (same t.s machinery as plain).
|
|
110
|
+
i[$TARGET].s = true;
|
|
111
|
+
markRawIngest(t);
|
|
112
|
+
}
|
|
113
|
+
let n;
|
|
114
|
+
if (r?.seedLoadingValue) n = {
|
|
115
|
+
loadingValue: undefined
|
|
116
|
+
};
|
|
117
|
+
const c = computed(() => {
|
|
118
|
+
if (!o.node) o.node = getOwner();
|
|
119
|
+
runProjectionComputedNext(i, e, r?.key === undefined ? "id" : r.key);
|
|
120
|
+
}, n);
|
|
121
|
+
c.T &= ~CONFIG_AUTO_DISPOSE;
|
|
122
|
+
o.node = c;
|
|
123
|
+
return {
|
|
124
|
+
store: i,
|
|
125
|
+
node: c
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function createProjectionNext(e, t, r) {
|
|
130
|
+
return createProjectionNextInternal(e, t, r).store;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Derived writable store (legacy parity): a projection whose public setter
|
|
134
|
+
* masks the recompute for the tick (core R31 — the manual write wins over a
|
|
135
|
+
* same-flush dependency change). */ function createStoreDerivedNext(e, t, r) {
|
|
136
|
+
const {store: o, node: i} = createProjectionNextInternal(e, t, r);
|
|
137
|
+
return [ o, e => {
|
|
138
|
+
// Mark the projection as manually written before notifying nodes.
|
|
139
|
+
suppressComputedRecompute(i);
|
|
140
|
+
storeSetterNext(o, e);
|
|
141
|
+
} ];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function runProjectionComputedNext(e, t, r, o, i) {
|
|
145
|
+
const n = getOwner();
|
|
146
|
+
let c = false;
|
|
147
|
+
let s;
|
|
148
|
+
// Open loading window (seedLoadingValue): the observable store IS commit #0
|
|
149
|
+
// for the whole first flight — the derive works a detached shadow of the
|
|
150
|
+
// seed so draft writes cannot tear through to readers (#2988). Every commit
|
|
151
|
+
// point reconciles the shadow through the normal commit path.
|
|
152
|
+
const u = n.Ie ? JSON.parse(JSON.stringify(e[$TARGET][STORE_VALUE])) : null;
|
|
153
|
+
const l = new Proxy(e, createWriteTraps(() => !c || n.o?.Ee === s, i));
|
|
154
|
+
storeSetterNext(l, i => {
|
|
155
|
+
s = t(u ?? i);
|
|
156
|
+
c = true;
|
|
157
|
+
const commit = t => {
|
|
158
|
+
// Shadow run: commit a detached snapshot, never the shadow itself
|
|
159
|
+
// (adoption takes the value by identity — handing it the live shadow
|
|
160
|
+
// would fuse the draft to the observable store).
|
|
161
|
+
if (u && (t === undefined || t === u)) t = JSON.parse(JSON.stringify(u));
|
|
162
|
+
if (t === i || t === undefined) return;
|
|
163
|
+
const write = () => storeSetterNext(e, e => reconcileNextState(t, e, r, true), false);
|
|
164
|
+
o ? o(write) : write();
|
|
165
|
+
};
|
|
166
|
+
const l = handleAsync(n, s, commit);
|
|
167
|
+
if (!n.Ie) commit(l);
|
|
168
|
+
}, false);
|
|
169
|
+
return n;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export { createProjectionNext, createStoreDerivedNext, runProjectionComputedNext };
|