@solidjs/signals 2.0.0-beta.9 → 2.0.0-rc.1
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 +14 -10
- package/dist/dev.js +6740 -2011
- package/dist/node.cjs +7927 -3340
- package/dist/prod/affects.js +126 -0
- package/dist/prod/boundaries.js +572 -0
- package/dist/prod/core/action.js +139 -0
- package/dist/prod/core/async.js +607 -0
- package/dist/prod/core/constants.js +92 -0
- package/dist/prod/core/context.js +67 -0
- package/dist/prod/core/core.js +828 -0
- package/dist/prod/core/dev.js +3 -0
- package/dist/prod/core/effect.js +145 -0
- package/dist/prod/core/error.js +68 -0
- package/dist/prod/core/external.js +98 -0
- package/dist/prod/core/graph.js +104 -0
- package/dist/prod/core/heap.js +140 -0
- package/dist/prod/core/invariants.js +42 -0
- package/dist/prod/core/lanes.js +140 -0
- package/dist/prod/core/optimistic.js +262 -0
- package/dist/prod/core/owner.js +302 -0
- package/dist/prod/core/scheduler.js +757 -0
- package/dist/prod/core/verdict.js +369 -0
- package/dist/prod/index.js +45 -0
- package/dist/prod/map.js +319 -0
- package/dist/prod/signals.js +394 -0
- package/dist/prod/store/index.js +36 -0
- package/dist/prod/store/next/optimistic.js +377 -0
- package/dist/prod/store/next/projection.js +172 -0
- package/dist/prod/store/next/reconcile.js +327 -0
- package/dist/prod/store/next/store.js +1232 -0
- package/dist/prod/store/next/target.js +20 -0
- package/dist/prod/store/store.js +325 -0
- package/dist/prod/store/storePath.js +103 -0
- package/dist/prod/store/utils.js +201 -0
- package/dist/types/affects.d.ts +47 -0
- package/dist/types/boundaries.d.ts +60 -13
- package/dist/types/core/action.d.ts +35 -6
- package/dist/types/core/async.d.ts +16 -1
- package/dist/types/core/constants.d.ts +35 -6
- package/dist/types/core/context.d.ts +10 -2
- package/dist/types/core/core.d.ts +66 -63
- package/dist/types/core/dev.d.ts +17 -2
- package/dist/types/core/effect.d.ts +1 -2
- package/dist/types/core/error.d.ts +33 -0
- package/dist/types/core/external.d.ts +0 -11
- package/dist/types/core/graph.d.ts +2 -1
- package/dist/types/core/heap.d.ts +8 -0
- package/dist/types/core/index.d.ts +3 -2
- package/dist/types/core/invariants.d.ts +59 -0
- package/dist/types/core/lanes.d.ts +2 -0
- package/dist/types/core/optimistic.d.ts +6 -0
- package/dist/types/core/owner.d.ts +50 -3
- package/dist/types/core/scheduler.d.ts +85 -11
- package/dist/types/core/types.d.ts +66 -1
- package/dist/types/core/verdict.d.ts +2 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/map.d.ts +21 -5
- package/dist/types/signals.d.ts +183 -12
- package/dist/types/store/index.d.ts +16 -6
- package/dist/types/store/next/optimistic.d.ts +20 -0
- package/dist/types/store/next/projection.d.ts +24 -0
- package/dist/types/store/next/reconcile.d.ts +3 -0
- package/dist/types/store/next/store.d.ts +71 -0
- package/dist/types/store/next/target.d.ts +99 -0
- package/dist/types/store/optimistic.d.ts +3 -3
- package/dist/types/store/projection.d.ts +10 -6
- package/dist/types/store/reconcile.d.ts +31 -8
- package/dist/types/store/store.d.ts +91 -71
- package/dist/types/store/utils.d.ts +0 -40
- package/dist/types-cjs/affects.d.cts +47 -0
- package/dist/types-cjs/boundaries.d.cts +60 -13
- package/dist/types-cjs/core/action.d.cts +35 -6
- package/dist/types-cjs/core/async.d.cts +16 -1
- package/dist/types-cjs/core/constants.d.cts +35 -6
- package/dist/types-cjs/core/context.d.cts +10 -2
- package/dist/types-cjs/core/core.d.cts +66 -63
- package/dist/types-cjs/core/dev.d.cts +17 -2
- package/dist/types-cjs/core/effect.d.cts +1 -2
- package/dist/types-cjs/core/error.d.cts +33 -0
- package/dist/types-cjs/core/external.d.cts +0 -11
- package/dist/types-cjs/core/graph.d.cts +2 -1
- package/dist/types-cjs/core/heap.d.cts +8 -0
- package/dist/types-cjs/core/index.d.cts +3 -2
- package/dist/types-cjs/core/invariants.d.cts +59 -0
- package/dist/types-cjs/core/lanes.d.cts +2 -0
- package/dist/types-cjs/core/optimistic.d.cts +6 -0
- package/dist/types-cjs/core/owner.d.cts +50 -3
- package/dist/types-cjs/core/scheduler.d.cts +85 -11
- package/dist/types-cjs/core/types.d.cts +66 -1
- package/dist/types-cjs/core/verdict.d.cts +2 -0
- package/dist/types-cjs/index.d.cts +3 -2
- package/dist/types-cjs/map.d.cts +21 -5
- package/dist/types-cjs/signals.d.cts +183 -12
- package/dist/types-cjs/store/index.d.cts +16 -6
- package/dist/types-cjs/store/next/optimistic.d.cts +20 -0
- package/dist/types-cjs/store/next/projection.d.cts +24 -0
- package/dist/types-cjs/store/next/reconcile.d.cts +3 -0
- package/dist/types-cjs/store/next/store.d.cts +71 -0
- package/dist/types-cjs/store/next/target.d.cts +99 -0
- package/dist/types-cjs/store/optimistic.d.cts +3 -3
- package/dist/types-cjs/store/projection.d.cts +10 -6
- package/dist/types-cjs/store/reconcile.d.cts +31 -8
- package/dist/types-cjs/store/store.d.cts +91 -71
- package/dist/types-cjs/store/utils.d.cts +0 -40
- package/package.json +12 -9
- package/dist/prod.js +0 -3627
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ownership (first cut, decision 2026-08-16d): one WeakSet of store-owned
|
|
3
|
+
* backings serving both the production identity-skip guard and the false
|
|
4
|
+
* no-mutation oracle.
|
|
5
|
+
*/
|
|
6
|
+
const ownedRaw = new WeakSet;
|
|
7
|
+
|
|
8
|
+
/** raw → target. The only raw-keyed lookup; boundary mechanism (O8). */ const storeNextLookup = new WeakMap;
|
|
9
|
+
|
|
10
|
+
function devAssertNeverUserMutation(o) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let optHooks = null;
|
|
15
|
+
|
|
16
|
+
function setOptHooks(o) {
|
|
17
|
+
optHooks = o;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { devAssertNeverUserMutation, optHooks, ownedRaw, setOptHooks, storeNextLookup };
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import "../core/core.js";
|
|
2
|
+
|
|
3
|
+
import { GlobalQueue } from "../core/scheduler.js";
|
|
4
|
+
|
|
5
|
+
import "../core/invariants.js";
|
|
6
|
+
|
|
7
|
+
import "../core/verdict.js";
|
|
8
|
+
|
|
9
|
+
import "../core/effect.js";
|
|
10
|
+
|
|
11
|
+
import { storeNextLookup } from "./next/target.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Brand symbols used internally by the store proxy / projection plumbing.
|
|
15
|
+
* Cross-package wiring; not part of the user-facing API.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/ const $TRACK = Symbol(0), $TARGET = Symbol(0), $PROXY = Symbol(0), // Node-map slot carrying a record-level `affects()` mark: any read through
|
|
19
|
+
// the record witnesses it into the active isPending() probe.
|
|
20
|
+
$AFFECTS = Symbol(0);
|
|
21
|
+
|
|
22
|
+
// Structural field names of store targets (StoreNextTarget aliases these, so
|
|
23
|
+
// shared machinery — affects walks, tests — reads targets via the consts).
|
|
24
|
+
const STORE_VALUE = "v", STORE_NODE = "n", STORE_HAS = "h";
|
|
25
|
+
|
|
26
|
+
function lookupTarget(e, t) {
|
|
27
|
+
// Family maps (projections/optimistic) map raw -> target; the global next
|
|
28
|
+
// lookup maps raw -> target too. Proxies resolve through $TARGET directly.
|
|
29
|
+
if (t !== undefined) {
|
|
30
|
+
const o = t.get(e);
|
|
31
|
+
if (o !== undefined) return o[$TARGET] ?? o;
|
|
32
|
+
}
|
|
33
|
+
return storeNextLookup.get(e);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Values marked raw never acquire a proxy identity: wrap() serves them as-is
|
|
37
|
+
// everywhere — deep stores hold them as leaf values replaced by reference.
|
|
38
|
+
// Once raw, always raw (identity stays single, just unwrapped). Consulted
|
|
39
|
+
// only on wrap-creation and ingest paths; reads never touch it.
|
|
40
|
+
const rawValues = new WeakSet;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Marks a value as raw: no store will ever wrap it — every store presents it
|
|
44
|
+
* as-is, tracked by reference at whatever slot holds it and updated by
|
|
45
|
+
* replacement. Useful for class instances and external objects (editors,
|
|
46
|
+
* scene graphs, Maps) and for record-shaped data updated wholesale. Sticky
|
|
47
|
+
* for the value's lifetime.
|
|
48
|
+
*/
|
|
49
|
+
// Flipped on the first mark and exported as a LIVE binding: reconcile
|
|
50
|
+
// consults it on every recursable pair, and importing the boolean directly
|
|
51
|
+
// lets those sites skip even the function call when no shallow store or raw
|
|
52
|
+
// mark exists anywhere in the app.
|
|
53
|
+
let rawValuesUsed = false;
|
|
54
|
+
|
|
55
|
+
function isRawValue(e) {
|
|
56
|
+
return rawValuesUsed && rawValues.has(e);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function markRawOne(e) {
|
|
60
|
+
if (isWrappable(e)) {
|
|
61
|
+
// A store proxy is already tracked elsewhere: the shallow boundary passes
|
|
62
|
+
// it through by reference (replaced, never edited — same slot semantics
|
|
63
|
+
// as a raw) instead of claiming it raw. The sticky mark is global, so
|
|
64
|
+
// marking a live proxy would make wrap() serve it verbatim through every
|
|
65
|
+
// OTHER store too — downstream deep stores then captured it instead of
|
|
66
|
+
// wrapping it in their own family, and their writes landed in the
|
|
67
|
+
// upstream store's override layer (#2932).
|
|
68
|
+
if (e[$TARGET] !== undefined) return;
|
|
69
|
+
rawValuesUsed = true;
|
|
70
|
+
rawValues.add(e);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function markRawIngest(e) {
|
|
75
|
+
if (Array.isArray(e)) {
|
|
76
|
+
for (let t = 0, o = e.length; t < o; t++) markRawOne(e[t]);
|
|
77
|
+
} else {
|
|
78
|
+
for (const t in e) markRawOne(e[t]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const OBJECT_PROTO = Object.prototype;
|
|
83
|
+
|
|
84
|
+
// Per-prototype memo for the custom-proto branch of isWrappable: the verdict
|
|
85
|
+
// is fully determined by the prototype (tag and Node lineage both live on
|
|
86
|
+
// the chain), so each class pays the tag call once — not per read.
|
|
87
|
+
const wrappableProtos = new WeakMap;
|
|
88
|
+
|
|
89
|
+
function isWrappable(e) {
|
|
90
|
+
if (e == null || typeof e !== "object" || Object.isFrozen(e)) return false;
|
|
91
|
+
// Plain data and user class instances wrap; platform objects never do
|
|
92
|
+
// (#2952). Native code brand-checks internal slots and throws through a
|
|
93
|
+
// proxy (`Map.prototype.size`, `Date.prototype.getTime`, ...), so
|
|
94
|
+
// collections and other built-ins can't honestly be stores — they get the
|
|
95
|
+
// markRaw-children contract automatically: served raw, mutations land raw,
|
|
96
|
+
// the property holding them still tracks (reassignment notifies). The tag
|
|
97
|
+
// check separates them structurally: user classes stringify as
|
|
98
|
+
// `[object Object]` while every native/host object carries its own brand
|
|
99
|
+
// (`[object Map]`, `[object Date]`, `[object Headers]`, ...), including
|
|
100
|
+
// subclasses, which inherit the tag. getPrototypeOf keeps the hot path
|
|
101
|
+
// (plain and null-proto objects) intrinsic-only — no property lookup.
|
|
102
|
+
const t = Object.getPrototypeOf(e);
|
|
103
|
+
if (t === OBJECT_PROTO || t === null) return true;
|
|
104
|
+
if (Array.isArray(e)) return true;
|
|
105
|
+
let o = wrappableProtos.get(t);
|
|
106
|
+
if (o === undefined) {
|
|
107
|
+
o = Object.prototype.toString.call(e) === "[object Object]" && (
|
|
108
|
+
// Dynamic Node check (kept dynamic so test/SSR overrides of
|
|
109
|
+
// `globalThis.Node` are observed at call time): shimmed DOMs implement
|
|
110
|
+
// nodes as plain user classes, which pass the tag check.
|
|
111
|
+
typeof Node === "undefined" || !(e instanceof Node));
|
|
112
|
+
wrappableProtos.set(t, o);
|
|
113
|
+
}
|
|
114
|
+
return o;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let writeOverride = false;
|
|
118
|
+
|
|
119
|
+
function setWriteOverride(e) {
|
|
120
|
+
writeOverride = e;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getWriteOverride() {
|
|
124
|
+
return writeOverride;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Own enumerable keys including symbols (`Object.keys` drops symbol-keyed props). #2769
|
|
128
|
+
function ownEnumerableKeys(e) {
|
|
129
|
+
return Reflect.ownKeys(e).filter(t => Object.prototype.propertyIsEnumerable.call(e, t));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Scope inheritance for late-created nodes: every live mark whose identity
|
|
134
|
+
* scope contains the owning record's raw — and, for keyed marks, whose key
|
|
135
|
+
* is this property — gets counted on the new node. Inherited marks live
|
|
136
|
+
* exactly as long as the scope's carrier — the release hook below drops
|
|
137
|
+
* them with the entry.
|
|
138
|
+
*/ function inheritAffectsMarks(e, t, o) {
|
|
139
|
+
// A live scope exists, so affects.ts already installed the mark engine.
|
|
140
|
+
for (const [r, s] of affectsScopes) {
|
|
141
|
+
if (r.t && s.scope.has(t) && (s.key === undefined || s.key === o)) {
|
|
142
|
+
GlobalQueue.M(e);
|
|
143
|
+
s.inherited.push(e);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const affectsScopes = new Map;
|
|
149
|
+
|
|
150
|
+
/** Next-store node factory for affects carriers/slots: injected by the
|
|
151
|
+
* rewrite module (next targets alias the legacy field names, so everything
|
|
152
|
+
* here EXCEPT node creation works on them structurally). */ let nextAffectsNodeResolver = null;
|
|
153
|
+
|
|
154
|
+
function setNextAffectsNodeResolver(e) {
|
|
155
|
+
nextAffectsNodeResolver = e;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Next-store optimistic view for the declaration walk (optimistic rows
|
|
159
|
+
* pushed before the declaration are in motion too — legacy reads its write
|
|
160
|
+
* overlays; next composes armed-node overrides). */ let nextOptimisticViewResolver = null;
|
|
161
|
+
|
|
162
|
+
function setNextOptimisticViewResolver(e) {
|
|
163
|
+
nextOptimisticViewResolver = e;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** @internal birth inheritance for nodes created inside a live mark window —
|
|
167
|
+
* exported for the rewrite's node factories. */ function affectsScopesLive() {
|
|
168
|
+
return affectsScopes.size > 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Snapshots the identities reachable from `value` into `scope`, reading
|
|
173
|
+
* through write overlays (an optimistic row pushed before the declaration is
|
|
174
|
+
* in motion too). Untracked by construction: walks raw values, never traps.
|
|
175
|
+
* Every LIVE node under each reachable record — property leaves, `$TRACK`,
|
|
176
|
+
* and has-nodes — collects into `found`: those are the graph edges existing
|
|
177
|
+
* readers subscribed through, so the mark registers on them directly and
|
|
178
|
+
* rides the status rails to everything derived. (Nodes born later inherit
|
|
179
|
+
* from the scope in `getNode`.)
|
|
180
|
+
*/ function walkAffectsScope(e, t, o, r,
|
|
181
|
+
// Cycle guard, fresh per declaration: the scope itself can't serve — a
|
|
182
|
+
// re-declaration on the same carrier unions into a scope that already
|
|
183
|
+
// holds the root, and must still descend to pick up records added since.
|
|
184
|
+
s) {
|
|
185
|
+
if (!isWrappable(e)) return;
|
|
186
|
+
const f = e[$TARGET] || lookupTarget(e, r);
|
|
187
|
+
// Next targets: walk the pending backing when present (a draft's writes are
|
|
188
|
+
// in motion too) and cover BOTH identities in the scope.
|
|
189
|
+
let n = f ? f.pb ?? f[STORE_VALUE] : e;
|
|
190
|
+
if (s.has(n)) return;
|
|
191
|
+
s.add(n);
|
|
192
|
+
t.scope.add(n);
|
|
193
|
+
if (f && f.pb) t.scope.add(f[STORE_VALUE]);
|
|
194
|
+
// Next optimistic families: enumerate the VISIBLE view (armed-node
|
|
195
|
+
// overrides compose membership/values the raw doesn't carry).
|
|
196
|
+
if (f && f.fam?.opt && nextOptimisticViewResolver) n = nextOptimisticViewResolver(f, n);
|
|
197
|
+
if (f) {
|
|
198
|
+
collectRecordNodes(f[STORE_NODE], o);
|
|
199
|
+
collectRecordNodes(f[STORE_HAS], o);
|
|
200
|
+
// The key-set and deep-witness nodes are record-level channels: a deep()
|
|
201
|
+
// probe reads ONLY these (one pair per record), so a declared affects
|
|
202
|
+
// scope must mark them like any property node.
|
|
203
|
+
if (f.k) o.push(f.k);
|
|
204
|
+
if (f.dk) o.push(f.dk);
|
|
205
|
+
// Carry the effective lookup into untouched descendants (family maps for
|
|
206
|
+
// projections/optimistic stores; the global next lookup otherwise).
|
|
207
|
+
r = f.fam?.map ?? r ?? storeNextLookup;
|
|
208
|
+
}
|
|
209
|
+
// Overlays are gone (next has no layer): raw enumeration; the optimistic
|
|
210
|
+
// view composition above already folded armed-node membership/values in.
|
|
211
|
+
if (Array.isArray(n)) {
|
|
212
|
+
for (let e = 0, f = n.length; e < f; e++) {
|
|
213
|
+
walkAffectsScope(n[e], t, o, r, s);
|
|
214
|
+
}
|
|
215
|
+
const e = Object.getOwnPropertySymbols(n);
|
|
216
|
+
for (let f = 0, c = e.length; f < c; f++) {
|
|
217
|
+
const c = Object.getOwnPropertyDescriptor(n, e[f]);
|
|
218
|
+
if (!c || c.get) continue;
|
|
219
|
+
walkAffectsScope(c.value, t, o, r, s);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
const e = Reflect.ownKeys(n);
|
|
223
|
+
for (let f = 0, c = e.length; f < c; f++) {
|
|
224
|
+
const c = Object.getOwnPropertyDescriptor(n, e[f]);
|
|
225
|
+
if (!c || c.get) continue;
|
|
226
|
+
walkAffectsScope(c.value, t, o, r, s);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** All live signal nodes of one record's node map (string + symbol keyed). */ function collectRecordNodes(e, t) {
|
|
232
|
+
if (!e) return;
|
|
233
|
+
for (const o of Object.keys(e)) t.push(e[o]);
|
|
234
|
+
const o = Object.getOwnPropertySymbols(e);
|
|
235
|
+
for (let r = 0, s = o.length; r < s; r++) {
|
|
236
|
+
// Another mark's carrier is its own channel — counting it here would
|
|
237
|
+
// extend that sibling scope's lifetime to this declaration's.
|
|
238
|
+
if (o[r] !== $AFFECTS) t.push(e[o[r]]);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Witness live mark coverage of a record into the active isPending() probe.
|
|
244
|
+
* Tracked reads don't need this — they go through real signal nodes, which
|
|
245
|
+
* carry marks directly (declaration walk or birth inheritance). This covers
|
|
246
|
+
* UNTRACKED probes reading through records whose nodes never materialized
|
|
247
|
+
* (no observer ever subscribed, so no node exists to carry the mark).
|
|
248
|
+
* Callers guard on `pendingCheckActive`, so plain reads never pay for this.
|
|
249
|
+
*
|
|
250
|
+
* @internal
|
|
251
|
+
*/ function witnessAffectsMark(e, t) {
|
|
252
|
+
// Callers guard on `pendingCheckActive`, which only flips inside
|
|
253
|
+
// isPending() — the verdict layer is loaded and its hook installed.
|
|
254
|
+
const o = e[STORE_NODE]?.[$AFFECTS];
|
|
255
|
+
if (o?.t) GlobalQueue.Lt(o);
|
|
256
|
+
if (affectsScopes.size) {
|
|
257
|
+
// Chained backings (§7b): a wrapper's STORE_VALUE can be another store's
|
|
258
|
+
// proxy — marks cover by identity of the BASE raw, so resolve the chain
|
|
259
|
+
// and check every identity along it.
|
|
260
|
+
let r = e[STORE_VALUE];
|
|
261
|
+
for (const [e, s] of affectsScopes) {
|
|
262
|
+
if (e !== o && e.t && (s.key === undefined || s.key === t)) {
|
|
263
|
+
let t = r;
|
|
264
|
+
for (;;) {
|
|
265
|
+
if (s.scope.has(t)) {
|
|
266
|
+
GlobalQueue.Lt(e);
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
const o = t?.[$TARGET];
|
|
270
|
+
if (o === undefined) break;
|
|
271
|
+
const r = o.pb ?? o[STORE_VALUE];
|
|
272
|
+
if (r === t) break;
|
|
273
|
+
t = r;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Resolves the store nodes an `affects()` declaration marks: with a `key`,
|
|
282
|
+
* the named slot's leaf node (upserted so the mark has an addressable
|
|
283
|
+
* carrier); without, the record's $AFFECTS carrier plus every LIVE node in
|
|
284
|
+
* its subtree (the edges existing readers subscribed through), with the
|
|
285
|
+
* subtree's identities snapshotted into the mark's scope so nodes created
|
|
286
|
+
* during the window — and untracked probes over captured proxies — resolve
|
|
287
|
+
* against it (#2882).
|
|
288
|
+
*
|
|
289
|
+
* @internal
|
|
290
|
+
*/ function getStoreAffectsNodes(e, t) {
|
|
291
|
+
GlobalQueue.p ||= e => {
|
|
292
|
+
const t = affectsScopes.get(e);
|
|
293
|
+
if (!t) return;
|
|
294
|
+
affectsScopes.delete(e);
|
|
295
|
+
for (let e = 0; e < t.inherited.length; e++) GlobalQueue.h(t.inherited[e]);
|
|
296
|
+
};
|
|
297
|
+
if (t === undefined) {
|
|
298
|
+
const t = nextAffectsNodeResolver(e, $AFFECTS);
|
|
299
|
+
let o = affectsScopes.get(t);
|
|
300
|
+
if (!o) affectsScopes.set(t, o = {
|
|
301
|
+
scope: new Set,
|
|
302
|
+
inherited: []
|
|
303
|
+
});
|
|
304
|
+
const r = [ t ];
|
|
305
|
+
walkAffectsScope(e[$PROXY], o, r, e.fam?.map, new Set);
|
|
306
|
+
return r;
|
|
307
|
+
}
|
|
308
|
+
const o = e.n?.[t] ?? nextAffectsNodeResolver(e, t);
|
|
309
|
+
// Keyed marks resolve by identity too (#2904): another store family's
|
|
310
|
+
// proxy can share this record's raw (a derived store swaps its backing to
|
|
311
|
+
// the source's raw when its projection lands), and reads through it never
|
|
312
|
+
// touch this target's node map. Scope is exactly the owning record's raw,
|
|
313
|
+
// narrowed to this key for witness and birth inheritance.
|
|
314
|
+
let r = affectsScopes.get(o);
|
|
315
|
+
if (!r) affectsScopes.set(o, r = {
|
|
316
|
+
scope: new Set,
|
|
317
|
+
inherited: [],
|
|
318
|
+
key: t
|
|
319
|
+
});
|
|
320
|
+
r.scope.add(e[STORE_VALUE]);
|
|
321
|
+
if (e.pb) r.scope.add(e.pb);
|
|
322
|
+
return [ o ];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export { $AFFECTS, $PROXY, $TARGET, $TRACK, STORE_HAS, STORE_NODE, STORE_VALUE, affectsScopesLive, getStoreAffectsNodes, getWriteOverride, inheritAffectsMarks, isRawValue, isWrappable, markRawIngest, markRawOne, nextAffectsNodeResolver, nextOptimisticViewResolver, ownEnumerableKeys, rawValuesUsed, setNextAffectsNodeResolver, setNextOptimisticViewResolver, setWriteOverride, witnessAffectsMark };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { isWrappable, ownEnumerableKeys } from "./store.js";
|
|
2
|
+
|
|
3
|
+
const DELETE = Symbol(0);
|
|
4
|
+
|
|
5
|
+
function isPrototypePollutionKey(t) {
|
|
6
|
+
return t === "__proto__" || t === "constructor" || t === "prototype";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function updatePath(t, e, o = 0) {
|
|
10
|
+
let r, n = t;
|
|
11
|
+
if (o < e.length - 1) {
|
|
12
|
+
r = e[o];
|
|
13
|
+
const i = typeof r;
|
|
14
|
+
const f = Array.isArray(t);
|
|
15
|
+
if (i === "string" && isPrototypePollutionKey(r)) return;
|
|
16
|
+
if (Array.isArray(r)) {
|
|
17
|
+
for (let n = 0; n < r.length; n++) {
|
|
18
|
+
e[o] = r[n];
|
|
19
|
+
updatePath(t, e, o);
|
|
20
|
+
}
|
|
21
|
+
e[o] = r;
|
|
22
|
+
return;
|
|
23
|
+
} else if (f && i === "function") {
|
|
24
|
+
for (let n = 0; n < t.length; n++) {
|
|
25
|
+
if (r(t[n], n)) {
|
|
26
|
+
e[o] = n;
|
|
27
|
+
updatePath(t, e, o);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
e[o] = r;
|
|
31
|
+
return;
|
|
32
|
+
} else if (f && i === "object") {
|
|
33
|
+
const {from: n = 0, to: i = t.length - 1, by: f = 1} = r;
|
|
34
|
+
for (let r = n; r <= i; r += f) {
|
|
35
|
+
e[o] = r;
|
|
36
|
+
updatePath(t, e, o);
|
|
37
|
+
}
|
|
38
|
+
e[o] = r;
|
|
39
|
+
return;
|
|
40
|
+
} else if (o < e.length - 2) {
|
|
41
|
+
updatePath(t[r], e, o + 1);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
n = t[r];
|
|
45
|
+
}
|
|
46
|
+
let i = e[e.length - 1];
|
|
47
|
+
if (typeof i === "function") {
|
|
48
|
+
i = i(n);
|
|
49
|
+
if (i === n) return;
|
|
50
|
+
}
|
|
51
|
+
if (r === undefined && i == undefined) return;
|
|
52
|
+
if (i === DELETE) {
|
|
53
|
+
delete t[r];
|
|
54
|
+
} else if (r === undefined || isWrappable(n) && isWrappable(i) && !Array.isArray(i)) {
|
|
55
|
+
const e = r !== undefined ? t[r] : t;
|
|
56
|
+
const o = ownEnumerableKeys(i);
|
|
57
|
+
for (let t = 0; t < o.length; t++) {
|
|
58
|
+
const r = o[t];
|
|
59
|
+
if (typeof r === "string" && isPrototypePollutionKey(r)) continue;
|
|
60
|
+
const n = Object.getOwnPropertyDescriptor(i, r);
|
|
61
|
+
if (n.get || n.set) Object.defineProperty(e, r, n); else e[r] = n.value;
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
t[r] = i;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Path-based setter helper for `createStore`. Call `storePath(...path, value)`
|
|
70
|
+
* to produce a draft-mutating function suitable for passing to `setStore`.
|
|
71
|
+
*
|
|
72
|
+
* The canonical setter form in Solid 2.0 is the draft-mutating callback
|
|
73
|
+
* (`setStore(s => { s.user.name = "Ada"; })`). `storePath` is a backwards-
|
|
74
|
+
* compatibility helper for users porting from Solid 1.x's
|
|
75
|
+
* `setStore("user", "name", "Ada")` style — it's optional and you can mix the
|
|
76
|
+
* two styles freely.
|
|
77
|
+
*
|
|
78
|
+
* Path parts can be:
|
|
79
|
+
* - a single key — `"user"`, `0`
|
|
80
|
+
* - an array of keys — `[0, 1, 2]`
|
|
81
|
+
* - a range over an array — `{ from?, to?, by? }`
|
|
82
|
+
* - a filter `(item, index) => boolean` for arrays
|
|
83
|
+
*
|
|
84
|
+
* The final argument is the new value or an updater `(prev) => next`. Use
|
|
85
|
+
* `storePath.DELETE` to remove a property.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const [state, setState] = createStore({ user: { name: "Ada" }, todos: [] });
|
|
90
|
+
*
|
|
91
|
+
* setState(storePath("user", "name", "Grace"));
|
|
92
|
+
* setState(storePath("todos", t => !t.done, "done", true)); // mark all undone as done
|
|
93
|
+
* setState(storePath("user", "nickname", storePath.DELETE));
|
|
94
|
+
* ```
|
|
95
|
+
*/ const storePath = /* @__PURE__ */ Object.assign(function storePath(...t) {
|
|
96
|
+
return e => {
|
|
97
|
+
updatePath(e, t);
|
|
98
|
+
};
|
|
99
|
+
}, {
|
|
100
|
+
DELETE: DELETE
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export { storePath };
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import "../core/core.js";
|
|
2
|
+
|
|
3
|
+
import { SUPPORTS_PROXY } from "../core/constants.js";
|
|
4
|
+
|
|
5
|
+
import "../core/scheduler.js";
|
|
6
|
+
|
|
7
|
+
import "../core/invariants.js";
|
|
8
|
+
|
|
9
|
+
import "../core/verdict.js";
|
|
10
|
+
|
|
11
|
+
import "../core/effect.js";
|
|
12
|
+
|
|
13
|
+
import { createMemo } from "../signals.js";
|
|
14
|
+
|
|
15
|
+
import { ownEnumerableKeys, $PROXY } from "./store.js";
|
|
16
|
+
|
|
17
|
+
function trueFn() {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const propTraps = {
|
|
22
|
+
get(e, r, t) {
|
|
23
|
+
if (r === $PROXY) return t;
|
|
24
|
+
return e.get(r);
|
|
25
|
+
},
|
|
26
|
+
has(e, r) {
|
|
27
|
+
if (r === $PROXY) return true;
|
|
28
|
+
return e.has(r);
|
|
29
|
+
},
|
|
30
|
+
set: trueFn,
|
|
31
|
+
deleteProperty: trueFn,
|
|
32
|
+
getOwnPropertyDescriptor(e, r) {
|
|
33
|
+
return {
|
|
34
|
+
configurable: true,
|
|
35
|
+
enumerable: true,
|
|
36
|
+
get() {
|
|
37
|
+
return e.get(r);
|
|
38
|
+
},
|
|
39
|
+
set: trueFn,
|
|
40
|
+
deleteProperty: trueFn
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
ownKeys(e) {
|
|
44
|
+
return e.keys();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function resolveSource(e) {
|
|
49
|
+
return !(e = typeof e === "function" ? e() : e) ? {} : e;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const $SOURCES = Symbol(0);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Merges multiple props-like objects into a single proxy that *preserves
|
|
56
|
+
* reactivity*. Reads are forwarded to the right-most source that defines the
|
|
57
|
+
* property, so later sources override earlier ones (like `Object.assign`).
|
|
58
|
+
*
|
|
59
|
+
* Function arguments are treated as memo-backed sources — useful for passing
|
|
60
|
+
* derived defaults whose computation should track reactively.
|
|
61
|
+
*
|
|
62
|
+
* Use this in component bodies to merge defaults / overrides without losing
|
|
63
|
+
* Solid's per-property tracking.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* function Button(_props: { label: string; type?: string; disabled?: boolean }) {
|
|
68
|
+
* const props = merge({ type: "button", disabled: false }, _props);
|
|
69
|
+
*
|
|
70
|
+
* return <button type={props.type} disabled={props.disabled}>{props.label}</button>;
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/ function merge(...e) {
|
|
74
|
+
if (e.length === 1 && typeof e[0] !== "function") return e[0];
|
|
75
|
+
let r = false;
|
|
76
|
+
const t = [];
|
|
77
|
+
for (let n = 0; n < e.length; n++) {
|
|
78
|
+
const o = e[n];
|
|
79
|
+
r = r || !!o && $PROXY in o;
|
|
80
|
+
const s = !!o && o[$SOURCES];
|
|
81
|
+
if (s) {
|
|
82
|
+
for (let e = 0; e < s.length; e++) t.push(s[e]);
|
|
83
|
+
} else t.push(typeof o === "function" ? (r = true, createMemo(o)) : o);
|
|
84
|
+
}
|
|
85
|
+
if (SUPPORTS_PROXY && r) {
|
|
86
|
+
return new Proxy({
|
|
87
|
+
get(e) {
|
|
88
|
+
if (e === $SOURCES) return t;
|
|
89
|
+
for (let r = t.length - 1; r >= 0; r--) {
|
|
90
|
+
const n = resolveSource(t[r]);
|
|
91
|
+
if (e in n) return n[e];
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
has(e) {
|
|
95
|
+
for (let r = t.length - 1; r >= 0; r--) {
|
|
96
|
+
if (e in resolveSource(t[r])) return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
},
|
|
100
|
+
keys() {
|
|
101
|
+
const e = new Set;
|
|
102
|
+
for (let r = 0; r < t.length; r++) {
|
|
103
|
+
const n = ownEnumerableKeys(resolveSource(t[r]));
|
|
104
|
+
for (let r = 0; r < n.length; r++) e.add(n[r]);
|
|
105
|
+
}
|
|
106
|
+
return [ ...e ];
|
|
107
|
+
}
|
|
108
|
+
}, propTraps);
|
|
109
|
+
}
|
|
110
|
+
const n = Object.create(null);
|
|
111
|
+
let o = false;
|
|
112
|
+
let s = t.length - 1;
|
|
113
|
+
for (let e = s; e >= 0; e--) {
|
|
114
|
+
const r = t[e];
|
|
115
|
+
if (!r) {
|
|
116
|
+
e === s && s--;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const u = Object.getOwnPropertyNames(r);
|
|
120
|
+
for (let t = u.length - 1; t >= 0; t--) {
|
|
121
|
+
const c = u[t];
|
|
122
|
+
if (c === "__proto__" || c === "constructor") continue;
|
|
123
|
+
if (!n[c]) {
|
|
124
|
+
o = o || e !== s;
|
|
125
|
+
const t = Object.getOwnPropertyDescriptor(r, c);
|
|
126
|
+
n[c] = t.get ? {
|
|
127
|
+
enumerable: true,
|
|
128
|
+
configurable: true,
|
|
129
|
+
get: t.get.bind(r)
|
|
130
|
+
} : t;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!o) return t[s];
|
|
135
|
+
const u = {};
|
|
136
|
+
const c = Object.keys(n);
|
|
137
|
+
for (let e = c.length - 1; e >= 0; e--) {
|
|
138
|
+
const r = c[e], t = n[r];
|
|
139
|
+
if (t.get) Object.defineProperty(u, r, t); else u[r] = t.value;
|
|
140
|
+
}
|
|
141
|
+
u[$SOURCES] = t;
|
|
142
|
+
return u;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Returns a reactive proxy of `props` with the listed keys hidden. Tracking
|
|
147
|
+
* on the remaining keys is preserved.
|
|
148
|
+
*
|
|
149
|
+
* Use it to forward "rest" props to a child element while pulling out the
|
|
150
|
+
* keys your component handles itself — the equivalent of `splitProps(p, ["a","b"])[1]`.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```tsx
|
|
154
|
+
* function Input(props: { label: string; value: string; onInput: (v: string) => void } & JSX.HTMLAttributes<HTMLInputElement>) {
|
|
155
|
+
* const rest = omit(props, "label", "value", "onInput");
|
|
156
|
+
*
|
|
157
|
+
* return (
|
|
158
|
+
* <label>
|
|
159
|
+
* {props.label}
|
|
160
|
+
* <input
|
|
161
|
+
* {...rest}
|
|
162
|
+
* value={props.value}
|
|
163
|
+
* onInput={e => props.onInput(e.currentTarget.value)}
|
|
164
|
+
* />
|
|
165
|
+
* </label>
|
|
166
|
+
* );
|
|
167
|
+
* }
|
|
168
|
+
* ```
|
|
169
|
+
*/ function omit(e, ...r) {
|
|
170
|
+
if (SUPPORTS_PROXY && $PROXY in e) {
|
|
171
|
+
return new Proxy({
|
|
172
|
+
get(t) {
|
|
173
|
+
// $SOURCES must not tunnel through the filter: merge() flattens
|
|
174
|
+
// whatever answers it, so forwarding would hand a re-merge the
|
|
175
|
+
// UNFILTERED sources of an underlying merge proxy and the omitted
|
|
176
|
+
// keys leak back in (#3014 — the SSR element-spread path re-merges
|
|
177
|
+
// static attributes with the rest object). Opaque here: merge
|
|
178
|
+
// composes omit proxies through their traps instead.
|
|
179
|
+
return t === $SOURCES || r.includes(t) ? undefined : e[t];
|
|
180
|
+
},
|
|
181
|
+
has(t) {
|
|
182
|
+
return t !== $SOURCES && !r.includes(t) && t in e;
|
|
183
|
+
},
|
|
184
|
+
keys() {
|
|
185
|
+
return ownEnumerableKeys(e).filter(e => !r.includes(e));
|
|
186
|
+
}
|
|
187
|
+
}, propTraps);
|
|
188
|
+
}
|
|
189
|
+
const t = {};
|
|
190
|
+
const n = Object.getOwnPropertyNames(e);
|
|
191
|
+
const o = r.length > 4 && n.length > r.length ? new Set(r) : undefined;
|
|
192
|
+
for (const s of n) {
|
|
193
|
+
if (o ? !o.has(s) : !r.includes(s)) {
|
|
194
|
+
const r = Object.getOwnPropertyDescriptor(e, s);
|
|
195
|
+
!r.get && !r.set && r.enumerable && r.writable && r.configurable ? t[s] = r.value : Object.defineProperty(t, s, r);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return t;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export { merge, omit };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Accessor } from "./signals.js";
|
|
2
|
+
import { type Store } from "./store/store.js";
|
|
3
|
+
/**
|
|
4
|
+
* Declares that in-flight work will change the targeted data: the named
|
|
5
|
+
* slot(s) — and everything DERIVED from them — read as pending
|
|
6
|
+
* (`isPending` → `true`) from the declaration until the surrounding
|
|
7
|
+
* transaction settles or reverts. A mark lives on its own channel — a
|
|
8
|
+
* refcount on the marked node plus dep-graph reachability in the verdict
|
|
9
|
+
* layer — so the marked values themselves stay readable (a mark is a promise
|
|
10
|
+
* of change, not an absence of value), no reader ever suspends on one, and
|
|
11
|
+
* completion/settlement accounting never sees one. This is the declaration
|
|
12
|
+
* verb of the pending model — additive only. A mark can turn pending ON for
|
|
13
|
+
* data the graph can't see changing yet; nothing can turn pending OFF while
|
|
14
|
+
* a real change is in flight — a quiet `refresh()` re-ask under a mark still
|
|
15
|
+
* reads pending (declaring the reload is what makes it a real question).
|
|
16
|
+
*
|
|
17
|
+
* Targets:
|
|
18
|
+
* - `affects(store)` — a store proxy (any record, root or nested): every
|
|
19
|
+
* record reachable from it at declaration time reads pending, including
|
|
20
|
+
* through captured child proxies (e.g. `<For>` rows). Siblings are
|
|
21
|
+
* untouched; records added after the declaration are not covered.
|
|
22
|
+
* - `affects(record, key)` — exactly the named slot of the record. One key
|
|
23
|
+
* per call (keys do NOT form a path — target the owning record directly).
|
|
24
|
+
* - `affects(accessor)` — a source accessor (signal or memo): the source
|
|
25
|
+
* reads pending.
|
|
26
|
+
*
|
|
27
|
+
* Typically called at the top of an `action` alongside optimistic writes —
|
|
28
|
+
* both are up-front declarations about the same mutation. Outside any
|
|
29
|
+
* transaction the mark is released at the end of the current flush.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const send = action(function* (text: string) {
|
|
34
|
+
* setState(s => { s.messages.push({ text, status: "sending" }); });
|
|
35
|
+
* affects(state.messages.at(-1)!, "status"); // this slot pends until settle
|
|
36
|
+
* yield api.send(text);
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* const reload = action(function* () {
|
|
40
|
+
* affects(thing); // the whole store pends…
|
|
41
|
+
* refresh(thing); // …over this otherwise-quiet re-ask
|
|
42
|
+
* yield api.done();
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function affects(target: Accessor<unknown> | Store<object>): void;
|
|
47
|
+
export declare function affects<T extends object>(target: Store<T>, key: keyof T): void;
|