@solidjs/signals 2.0.0-rc.2 → 2.0.0-rc.3
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.js +120 -24
- package/dist/node.cjs +190 -99
- package/dist/prod/core/optimistic.js +37 -15
- package/dist/prod/core/scheduler.js +8 -8
- package/dist/prod/core/verdict.js +7 -0
- package/dist/prod/store/next/optimistic.js +3 -3
- package/dist/prod/store/next/projection.js +107 -45
- package/dist/types/core/invariants.d.ts +1 -1
- package/dist/types-cjs/core/invariants.d.cts +1 -1
- package/package.json +15 -15
- package/dist/types/store/optimistic.d.ts +0 -45
- package/dist/types/store/projection.d.ts +0 -70
- package/dist/types/store/reconcile.d.ts +0 -46
- package/dist/types-cjs/store/optimistic.d.cts +0 -45
- package/dist/types-cjs/store/projection.d.cts +0 -70
- package/dist/types-cjs/store/reconcile.d.cts +0 -46
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, CONFIG_HAS_LANE, OVERRIDE_UNDEFINED, STATUS_PENDING, REACTIVE_MANUAL_WRITE, REACTIVE_OPTIMISTIC_DIRTY, EFFECT_RENDER, EFFECT_USER } from "./constants.js";
|
|
1
|
+
import { NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, REACTIVE_DIRTY, REACTIVE_CHECK, CONFIG_HAS_LANE, OVERRIDE_UNDEFINED, STATUS_PENDING, REACTIVE_MANUAL_WRITE, REACTIVE_OPTIMISTIC_DIRTY, EFFECT_RENDER, EFFECT_USER } from "./constants.js";
|
|
2
2
|
|
|
3
3
|
import { ext, latestReadActive, currentOptimisticLane, stale } from "./core.js";
|
|
4
4
|
|
|
@@ -27,7 +27,14 @@ import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, schedule
|
|
|
27
27
|
const t = e.o?.De !== NOT_PENDING;
|
|
28
28
|
const i = t ? unwrapOverride(e.o?.De) : e.be;
|
|
29
29
|
if (typeof n === "function") n = n(i);
|
|
30
|
-
const u = !!(e.S & STATUS_UNINITIALIZED) ||
|
|
30
|
+
const u = !!(e.S & STATUS_UNINITIALIZED) ||
|
|
31
|
+
// A dirty node's _value is stale (its queued recompute hasn't run — e.g.
|
|
32
|
+
// a latest() shadow marked by the previous landing's companion snap), so
|
|
33
|
+
// equality against it must not swallow the write. Without this, a sync
|
|
34
|
+
// push returning the shadow to that stale value was dropped, the snap
|
|
35
|
+
// recompute then committed the parent's old value, and the banner showed
|
|
36
|
+
// the previous transition's target (#3041 follow-up).
|
|
37
|
+
!!((e.ie ?? 0) & (REACTIVE_DIRTY | REACTIVE_CHECK)) || !e.Ue || !e.Ue(i, n);
|
|
31
38
|
if (!u) {
|
|
32
39
|
// Same-value write with an active override still entangles the current
|
|
33
40
|
// action's transition — the hold must outlast all overlapping actions.
|
|
@@ -166,16 +173,31 @@ function cleanupCompletedLanes(e) {
|
|
|
166
173
|
* read()'s value selection under a lane: return the committed `_value` for
|
|
167
174
|
* optimistic/lane-assigned signals, stale-mode reads, and pending owners.
|
|
168
175
|
*/ function laneReadsCommitted(e, n, t) {
|
|
169
|
-
if (e.o?.De !== undefined || !!e.o?.Be || !!(n.S & STATUS_PENDING))
|
|
176
|
+
if (e.o?.De !== undefined || !!e.o?.Be || !!(n.S & STATUS_PENDING)) {
|
|
177
|
+
// The committed view hides a staged in-flight value that will promote
|
|
178
|
+
// silently (commitPendingNode never re-notifies). gatedRead records plain
|
|
179
|
+
// signals for replay at commit; async memos are excluded from it by the
|
|
180
|
+
// `_fn` check and reach here instead — a lane-assigned source whose async
|
|
181
|
+
// already settled (laneAsyncSettled keeps _optimisticLane) served its
|
|
182
|
+
// committed value to a reader that never re-ran after the landing, so a
|
|
183
|
+
// pending-gated branch stayed one value behind permanently (#3041
|
|
184
|
+
// follow-up). Record the reader under the same replay contract.
|
|
185
|
+
if (e.Pe !== NOT_PENDING) (activeTransition ?? globalQueue.k).ln.add(t);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
170
188
|
if (n === e && stale && t.o?.It !== e) {
|
|
171
|
-
// The committed view can hide a
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
|
|
189
|
+
// The committed view can hide a staged write (a lane member — even just
|
|
190
|
+
// an isPending companion flip — puts the reader "under a lane"). The
|
|
191
|
+
// staged value commits with no re-delivery (commitPendingNode never
|
|
192
|
+
// re-notifies), so record the reader for replay at commit — the same
|
|
193
|
+
// contract gatedRead provides (#2963). gatedRead itself only covers
|
|
194
|
+
// signal reads where the reading computed differs from the source; the
|
|
195
|
+
// owner === el memo/self read lands here instead. With a transaction
|
|
196
|
+
// active the staged value promotes silently at ITS landing, so record
|
|
197
|
+
// into the transaction (#3041 follow-up: a pending-gated branch that
|
|
198
|
+
// first read its async source during the landing flush stayed one value
|
|
199
|
+
// behind permanently); with none, into the ambient batch.
|
|
200
|
+
if (e.Pe !== NOT_PENDING) (activeTransition ?? globalQueue.k).ln.add(t);
|
|
179
201
|
return true;
|
|
180
202
|
}
|
|
181
203
|
return false;
|
|
@@ -250,16 +272,16 @@ function trackOptimisticStore(e) {
|
|
|
250
272
|
if (GlobalQueue.vt !== null) return;
|
|
251
273
|
GlobalQueue.vt = optimisticWrite;
|
|
252
274
|
GlobalQueue.fn = resolveOptimisticNodes;
|
|
253
|
-
GlobalQueue.
|
|
254
|
-
GlobalQueue.
|
|
255
|
-
GlobalQueue.
|
|
275
|
+
GlobalQueue.En = transitionBlocked;
|
|
276
|
+
GlobalQueue.Tn = cleanupCompletedLanes;
|
|
277
|
+
GlobalQueue.dn = runLaneEffects;
|
|
256
278
|
GlobalQueue.Ft = gatedRead;
|
|
257
279
|
GlobalQueue.ht = laneSuspends;
|
|
258
280
|
GlobalQueue.gt = laneReadsCommitted;
|
|
259
281
|
GlobalQueue.je = recomputeLane;
|
|
260
282
|
GlobalQueue.$e = laneAsyncPending;
|
|
261
283
|
GlobalQueue.ze = laneAsyncSettled;
|
|
262
|
-
GlobalQueue.
|
|
284
|
+
GlobalQueue.In = trackOptimisticStore;
|
|
263
285
|
}
|
|
264
286
|
|
|
265
287
|
export { installOptimisticEngine };
|
|
@@ -329,16 +329,16 @@ class GlobalQueue extends Queue {
|
|
|
329
329
|
// once the gate holds.
|
|
330
330
|
static vt=null;
|
|
331
331
|
static fn=null;
|
|
332
|
-
static dn=null;
|
|
333
332
|
static En=null;
|
|
334
333
|
static Tn=null;
|
|
334
|
+
static dn=null;
|
|
335
335
|
static Ft=null;
|
|
336
336
|
static ht=null;
|
|
337
337
|
static gt=null;
|
|
338
338
|
static je=null;
|
|
339
339
|
static $e=null;
|
|
340
340
|
static ze=null;
|
|
341
|
-
static
|
|
341
|
+
static In=null;
|
|
342
342
|
flush() {
|
|
343
343
|
if (this.sn) return;
|
|
344
344
|
// Fast drain: nothing in flight but plain pending commits — no dirty
|
|
@@ -383,8 +383,8 @@ class GlobalQueue extends Queue {
|
|
|
383
383
|
if (this.k === e) currentBatch = this.k = createBatch();
|
|
384
384
|
// Run lane effects immediately (before stashing) - lanes with no pending async
|
|
385
385
|
if (activeLanes.size) {
|
|
386
|
-
GlobalQueue.
|
|
387
|
-
GlobalQueue.
|
|
386
|
+
GlobalQueue.dn(EFFECT_RENDER);
|
|
387
|
+
GlobalQueue.dn(EFFECT_USER);
|
|
388
388
|
}
|
|
389
389
|
this.stashQueues(e.bt);
|
|
390
390
|
clock++;
|
|
@@ -432,9 +432,9 @@ class GlobalQueue extends Queue {
|
|
|
432
432
|
// Check if finalization added items to the heap (from optimistic reversion)
|
|
433
433
|
scheduled = dirtyQueue.EE >= dirtyQueue.xe;
|
|
434
434
|
// Run lane effects first (for ready lanes), then regular effects
|
|
435
|
-
activeLanes.size && GlobalQueue.
|
|
435
|
+
activeLanes.size && GlobalQueue.dn(EFFECT_RENDER);
|
|
436
436
|
this.run(EFFECT_RENDER);
|
|
437
|
-
activeLanes.size && GlobalQueue.
|
|
437
|
+
activeLanes.size && GlobalQueue.dn(EFFECT_USER);
|
|
438
438
|
this.run(EFFECT_USER);
|
|
439
439
|
if (false) ;
|
|
440
440
|
if (false && !scheduled && !activeTransition && transitions.size === 0 && activeLanes.size === 0) ;
|
|
@@ -677,7 +677,7 @@ function finalizePureQueue(e = null, t = false) {
|
|
|
677
677
|
if (t.cn.size) GlobalQueue.Vt(t.cn, e);
|
|
678
678
|
sweepTransientStoreNodes();
|
|
679
679
|
// Lanes only enter activeLanes through the engine's getOrCreateLane.
|
|
680
|
-
if (activeLanes.size) GlobalQueue.
|
|
680
|
+
if (activeLanes.size) GlobalQueue.Tn(e);
|
|
681
681
|
}
|
|
682
682
|
}
|
|
683
683
|
|
|
@@ -783,7 +783,7 @@ function transitionComplete(e) {
|
|
|
783
783
|
// Override blockage lives with the engine (absent hook = "no optimistic
|
|
784
784
|
// blockage"); the hook's loops over _optimisticNodes/_optimisticStores are
|
|
785
785
|
// no-ops when the transition holds neither, so no pre-check is needed.
|
|
786
|
-
if (t && GlobalQueue.
|
|
786
|
+
if (t && GlobalQueue.En?.(e)) t = false;
|
|
787
787
|
t && (e.an = true);
|
|
788
788
|
return t;
|
|
789
789
|
}
|
|
@@ -273,6 +273,13 @@ function snapCompanionsToState(e) {
|
|
|
273
273
|
|
|
274
274
|
function getLatestValueComputed(e) {
|
|
275
275
|
let t = e.o?.ge;
|
|
276
|
+
// A shadow disposed while unobserved (its gated reader unmounted at a
|
|
277
|
+
// landing) is a corpse: sync writes into it equality-swallow against its
|
|
278
|
+
// frozen _value, and a later read revives it via recompute — clearing
|
|
279
|
+
// DISPOSED and re-deriving from the committed view, so the banner showed
|
|
280
|
+
// the previous transition's target (#3041 follow-up). Treat it as absent;
|
|
281
|
+
// recreation backfills from the in-flight write below.
|
|
282
|
+
if (t && t.ie & REACTIVE_DISPOSED) t = undefined;
|
|
276
283
|
if (!t) {
|
|
277
284
|
const n = latestReadActive;
|
|
278
285
|
setLatestReadActive(false);
|
|
@@ -56,8 +56,8 @@ function installNextBlockedHalf() {
|
|
|
56
56
|
e.clear();
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
-
const e = GlobalQueue.
|
|
60
|
-
GlobalQueue.
|
|
59
|
+
const e = GlobalQueue.En;
|
|
60
|
+
GlobalQueue.En = t => {
|
|
61
61
|
for (const e of t.cn) {
|
|
62
62
|
const t = e?.[$TARGET];
|
|
63
63
|
const n = t?.fam?.node;
|
|
@@ -198,7 +198,7 @@ function createOptimisticStoreNext(e, t, n) {
|
|
|
198
198
|
// and the target for landing consumption (RUL-2).
|
|
199
199
|
e.pb = null;
|
|
200
200
|
(e.fam.overlaid ??= new Set).add(e);
|
|
201
|
-
GlobalQueue.
|
|
201
|
+
GlobalQueue.In?.(e.fam.px ?? e.px);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
/**
|
|
@@ -33,81 +33,143 @@ import { wrapNext, storeSetterNext } from "./store.js";
|
|
|
33
33
|
* loading windows, handleAsync landings, commit-through-setter) on next
|
|
34
34
|
* primitives; the generic draft write-traps are reused from the legacy
|
|
35
35
|
* module unchanged.
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Wrap a store proxy as a projection DRAFT: every operation carries the write
|
|
39
|
+
* override (the derive is the author — its ops must not hit the §6c firewall
|
|
40
|
+
* gate, even in a continuation after an `await`/`yield` where the sync write
|
|
41
|
+
* scope has closed).
|
|
42
|
+
*
|
|
43
|
+
* FAKE TARGET, not the store proxy itself (#3060): after a proxy trap
|
|
44
|
+
* returns, the engine runs spec invariant validation against the proxy's
|
|
45
|
+
* TARGET — [[OwnPropertyKeys]] after ownKeys, [[GetOwnProperty]] after
|
|
46
|
+
* set/getOwnPropertyDescriptor/defineProperty. With the store proxy as
|
|
47
|
+
* target those checks re-enter the store's traps OUTSIDE the override
|
|
48
|
+
* bracket (the trap's finally has already run), so `Object.keys(state)` in
|
|
49
|
+
* a derive continuation fired the firewall gate and re-threw the
|
|
50
|
+
* projection's own pending NotReadyError into the derive. A dummy of
|
|
51
|
+
* matching kind (array/object, same trick as the store's own TargetShape)
|
|
52
|
+
* keeps invariant validation away from the store entirely; the traps
|
|
53
|
+
* forward to the closed-over inner proxy inside the bracket.
|
|
54
|
+
*
|
|
55
|
+
* Save/restore projectionWriteActive, never hard-reset: the draft can be
|
|
56
|
+
* driven from inside an enclosing authoritative-write scope (next-store
|
|
57
|
+
* optimistic derives), and a hard `false` would clobber it mid-derive.
|
|
58
|
+
*/ function wrapDraft(e, t, r) {
|
|
59
|
+
const i = {
|
|
60
|
+
get(i, o) {
|
|
61
|
+
let n;
|
|
62
|
+
const c = projectionWriteActive;
|
|
44
63
|
setWriteOverride(true);
|
|
45
64
|
setProjectionWriteActive(true);
|
|
46
65
|
try {
|
|
47
|
-
|
|
66
|
+
n = e[o];
|
|
48
67
|
} finally {
|
|
49
68
|
setWriteOverride(false);
|
|
50
|
-
setProjectionWriteActive(
|
|
69
|
+
setProjectionWriteActive(c);
|
|
51
70
|
}
|
|
52
|
-
if (
|
|
53
|
-
return typeof
|
|
71
|
+
if (o === $TARGET) return n;
|
|
72
|
+
return typeof n === "object" && n !== null ? wrapDraft(n, t, r) : n;
|
|
54
73
|
},
|
|
55
|
-
has(
|
|
56
|
-
let
|
|
74
|
+
has(t, r) {
|
|
75
|
+
let i;
|
|
57
76
|
const o = projectionWriteActive;
|
|
58
77
|
setWriteOverride(true);
|
|
59
78
|
setProjectionWriteActive(true);
|
|
60
79
|
try {
|
|
61
|
-
|
|
80
|
+
i = r in e;
|
|
62
81
|
} finally {
|
|
63
82
|
setWriteOverride(false);
|
|
64
83
|
setProjectionWriteActive(o);
|
|
65
84
|
}
|
|
66
|
-
return
|
|
85
|
+
return i;
|
|
86
|
+
},
|
|
87
|
+
set(i, o, n) {
|
|
88
|
+
if (t && !t()) return true;
|
|
89
|
+
const c = projectionWriteActive;
|
|
90
|
+
setWriteOverride(true);
|
|
91
|
+
setProjectionWriteActive(true);
|
|
92
|
+
try {
|
|
93
|
+
e[o] = n;
|
|
94
|
+
r?.();
|
|
95
|
+
} finally {
|
|
96
|
+
setWriteOverride(false);
|
|
97
|
+
setProjectionWriteActive(c);
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
67
100
|
},
|
|
68
|
-
|
|
69
|
-
if (
|
|
101
|
+
deleteProperty(i, o) {
|
|
102
|
+
if (t && !t()) return true;
|
|
70
103
|
const n = projectionWriteActive;
|
|
71
104
|
setWriteOverride(true);
|
|
72
105
|
setProjectionWriteActive(true);
|
|
73
106
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
107
|
+
delete e[o];
|
|
108
|
+
r?.();
|
|
76
109
|
} finally {
|
|
77
110
|
setWriteOverride(false);
|
|
78
111
|
setProjectionWriteActive(n);
|
|
79
112
|
}
|
|
80
113
|
return true;
|
|
81
114
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
ownKeys() {
|
|
116
|
+
const t = projectionWriteActive;
|
|
117
|
+
setWriteOverride(true);
|
|
118
|
+
setProjectionWriteActive(true);
|
|
119
|
+
try {
|
|
120
|
+
return Reflect.ownKeys(e);
|
|
121
|
+
} finally {
|
|
122
|
+
setWriteOverride(false);
|
|
123
|
+
setProjectionWriteActive(t);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
getOwnPropertyDescriptor(t, r) {
|
|
127
|
+
let i;
|
|
128
|
+
const o = projectionWriteActive;
|
|
129
|
+
setWriteOverride(true);
|
|
130
|
+
setProjectionWriteActive(true);
|
|
131
|
+
try {
|
|
132
|
+
i = Reflect.getOwnPropertyDescriptor(e, r);
|
|
133
|
+
} finally {
|
|
134
|
+
setWriteOverride(false);
|
|
135
|
+
setProjectionWriteActive(o);
|
|
136
|
+
}
|
|
137
|
+
// The dummy target doesn't hold the key, so a non-configurable report
|
|
138
|
+
// would violate the proxy invariant. Store descriptors are already
|
|
139
|
+
// normalized configurable; enforce it for raw leaves too.
|
|
140
|
+
if (i) i.configurable = true;
|
|
141
|
+
return i;
|
|
142
|
+
},
|
|
143
|
+
defineProperty(i, o, n) {
|
|
144
|
+
if (t && !t()) return true;
|
|
145
|
+
const c = projectionWriteActive;
|
|
85
146
|
setWriteOverride(true);
|
|
86
147
|
setProjectionWriteActive(true);
|
|
87
148
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
149
|
+
Reflect.defineProperty(e, o, n);
|
|
150
|
+
r?.();
|
|
90
151
|
} finally {
|
|
91
152
|
setWriteOverride(false);
|
|
92
|
-
setProjectionWriteActive(
|
|
153
|
+
setProjectionWriteActive(c);
|
|
93
154
|
}
|
|
94
155
|
return true;
|
|
95
156
|
}
|
|
96
157
|
};
|
|
97
|
-
|
|
158
|
+
// Matching-kind dummy so Array.isArray(draft) answers like the store.
|
|
159
|
+
return new Proxy(Array.isArray(e) ? [] : {}, i);
|
|
98
160
|
}
|
|
99
161
|
|
|
100
162
|
function createProjectionNextInternal(e, t, r) {
|
|
101
|
-
const
|
|
163
|
+
const i = {
|
|
102
164
|
map: new WeakMap,
|
|
103
165
|
node: null,
|
|
104
166
|
shallow: !!r?.shallow
|
|
105
167
|
};
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
168
|
+
const o = wrapNext(t, null, null, i);
|
|
169
|
+
if (i.shallow) {
|
|
108
170
|
// Shallow projection: the root is the only wrapped level — slot values
|
|
109
171
|
// serve raw, ingests sticky raw-mark (same t.s machinery as plain).
|
|
110
|
-
|
|
172
|
+
o[$TARGET].s = true;
|
|
111
173
|
markRawIngest(t);
|
|
112
174
|
}
|
|
113
175
|
let n;
|
|
@@ -115,13 +177,13 @@ function createProjectionNextInternal(e, t, r) {
|
|
|
115
177
|
loadingValue: undefined
|
|
116
178
|
};
|
|
117
179
|
const c = computed(() => {
|
|
118
|
-
if (!
|
|
119
|
-
runProjectionComputedNext(
|
|
180
|
+
if (!i.node) i.node = getOwner();
|
|
181
|
+
runProjectionComputedNext(o, e, r?.key === undefined ? "id" : r.key);
|
|
120
182
|
}, n);
|
|
121
183
|
c.T &= ~CONFIG_AUTO_DISPOSE;
|
|
122
|
-
|
|
184
|
+
i.node = c;
|
|
123
185
|
return {
|
|
124
|
-
store:
|
|
186
|
+
store: o,
|
|
125
187
|
node: c
|
|
126
188
|
};
|
|
127
189
|
}
|
|
@@ -133,15 +195,15 @@ function createProjectionNext(e, t, r) {
|
|
|
133
195
|
/** Derived writable store (legacy parity): a projection whose public setter
|
|
134
196
|
* masks the recompute for the tick (core R31 — the manual write wins over a
|
|
135
197
|
* same-flush dependency change). */ function createStoreDerivedNext(e, t, r) {
|
|
136
|
-
const {store:
|
|
137
|
-
return [
|
|
198
|
+
const {store: i, node: o} = createProjectionNextInternal(e, t, r);
|
|
199
|
+
return [ i, e => {
|
|
138
200
|
// Mark the projection as manually written before notifying nodes.
|
|
139
|
-
suppressComputedRecompute(
|
|
140
|
-
storeSetterNext(
|
|
201
|
+
suppressComputedRecompute(o);
|
|
202
|
+
storeSetterNext(i, e);
|
|
141
203
|
} ];
|
|
142
204
|
}
|
|
143
205
|
|
|
144
|
-
function runProjectionComputedNext(e, t, r,
|
|
206
|
+
function runProjectionComputedNext(e, t, r, i, o) {
|
|
145
207
|
const n = getOwner();
|
|
146
208
|
let c = false;
|
|
147
209
|
let s;
|
|
@@ -150,18 +212,18 @@ function runProjectionComputedNext(e, t, r, o, i) {
|
|
|
150
212
|
// seed so draft writes cannot tear through to readers (#2988). Every commit
|
|
151
213
|
// point reconciles the shadow through the normal commit path.
|
|
152
214
|
const u = n.Ie ? JSON.parse(JSON.stringify(e[$TARGET][STORE_VALUE])) : null;
|
|
153
|
-
const l =
|
|
154
|
-
storeSetterNext(l,
|
|
155
|
-
s = t(u ??
|
|
215
|
+
const l = wrapDraft(e, () => !c || n.o?.Ee === s, o);
|
|
216
|
+
storeSetterNext(l, o => {
|
|
217
|
+
s = t(u ?? o);
|
|
156
218
|
c = true;
|
|
157
219
|
const commit = t => {
|
|
158
220
|
// Shadow run: commit a detached snapshot, never the shadow itself
|
|
159
221
|
// (adoption takes the value by identity — handing it the live shadow
|
|
160
222
|
// would fuse the draft to the observable store).
|
|
161
223
|
if (u && (t === undefined || t === u)) t = JSON.parse(JSON.stringify(u));
|
|
162
|
-
if (t ===
|
|
224
|
+
if (t === o || t === undefined) return;
|
|
163
225
|
const write = () => storeSetterNext(e, e => reconcileNextState(t, e, r, true), false);
|
|
164
|
-
|
|
226
|
+
i ? i(write) : write();
|
|
165
227
|
};
|
|
166
228
|
const l = handleAsync(n, s, commit);
|
|
167
229
|
if (!n.Ie) commit(l);
|
|
@@ -2,7 +2,7 @@ import type { OptimisticLane } from "./lanes.js";
|
|
|
2
2
|
import type { Computed, Signal } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Test-mode invariant checks for the async/transition/lane machinery.
|
|
5
|
-
* Catalog and rationale: packages/
|
|
5
|
+
* Catalog and rationale: packages/signals/INTERNALS-ASYNC-STATE.md.
|
|
6
6
|
*
|
|
7
7
|
* These are implementation self-consistency checks, not semantic rules: a
|
|
8
8
|
* violation means the reactive system contradicted itself.
|
|
@@ -2,7 +2,7 @@ import type { OptimisticLane } from "./lanes.cjs";
|
|
|
2
2
|
import type { Computed, Signal } from "./types.cjs";
|
|
3
3
|
/**
|
|
4
4
|
* Test-mode invariant checks for the async/transition/lane machinery.
|
|
5
|
-
* Catalog and rationale: packages/
|
|
5
|
+
* Catalog and rationale: packages/signals/INTERNALS-ASYNC-STATE.md.
|
|
6
6
|
*
|
|
7
7
|
* These are implementation self-consistency checks, not semantic rules: a
|
|
8
8
|
* violation means the reactive system contradicted itself.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidjs/signals",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.3",
|
|
4
4
|
"description": "Solid's reactive primitives: signals, memos, effects, stores, and async-aware computations.",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/solidjs/solid.git",
|
|
11
|
-
"directory": "packages/
|
|
11
|
+
"directory": "packages/signals"
|
|
12
12
|
},
|
|
13
13
|
"publishConfig": {
|
|
14
14
|
"access": "public"
|
|
@@ -39,6 +39,18 @@
|
|
|
39
39
|
},
|
|
40
40
|
"./package.json": "./package.json"
|
|
41
41
|
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "npm-run-all -nl build:* && pnpm types",
|
|
44
|
+
"build:clean": "rimraf dist/dev dist/prod dist/node dist/dev.js dist/prod.js dist/node.cjs",
|
|
45
|
+
"build:js": "rollup -c && node ./scripts/mangle-props.mjs dist/prod dist/node.cjs && node ./scripts/check-pure.mjs dist/prod",
|
|
46
|
+
"types": "tsc -p tsconfig.build.json && node ../../scripts/sync-dual-types.mjs ./dist/types ./dist/types-cjs",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest watch tests",
|
|
49
|
+
"test:gc": "node --expose-gc ./vitest.js",
|
|
50
|
+
"test:gc:watch": "node --expose-gc ./vitest.js --watch",
|
|
51
|
+
"coverage": "vitest run --coverage",
|
|
52
|
+
"bench": "vitest bench --run"
|
|
53
|
+
},
|
|
42
54
|
"devDependencies": {
|
|
43
55
|
"@codspeed/vitest-plugin": "^5.4.0",
|
|
44
56
|
"@ianvs/prettier-plugin-sort-imports": "^4.1.1",
|
|
@@ -53,17 +65,5 @@
|
|
|
53
65
|
"typescript": "^6.0.3",
|
|
54
66
|
"vite": "^7.0.0",
|
|
55
67
|
"vitest": "^4.1.6"
|
|
56
|
-
},
|
|
57
|
-
"scripts": {
|
|
58
|
-
"build": "npm-run-all -nl build:* && pnpm types",
|
|
59
|
-
"build:clean": "rimraf dist/dev dist/prod dist/node dist/dev.js dist/prod.js dist/node.cjs",
|
|
60
|
-
"build:js": "rollup -c && node ./scripts/mangle-props.mjs dist/prod dist/node.cjs && node ./scripts/check-pure.mjs dist/prod",
|
|
61
|
-
"types": "tsc -p tsconfig.build.json && node ../../scripts/sync-dual-types.mjs ./dist/types ./dist/types-cjs",
|
|
62
|
-
"test": "vitest run",
|
|
63
|
-
"test:watch": "vitest watch tests",
|
|
64
|
-
"test:gc": "node --expose-gc ./vitest.js",
|
|
65
|
-
"test:gc:watch": "node --expose-gc ./vitest.js --watch",
|
|
66
|
-
"coverage": "vitest run --coverage",
|
|
67
|
-
"bench": "vitest bench --run"
|
|
68
68
|
}
|
|
69
|
-
}
|
|
69
|
+
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { type Refreshable } from "../core/index.js";
|
|
2
|
-
import { type NoFn, type ProjectionOptions, type Store, type StoreSetter } from "./store.js";
|
|
3
|
-
/**
|
|
4
|
-
* The store equivalent of `createOptimistic`. Writes inside an `action`
|
|
5
|
-
* transition are tentative — they show up immediately but auto-revert (or
|
|
6
|
-
* reconcile to the action's resolved value) once the transition finishes.
|
|
7
|
-
*
|
|
8
|
-
* Use this for optimistic UI on collection-shaped data. For single-value
|
|
9
|
-
* optimistic state, prefer `createOptimistic`.
|
|
10
|
-
*
|
|
11
|
-
* - Plain form: `createOptimisticStore(initialValue)`.
|
|
12
|
-
* - Derived form: `createOptimisticStore(fn, seed, options?)` — a projection
|
|
13
|
-
* store whose authoritative value is recomputed by `fn` and whose
|
|
14
|
-
* optimistic overlay reverts after each transition.
|
|
15
|
-
*
|
|
16
|
-
* `options.key` defaults to `"id"`; specify it only when your data uses a
|
|
17
|
-
* different identity field (e.g. `{ key: "uuid" }` or `{ key: t => t.slug }`),
|
|
18
|
-
* or `null` to merge positionally. Restating the default just adds noise.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const [todos, setTodos] = createOptimisticStore<Todo[]>([]);
|
|
23
|
-
*
|
|
24
|
-
* // Mutation: optimistic add, then in-place reconcile to the saved row.
|
|
25
|
-
* const addTodo = action(function* (text: string) {
|
|
26
|
-
* const tempId = crypto.randomUUID();
|
|
27
|
-
* setTodos(t => { t.push({ id: tempId, text, pending: true }); });
|
|
28
|
-
* const saved = yield api.createTodo(text);
|
|
29
|
-
* setTodos(t => {
|
|
30
|
-
* const i = t.findIndex(x => x.id === tempId);
|
|
31
|
-
* if (i >= 0) t[i] = saved;
|
|
32
|
-
* });
|
|
33
|
-
* });
|
|
34
|
-
*
|
|
35
|
-
* // Return form: filter is the natural shape for removal.
|
|
36
|
-
* const removeTodo = action(function* (id: string) {
|
|
37
|
-
* setTodos(t => t.filter(x => x.id !== id));
|
|
38
|
-
* yield api.removeTodo(id);
|
|
39
|
-
* });
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* @returns `[store: Store<T>, setStore: StoreSetter<T>]`
|
|
43
|
-
*/
|
|
44
|
-
export declare function createOptimisticStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Store<T>, set: StoreSetter<T>];
|
|
45
|
-
export declare function createOptimisticStore<T extends object = {}>(fn: (store: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, store: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Refreshable<Store<T>>, set: StoreSetter<T>];
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { type Computed, type Refreshable } from "../core/index.js";
|
|
2
|
-
import { type NoFn, type ProjectionOptions, type Store } from "./store.js";
|
|
3
|
-
export declare function createProjectionInternal<T extends object = {}>(fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, seed: Partial<T>, options?: ProjectionOptions): {
|
|
4
|
-
store: Refreshable<Store<T>>;
|
|
5
|
-
node: Computed<void | T>;
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* Creates a derived (projected) store. Like `createMemo` but for stores: the
|
|
9
|
-
* derive function receives a mutable draft and either mutates it in place
|
|
10
|
-
* (canonical) or returns a new value. Either way the result is reconciled
|
|
11
|
-
* against the previous draft by `options.key` (default `"id"`), so surviving
|
|
12
|
-
* items keep their proxy identity — only added/removed items are
|
|
13
|
-
* created/disposed.
|
|
14
|
-
*
|
|
15
|
-
* If the derive returns a different entity than the one currently held (the
|
|
16
|
-
* `/users/1` → `/users/2` shape), the store swaps to it rather than merging,
|
|
17
|
-
* and nothing below it is treated as surviving.
|
|
18
|
-
*
|
|
19
|
-
* Returns the projected store directly (no setter — reads only).
|
|
20
|
-
*
|
|
21
|
-
* Use this when you want the structural-sharing / per-property tracking
|
|
22
|
-
* behaviour of a store on top of a derived computation. For simple read-only
|
|
23
|
-
* derivations, `createMemo` is lighter.
|
|
24
|
-
*
|
|
25
|
-
* @param fn receives the current draft; mutate it in place or return new
|
|
26
|
-
* data. Return is convenient for filter/derive shapes where mutation is
|
|
27
|
-
* awkward.
|
|
28
|
-
* @param seed the backing store value to wrap and reconcile into
|
|
29
|
-
* @param options `ProjectionOptions` — `name`, `key`. `key` defaults to
|
|
30
|
-
* `"id"`; specify it only when your data uses a different identity field
|
|
31
|
-
* (e.g. `{ key: "uuid" }` or `{ key: u => u.slug }`), or `null` to merge
|
|
32
|
-
* positionally with no keyed pass.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* // Mutation form — update individual fields on the draft.
|
|
37
|
-
* const summary = createProjection<{ total: number; active: number }>(
|
|
38
|
-
* draft => {
|
|
39
|
-
* draft.total = users().length;
|
|
40
|
-
* draft.active = users().filter(u => u.active).length;
|
|
41
|
-
* },
|
|
42
|
-
* { total: 0, active: 0 }
|
|
43
|
-
* );
|
|
44
|
-
*
|
|
45
|
-
* // Return form — produce a derived collection. Reconciled by `id` so each
|
|
46
|
-
* // surviving user keeps the same store identity across recomputes.
|
|
47
|
-
* const activeUsers = createProjection<User[]>(
|
|
48
|
-
* () => allUsers().filter(u => u.active),
|
|
49
|
-
* []
|
|
50
|
-
* );
|
|
51
|
-
* ```
|
|
52
|
-
*
|
|
53
|
-
* @see {@link https://github.com/solidjs/x-reactivity#createprojection}
|
|
54
|
-
*/
|
|
55
|
-
export declare function createProjection<T extends object = {}>(fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, seed: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): Refreshable<Store<T>>;
|
|
56
|
-
/**
|
|
57
|
-
* Shared projection computed body used by both `createProjection` and the derived
|
|
58
|
-
* form of `createOptimisticStore`. Encapsulates the write-trap draft, `storeSetter`
|
|
59
|
-
* wrapping, the `handleAsync` subscription with a setter callback, and the commit
|
|
60
|
-
* path (which must always go through `storeSetter` so the `writeOnly` guard is
|
|
61
|
-
* engaged during `reconcile`'s property reads).
|
|
62
|
-
*
|
|
63
|
-
* `wrapCommit` is invoked for every commit (sync return and each async yield) and
|
|
64
|
-
* lets callers layer extra context around the write — e.g. the optimistic store
|
|
65
|
-
* re-enters `setProjectionWriteActive` so reconciles target `STORE_OVERRIDE`
|
|
66
|
-
* instead of `STORE_OPTIMISTIC_OVERRIDE` even when an async yield fires outside
|
|
67
|
-
* the outer `setProjectionWriteActive` scope.
|
|
68
|
-
*/
|
|
69
|
-
export declare function runProjectionComputed<T extends object>(wrappedStore: Store<T>, fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, key: string | ((item: NonNullable<any>) => any) | null, wrapCommit?: (write: () => void) => void, onDraftWrite?: () => void): Computed<void | T>;
|
|
70
|
-
export declare function createWriteTraps(isActive?: () => boolean, onDraftWrite?: () => void): ProxyHandler<any>;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared body of `reconcile()` and the projection commit. `replace` is the
|
|
3
|
-
* only difference: a projection commit is a value swap, not a merge — its root
|
|
4
|
-
* proxy is a cell handed out by `createProjection` that can never change
|
|
5
|
-
* reference, so a derive returning a different entity is not the slot mistake
|
|
6
|
-
* `reconcile()` throws on. Nothing below the root survives that swap, which is
|
|
7
|
-
* the rule the keyed diff already applies at a nested slot on a key mismatch.
|
|
8
|
-
*
|
|
9
|
-
* @internal
|
|
10
|
-
*/
|
|
11
|
-
export declare function reconcileState(value: any, state: any, key: any, replace: boolean): void;
|
|
12
|
-
/**
|
|
13
|
-
* Returns a draft-mutating function that smart-merges `value` into a store,
|
|
14
|
-
* preserving fine-grained reactivity: only changed leaves trigger updates.
|
|
15
|
-
*
|
|
16
|
-
* With a `key` (default `"id"`), array items whose key matches between old
|
|
17
|
-
* and new states keep their identity (updated in place, moves and removals
|
|
18
|
-
* update the corresponding signals) — the shape for keyed server payloads.
|
|
19
|
-
* Items without the key field fall back to positional matching.
|
|
20
|
-
*
|
|
21
|
-
* With `key: null`, matching is purely positional: index N of the new array
|
|
22
|
-
* merges into index N of the old, and object properties merge recursively —
|
|
23
|
-
* the classic pattern for fixed-shape data that churns in place (dashboards,
|
|
24
|
-
* monitors), where no keyed diff pass is needed or wanted.
|
|
25
|
-
*
|
|
26
|
-
* Merging into a slot that holds a *different* entity throws — the caller
|
|
27
|
-
* picked the slot, so a key mismatch there is a bug.
|
|
28
|
-
*
|
|
29
|
-
* @param value the next state to merge in
|
|
30
|
-
* @param key property name (string) or extractor function for stable
|
|
31
|
-
* identity (default `"id"`); pass `null` for positional merging
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```ts
|
|
35
|
-
* const [todos, setTodos] = createStore<Todo[]>([]);
|
|
36
|
-
*
|
|
37
|
-
* async function refresh() {
|
|
38
|
-
* const fresh = await api.getTodos();
|
|
39
|
-
* setTodos(reconcile(fresh)); // diff-merge by `id`
|
|
40
|
-
* }
|
|
41
|
-
*
|
|
42
|
-
* // fixed-shape polling data — positional merge
|
|
43
|
-
* setStats(reconcile(nextStats, null));
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
export declare function reconcile<T extends U, U>(value: T, key?: string | ((item: NonNullable<any>) => any) | null): (state: U) => void;
|