@solidjs/signals 2.0.0-beta.25 → 2.0.0-beta.27
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 +370 -73
- package/dist/node.cjs +1247 -946
- package/dist/prod/core/action.js +11 -6
- package/dist/prod/core/async.js +212 -108
- package/dist/prod/core/core.js +203 -189
- package/dist/prod/core/effect.js +28 -28
- package/dist/prod/core/external.js +3 -3
- package/dist/prod/core/graph.js +28 -28
- package/dist/prod/core/heap.js +30 -30
- package/dist/prod/core/lanes.js +15 -15
- package/dist/prod/core/optimistic.js +39 -39
- package/dist/prod/core/owner.js +44 -44
- package/dist/prod/core/scheduler.js +127 -106
- package/dist/prod/core/verdict.js +54 -54
- package/dist/prod/map.js +60 -60
- package/dist/prod/signals.js +1 -1
- package/dist/prod/store/optimistic.js +55 -36
- package/dist/prod/store/projection.js +38 -33
- package/dist/prod/store/reconcile.js +299 -243
- package/dist/prod/store/store.js +127 -50
- package/dist/types/core/async.d.ts +2 -0
- package/dist/types/core/scheduler.d.ts +1 -0
- package/dist/types/store/optimistic.d.ts +2 -2
- package/dist/types/store/projection.d.ts +7 -2
- package/dist/types/store/reconcile.d.ts +14 -0
- package/dist/types/store/store.d.ts +2 -2
- package/dist/types-cjs/core/async.d.cts +2 -0
- package/dist/types-cjs/core/scheduler.d.cts +1 -0
- package/dist/types-cjs/store/optimistic.d.cts +2 -2
- package/dist/types-cjs/store/projection.d.cts +7 -2
- package/dist/types-cjs/store/reconcile.d.cts +14 -0
- package/dist/types-cjs/store/store.d.cts +2 -2
- package/package.json +1 -1
package/dist/prod/store/store.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $REFRESH, STORE_SNAPSHOT_PROPS, NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, STATUS_PENDING, NO_SNAPSHOT } from "../core/constants.js";
|
|
1
|
+
import { $REFRESH, STORE_SNAPSHOT_PROPS, NOT_PENDING, unwrapOverride, STATUS_ERROR, STATUS_UNINITIALIZED, STATUS_PENDING, NO_SNAPSHOT } from "../core/constants.js";
|
|
2
2
|
|
|
3
3
|
import { suppressComputedRecompute, isEqual, signal, pendingCheckActive, untrack, setSignal, read, readNodeFast, READ_SLOW, snapshotCaptureActive, snapshotSources } from "../core/core.js";
|
|
4
4
|
|
|
@@ -122,6 +122,14 @@ function isRawValue(e) {
|
|
|
122
122
|
|
|
123
123
|
function markRawOne(e) {
|
|
124
124
|
if (isWrappable(e)) {
|
|
125
|
+
// A store proxy is already tracked elsewhere: the shallow boundary passes
|
|
126
|
+
// it through by reference (replaced, never edited — same slot semantics
|
|
127
|
+
// as a raw) instead of claiming it raw. The sticky mark is global, so
|
|
128
|
+
// marking a live proxy would make wrap() serve it verbatim through every
|
|
129
|
+
// OTHER store too — downstream deep stores then captured it instead of
|
|
130
|
+
// wrapping it in their own family, and their writes landed in the
|
|
131
|
+
// upstream store's override layer (#2932).
|
|
132
|
+
if (e[$TARGET] !== undefined) return;
|
|
125
133
|
rawValuesUsed = true;
|
|
126
134
|
rawValues.add(e);
|
|
127
135
|
}
|
|
@@ -173,11 +181,39 @@ function wrapShallow(e) {
|
|
|
173
181
|
return r;
|
|
174
182
|
}
|
|
175
183
|
|
|
184
|
+
const OBJECT_PROTO = Object.prototype;
|
|
185
|
+
|
|
186
|
+
// Per-prototype memo for the custom-proto branch of isWrappable: the verdict
|
|
187
|
+
// is fully determined by the prototype (tag and Node lineage both live on
|
|
188
|
+
// the chain), so each class pays the tag call once — not per read.
|
|
189
|
+
const wrappableProtos = new WeakMap;
|
|
190
|
+
|
|
176
191
|
function isWrappable(e) {
|
|
177
192
|
if (e == null || typeof e !== "object" || Object.isFrozen(e)) return false;
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
|
|
193
|
+
// Plain data and user class instances wrap; platform objects never do
|
|
194
|
+
// (#2952). Native code brand-checks internal slots and throws through a
|
|
195
|
+
// proxy (`Map.prototype.size`, `Date.prototype.getTime`, ...), so
|
|
196
|
+
// collections and other built-ins can't honestly be stores — they get the
|
|
197
|
+
// markRaw-children contract automatically: served raw, mutations land raw,
|
|
198
|
+
// the property holding them still tracks (reassignment notifies). The tag
|
|
199
|
+
// check separates them structurally: user classes stringify as
|
|
200
|
+
// `[object Object]` while every native/host object carries its own brand
|
|
201
|
+
// (`[object Map]`, `[object Date]`, `[object Headers]`, ...), including
|
|
202
|
+
// subclasses, which inherit the tag. getPrototypeOf keeps the hot path
|
|
203
|
+
// (plain and null-proto objects) intrinsic-only — no property lookup.
|
|
204
|
+
const t = Object.getPrototypeOf(e);
|
|
205
|
+
if (t === OBJECT_PROTO || t === null) return true;
|
|
206
|
+
if (Array.isArray(e)) return true;
|
|
207
|
+
let r = wrappableProtos.get(t);
|
|
208
|
+
if (r === undefined) {
|
|
209
|
+
r = Object.prototype.toString.call(e) === "[object Object]" && (
|
|
210
|
+
// Dynamic Node check (kept dynamic so test/SSR overrides of
|
|
211
|
+
// `globalThis.Node` are observed at call time): shimmed DOMs implement
|
|
212
|
+
// nodes as plain user classes, which pass the tag check.
|
|
213
|
+
typeof Node === "undefined" || !(e instanceof Node));
|
|
214
|
+
wrappableProtos.set(t, r);
|
|
215
|
+
}
|
|
216
|
+
return r;
|
|
181
217
|
}
|
|
182
218
|
|
|
183
219
|
let writeOverride = false;
|
|
@@ -255,7 +291,7 @@ function ownEnumerableKeysPlain(e) {
|
|
|
255
291
|
* The value a store leaf's backing signal currently shows to readers: active
|
|
256
292
|
* override, else held pending value, else committed value.
|
|
257
293
|
*/ function visibleNodeValue(e) {
|
|
258
|
-
return e.
|
|
294
|
+
return e.Ae !== undefined && e.Ae !== NOT_PENDING ? unwrapOverride(e.Ae) : e.De !== NOT_PENDING ? e.De : e.Ue;
|
|
259
295
|
}
|
|
260
296
|
|
|
261
297
|
function hasOwnStoreProperty(e, t) {
|
|
@@ -306,11 +342,11 @@ function getNode(e, t, r, n, o = isEqual, i) {
|
|
|
306
342
|
}
|
|
307
343
|
}, e[STORE_FIREWALL]);
|
|
308
344
|
if (e[STORE_OPTIMISTIC]) {
|
|
309
|
-
O.
|
|
345
|
+
O.Ae = NOT_PENDING;
|
|
310
346
|
}
|
|
311
347
|
if (i && r in i) {
|
|
312
348
|
const e = i[r];
|
|
313
|
-
O.
|
|
349
|
+
O.xe = e === undefined ? NO_SNAPSHOT : e;
|
|
314
350
|
snapshotSources?.add(O);
|
|
315
351
|
}
|
|
316
352
|
if (typeof r === "symbol" && r !== $TRACK && r !== $AFFECTS) symbolKeyedRecords.add(t);
|
|
@@ -574,8 +610,8 @@ function getPropertyDescriptor(e, t, r) {
|
|
|
574
610
|
function prepareStoreWrite(e, t, r) {
|
|
575
611
|
if (e[STORE_OPTIMISTIC]) {
|
|
576
612
|
const t = e[STORE_FIREWALL];
|
|
577
|
-
if (t?.
|
|
578
|
-
globalQueue.initTransition(t.
|
|
613
|
+
if (t?.Ie) {
|
|
614
|
+
globalQueue.initTransition(t.Ie);
|
|
579
615
|
}
|
|
580
616
|
}
|
|
581
617
|
const n = e[STORE_VALUE];
|
|
@@ -618,8 +654,11 @@ function prepareStoreWrite(e, t, r) {
|
|
|
618
654
|
* concurrent actions writing disjoint keys must revert independently, exactly
|
|
619
655
|
* like optimistic signal nodes do via the transition's _optimisticNodes.
|
|
620
656
|
* `activeTransition` is the write's transaction (action() opens it before the
|
|
621
|
-
* body runs); null marks an ambient write
|
|
622
|
-
*
|
|
657
|
+
* body runs); null marks an ambient write, which clears at plain flush end —
|
|
658
|
+
* unless its flush's transition is blocked on the store's own in-flight truth
|
|
659
|
+
* (pending firewall, #2951), in which case it rides that transaction to
|
|
660
|
+
* settle. Same-key writes across actions keep last-write-wins layer
|
|
661
|
+
* semantics.
|
|
623
662
|
*/ function stampOptimisticOwner(e, t, r) {
|
|
624
663
|
if (t === STORE_OPTIMISTIC_OVERRIDE) (e[STORE_OPTIMISTIC_OWNERS] ??= Object.create(null))[r] = activeTransition;
|
|
625
664
|
}
|
|
@@ -672,16 +711,36 @@ function notifyStoreProperty(e, t, r, n, o, i) {
|
|
|
672
711
|
let Writing = null;
|
|
673
712
|
|
|
674
713
|
/**
|
|
675
|
-
* A derived store
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
*
|
|
682
|
-
|
|
714
|
+
* A derived store follows async memo rules (#2897 ruling): its seed is a
|
|
715
|
+
* draft for the derive function, never an observable value, and an errored
|
|
716
|
+
* derive is an error state, never a silent stale/seed serve. Until the
|
|
717
|
+
* firewall first resolves there is nothing to read, so every consumer path
|
|
718
|
+
* throws NotReady — tracked reads through their node (core read()), and the
|
|
719
|
+
* untracked fall-throughs in the traps through this guard. Returning the
|
|
720
|
+
* seed leaked it; returning `undefined` would break non-nullable types.
|
|
721
|
+
* Callers exempt the firewall itself (the derive function works its own
|
|
722
|
+
* draft while uninitialized).
|
|
723
|
+
*
|
|
724
|
+
* Error rail: a firewall carrying STATUS_ERROR throws its error for every
|
|
725
|
+
* late reader — memo parity, where read()'s error branch does the same for
|
|
726
|
+
* plain computeds. Rejection clears STATUS_UNINITIALIZED at commit, so
|
|
727
|
+
* without this check late readers silently got the seed while settle-time
|
|
728
|
+
* subscribers saw the error.
|
|
729
|
+
*
|
|
730
|
+
* Loading rail: the veto requires the firewall to still be in flight, not
|
|
731
|
+
* just flagged: STATUS_UNINITIALIZED's clear is deferred to batch commit,
|
|
732
|
+
* so during the settle flush a firewall that has already recomputed — and
|
|
733
|
+
* reconciled real values into STORE_VALUE — still carries the stale flag.
|
|
734
|
+
* STATUS_PENDING is the live bit (it clears eagerly at settle, mirroring
|
|
735
|
+
* core read()'s verdict), so gating on it stops the guard from throwing a
|
|
736
|
+
* fresh NotReadyError that nothing would ever sweep. #2944: mapArray's
|
|
737
|
+
* keyed diff reads items inside its internal owner (untracked by design)
|
|
738
|
+
* in exactly this window, and the stale throw wedged <For> permanently.
|
|
739
|
+
*/ function throwIfUnreadable(e) {
|
|
683
740
|
const t = e[STORE_FIREWALL];
|
|
684
|
-
if (t
|
|
741
|
+
if (!t) return;
|
|
742
|
+
const r = t.S;
|
|
743
|
+
if (r & STATUS_ERROR || r & STATUS_UNINITIALIZED && r & STATUS_PENDING) throw t._ ?? new NotReadyError(t);
|
|
685
744
|
}
|
|
686
745
|
|
|
687
746
|
const storeTraps = {
|
|
@@ -731,20 +790,20 @@ const storeTraps = {
|
|
|
731
790
|
const s = getOverlayLayer(e, t);
|
|
732
791
|
const E = !!s;
|
|
733
792
|
const S = !!e[STORE_VALUE][$TARGET];
|
|
734
|
-
const
|
|
793
|
+
const T = s ?? e[STORE_VALUE];
|
|
735
794
|
if (!i) {
|
|
736
|
-
const n = Object.getOwnPropertyDescriptor(
|
|
795
|
+
const n = Object.getOwnPropertyDescriptor(T, t);
|
|
737
796
|
if (n && n.get) return n.get.call(r);
|
|
738
797
|
if (!n && !E && e[STORE_CUSTOM_PROTO]) {
|
|
739
|
-
const e = unwrapStoreValue(
|
|
798
|
+
const e = unwrapStoreValue(T);
|
|
740
799
|
if (hasInheritedAccessor(e, t)) {
|
|
741
|
-
return Reflect.get(
|
|
800
|
+
return Reflect.get(T, t, r);
|
|
742
801
|
}
|
|
743
802
|
}
|
|
744
803
|
}
|
|
745
804
|
if (writeOnly(r)) {
|
|
746
805
|
if (isPrototypePollutionKey(t) && !hasOwnStoreProperty(e, t)) return undefined;
|
|
747
|
-
let r = i && (E || !S) ? visibleNodeValue(i) :
|
|
806
|
+
let r = i && (E || !S) ? visibleNodeValue(i) : T[t];
|
|
748
807
|
r === $DELETED && (r = undefined);
|
|
749
808
|
if (!isWrappable(r)) return r;
|
|
750
809
|
// Shallow boundary: records are replaced, never edited in place. Reads
|
|
@@ -756,19 +815,26 @@ const storeTraps = {
|
|
|
756
815
|
Writing?.add(n);
|
|
757
816
|
return n;
|
|
758
817
|
}
|
|
759
|
-
let c = i ? E || !S ? read(o[t]) : (read(o[t]),
|
|
818
|
+
let c = i ? E || !S ? read(o[t]) : (read(o[t]), T[t]) : T[t];
|
|
760
819
|
c === $DELETED && (c = undefined);
|
|
761
820
|
if (!i) {
|
|
762
|
-
if (!E && typeof c === "function" && !Object.prototype.hasOwnProperty.call(
|
|
821
|
+
if (!E && typeof c === "function" && !Object.prototype.hasOwnProperty.call(T, t)) {
|
|
763
822
|
let t;
|
|
764
|
-
return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? c.bind(
|
|
823
|
+
return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? c.bind(T) : c;
|
|
765
824
|
} else if (getObserver() && !n) {
|
|
766
825
|
return read(getNode(e, o, t, isWrappable(c) ? wrap(c, e) : c, isEqual, e[STORE_SNAPSHOT_PROPS]));
|
|
767
826
|
}
|
|
768
827
|
}
|
|
769
828
|
// Untracked fall-through (tracked reads already threw via their node in
|
|
770
829
|
// read(); the dev strictRead error above wins first for memo parity).
|
|
771
|
-
|
|
830
|
+
// Observer-present reads must NOT re-consult the flag here: during the
|
|
831
|
+
// settle flush the firewall has recomputed (first values live on the
|
|
832
|
+
// pending rail, served by read() above) but its UNINITIALIZED clear is
|
|
833
|
+
// deferred to batch commit — vetoing read()'s verdict with the stale flag
|
|
834
|
+
// threw a fresh NotReadyError for an already-settled source, which no
|
|
835
|
+
// sweep would ever release (#2938: projection over an async store wedged
|
|
836
|
+
// its Loading boundary on `undefined`).
|
|
837
|
+
if (!n && !getObserver()) throwIfUnreadable(e);
|
|
772
838
|
return isWrappable(c) ? wrap(c, e) : c;
|
|
773
839
|
},
|
|
774
840
|
has(e, t) {
|
|
@@ -788,7 +854,7 @@ const storeTraps = {
|
|
|
788
854
|
if (getObserver()) {
|
|
789
855
|
return read(getNode(e, o, t, n));
|
|
790
856
|
}
|
|
791
|
-
|
|
857
|
+
throwIfUnreadable(e);
|
|
792
858
|
return n;
|
|
793
859
|
},
|
|
794
860
|
set(e, t, r) {
|
|
@@ -800,37 +866,48 @@ const storeTraps = {
|
|
|
800
866
|
const s = getOverlayLayer(e, t);
|
|
801
867
|
const E = s ? s[t] : o;
|
|
802
868
|
const S = s ? s[t] !== $DELETED : t in e[STORE_VALUE];
|
|
803
|
-
|
|
804
|
-
|
|
869
|
+
// Shallow slots hold store proxies verbatim (pass-through reference,
|
|
870
|
+
// never raw-marked — see markRawOne/#2932); everything else unwraps
|
|
871
|
+
// and marks as usual.
|
|
872
|
+
const T = !!e[STORE_SHALLOW] && r?.[$TARGET] !== undefined;
|
|
873
|
+
const c = T ? r : unwrapStoreValue(r);
|
|
874
|
+
if (e[STORE_SHALLOW] && !T && isWrappable(c)) {
|
|
875
|
+
// Flip the live gate too: a bare add was inert unless something else
|
|
876
|
+
// had already marked a raw somewhere (wrap() checks rawValuesUsed
|
|
877
|
+
// first), so the documented set-trap ingest mark silently no-oped in
|
|
878
|
+
// apps whose only shallow data arrived through writes.
|
|
879
|
+
rawValuesUsed = true;
|
|
880
|
+
rawValues.add(c);
|
|
881
|
+
}
|
|
805
882
|
// Symbol-keyed writes on arrays are metadata, not index writes — never run
|
|
806
883
|
// them through the numeric index/length machinery (`parseInt` on a symbol
|
|
807
884
|
// throws). #2769
|
|
808
|
-
const
|
|
809
|
-
const R = Array.isArray(O) && Number.isInteger(
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
if (E ===
|
|
885
|
+
const f = typeof t === "string" ? Number(t) : -1;
|
|
886
|
+
const R = Array.isArray(O) && Number.isInteger(f) && f >= 0 && f < 4294967295 && String(f) === t;
|
|
887
|
+
const u = R ? f + 1 : 0;
|
|
888
|
+
const a = R && (getOverlayLayer(e, "length") ?? O).length;
|
|
889
|
+
const l = R && u > a ? u : undefined;
|
|
890
|
+
if (E === c && l === undefined) return true;
|
|
814
891
|
armOptimisticStoreWrite(e, n);
|
|
815
|
-
if (
|
|
892
|
+
if (c !== undefined && c === o && l === undefined) {
|
|
816
893
|
delete e[i]?.[t];
|
|
817
894
|
if (i === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t];
|
|
818
895
|
} else {
|
|
819
896
|
const r = e[i] || (e[i] = Object.create(null));
|
|
820
|
-
r[t] =
|
|
897
|
+
r[t] = c;
|
|
821
898
|
stampOptimisticOwner(e, i, t);
|
|
822
|
-
if (
|
|
823
|
-
r.length =
|
|
899
|
+
if (l !== undefined) {
|
|
900
|
+
r.length = l;
|
|
824
901
|
stampOptimisticOwner(e, i, "length");
|
|
825
902
|
}
|
|
826
903
|
}
|
|
827
|
-
notifyStoreProperty(e, t, "set",
|
|
904
|
+
notifyStoreProperty(e, t, "set", c, E, S);
|
|
828
905
|
// Shrinking an array's length must remove the truncated indices, otherwise
|
|
829
906
|
// they leak through `has`, `ownKeys`, and (tracked) index reads from the
|
|
830
907
|
// underlying value. Mark each as deleted and notify so reactive reads update. #2768
|
|
831
|
-
if (Array.isArray(O) && t === "length" && typeof
|
|
908
|
+
if (Array.isArray(O) && t === "length" && typeof c === "number" && typeof E === "number" && c < E) {
|
|
832
909
|
const t = e[i] || (e[i] = Object.create(null));
|
|
833
|
-
for (let r =
|
|
910
|
+
for (let r = c; r < E; r++) {
|
|
834
911
|
if (t[r] === $DELETED) continue;
|
|
835
912
|
const n = r in t ? t[r] : O[r];
|
|
836
913
|
if (!(r in t) && !(r in O)) continue;
|
|
@@ -840,13 +917,13 @@ const storeTraps = {
|
|
|
840
917
|
}
|
|
841
918
|
}
|
|
842
919
|
// notify length change
|
|
843
|
-
if (Array.isArray(O) && t !== "length" &&
|
|
920
|
+
if (Array.isArray(O) && t !== "length" && l !== undefined) {
|
|
844
921
|
const t = getNodes(e, STORE_NODE);
|
|
845
922
|
if (t.length) {
|
|
846
|
-
setSignal(t.length,
|
|
923
|
+
setSignal(t.length, l);
|
|
847
924
|
} else if (!projectionWriteActive && !e[STORE_OPTIMISTIC]) {
|
|
848
|
-
const r = upsertStoreNode(e, t, "length",
|
|
849
|
-
setSignal(r,
|
|
925
|
+
const r = upsertStoreNode(e, t, "length", a, e[STORE_SNAPSHOT_PROPS]);
|
|
926
|
+
setSignal(r, l);
|
|
850
927
|
}
|
|
851
928
|
}
|
|
852
929
|
if (false) ;
|
|
@@ -907,7 +984,7 @@ const storeTraps = {
|
|
|
907
984
|
// path is exempt (like the get/has traps' writeOnly early returns):
|
|
908
985
|
// the first landing's reconcile enumerates the store while
|
|
909
986
|
// STATUS_UNINITIALIZED is still set — it IS the initialization.
|
|
910
|
-
if (!getObserver() && !writeOnly(e[$PROXY]))
|
|
987
|
+
if (!getObserver() && !writeOnly(e[$PROXY])) throwIfUnreadable(e);
|
|
911
988
|
}
|
|
912
989
|
// Merge optimistic override with regular override for key enumeration
|
|
913
990
|
let t = getKeys(e[STORE_VALUE], e[STORE_OVERRIDE], false);
|
|
@@ -3,6 +3,8 @@ import type { Computed, Link } from "./types.js";
|
|
|
3
3
|
export declare function addPendingSource(el: Computed<any>, source: Computed<any>): boolean;
|
|
4
4
|
export declare function setPendingError(el: Computed<any>, source?: Computed<any>, error?: any): void;
|
|
5
5
|
export declare function forEachDependent(el: Computed<any>, fn: (node: Computed<any>, link: Link) => void): void;
|
|
6
|
+
export declare function releaseSettledDependents(el: Computed<any>): void;
|
|
7
|
+
export declare function settleErroredDependents(el: Computed<any>, error: any): void;
|
|
6
8
|
export declare function settlePendingSource(el: Computed<any>): void;
|
|
7
9
|
export declare function isThenable<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
|
|
8
10
|
export declare function handleAsync<T>(el: Computed<T>, result: T | PromiseLike<T> | AsyncIterable<T>, setter?: (value: T) => void): T;
|
|
@@ -14,8 +14,8 @@ import { type NoFn, type ProjectionOptions, type Store, type StoreSetter } from
|
|
|
14
14
|
* optimistic overlay reverts after each transition.
|
|
15
15
|
*
|
|
16
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
|
-
* Restating the default just adds noise.
|
|
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
19
|
*
|
|
20
20
|
* @example
|
|
21
21
|
* ```ts
|
|
@@ -12,6 +12,10 @@ export declare function createProjectionInternal<T extends object = {}>(fn: (dra
|
|
|
12
12
|
* items keep their proxy identity — only added/removed items are
|
|
13
13
|
* created/disposed.
|
|
14
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
|
+
*
|
|
15
19
|
* Returns the projected store directly (no setter — reads only).
|
|
16
20
|
*
|
|
17
21
|
* Use this when you want the structural-sharing / per-property tracking
|
|
@@ -24,7 +28,8 @@ export declare function createProjectionInternal<T extends object = {}>(fn: (dra
|
|
|
24
28
|
* @param seed the backing store value to wrap and reconcile into
|
|
25
29
|
* @param options `ProjectionOptions` — `name`, `key`. `key` defaults to
|
|
26
30
|
* `"id"`; specify it only when your data uses a different identity field
|
|
27
|
-
* (e.g. `{ key: "uuid" }` or `{ key: u => u.slug }`)
|
|
31
|
+
* (e.g. `{ key: "uuid" }` or `{ key: u => u.slug }`), or `null` to merge
|
|
32
|
+
* positionally with no keyed pass.
|
|
28
33
|
*
|
|
29
34
|
* @example
|
|
30
35
|
* ```ts
|
|
@@ -61,5 +66,5 @@ export declare function createProjection<T extends object = {}>(fn: (draft: T) =
|
|
|
61
66
|
* instead of `STORE_OPTIMISTIC_OVERRIDE` even when an async yield fires outside
|
|
62
67
|
* the outer `setProjectionWriteActive` scope.
|
|
63
68
|
*/
|
|
64
|
-
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), wrapCommit?: (write: () => void) => void, onDraftWrite?: () => void): Computed<void | T>;
|
|
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>;
|
|
65
70
|
export declare function createWriteTraps(isActive?: () => boolean, onDraftWrite?: () => void): ProxyHandler<any>;
|
|
@@ -1,3 +1,14 @@
|
|
|
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;
|
|
1
12
|
/**
|
|
2
13
|
* Returns a draft-mutating function that smart-merges `value` into a store,
|
|
3
14
|
* preserving fine-grained reactivity: only changed leaves trigger updates.
|
|
@@ -12,6 +23,9 @@
|
|
|
12
23
|
* the classic pattern for fixed-shape data that churns in place (dashboards,
|
|
13
24
|
* monitors), where no keyed diff pass is needed or wanted.
|
|
14
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
|
+
*
|
|
15
29
|
* @param value the next state to merge in
|
|
16
30
|
* @param key property name (string) or extractor function for stable
|
|
17
31
|
* identity (default `"id"`); pass `null` for positional merging
|
|
@@ -29,8 +29,8 @@ export interface StoreOptions {
|
|
|
29
29
|
}
|
|
30
30
|
/** Options for derived/projected stores created with `createStore(fn)`, `createProjection`, or `createOptimisticStore(fn)`. */
|
|
31
31
|
export interface ProjectionOptions extends StoreOptions {
|
|
32
|
-
/** Key property name or function for reconciliation identity */
|
|
33
|
-
key?: string | ((item: NonNullable<any>) => any);
|
|
32
|
+
/** Key property name or function for reconciliation identity; `null` merges positionally */
|
|
33
|
+
key?: string | ((item: NonNullable<any>) => any) | null;
|
|
34
34
|
/** Single-layer store: root keys reactive, values raw records replaced by reference */
|
|
35
35
|
shallow?: boolean;
|
|
36
36
|
}
|
|
@@ -3,6 +3,8 @@ import type { Computed, Link } from "./types.cjs";
|
|
|
3
3
|
export declare function addPendingSource(el: Computed<any>, source: Computed<any>): boolean;
|
|
4
4
|
export declare function setPendingError(el: Computed<any>, source?: Computed<any>, error?: any): void;
|
|
5
5
|
export declare function forEachDependent(el: Computed<any>, fn: (node: Computed<any>, link: Link) => void): void;
|
|
6
|
+
export declare function releaseSettledDependents(el: Computed<any>): void;
|
|
7
|
+
export declare function settleErroredDependents(el: Computed<any>, error: any): void;
|
|
6
8
|
export declare function settlePendingSource(el: Computed<any>): void;
|
|
7
9
|
export declare function isThenable<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
|
|
8
10
|
export declare function handleAsync<T>(el: Computed<T>, result: T | PromiseLike<T> | AsyncIterable<T>, setter?: (value: T) => void): T;
|
|
@@ -14,8 +14,8 @@ import { type NoFn, type ProjectionOptions, type Store, type StoreSetter } from
|
|
|
14
14
|
* optimistic overlay reverts after each transition.
|
|
15
15
|
*
|
|
16
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
|
-
* Restating the default just adds noise.
|
|
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
19
|
*
|
|
20
20
|
* @example
|
|
21
21
|
* ```ts
|
|
@@ -12,6 +12,10 @@ export declare function createProjectionInternal<T extends object = {}>(fn: (dra
|
|
|
12
12
|
* items keep their proxy identity — only added/removed items are
|
|
13
13
|
* created/disposed.
|
|
14
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
|
+
*
|
|
15
19
|
* Returns the projected store directly (no setter — reads only).
|
|
16
20
|
*
|
|
17
21
|
* Use this when you want the structural-sharing / per-property tracking
|
|
@@ -24,7 +28,8 @@ export declare function createProjectionInternal<T extends object = {}>(fn: (dra
|
|
|
24
28
|
* @param seed the backing store value to wrap and reconcile into
|
|
25
29
|
* @param options `ProjectionOptions` — `name`, `key`. `key` defaults to
|
|
26
30
|
* `"id"`; specify it only when your data uses a different identity field
|
|
27
|
-
* (e.g. `{ key: "uuid" }` or `{ key: u => u.slug }`)
|
|
31
|
+
* (e.g. `{ key: "uuid" }` or `{ key: u => u.slug }`), or `null` to merge
|
|
32
|
+
* positionally with no keyed pass.
|
|
28
33
|
*
|
|
29
34
|
* @example
|
|
30
35
|
* ```ts
|
|
@@ -61,5 +66,5 @@ export declare function createProjection<T extends object = {}>(fn: (draft: T) =
|
|
|
61
66
|
* instead of `STORE_OPTIMISTIC_OVERRIDE` even when an async yield fires outside
|
|
62
67
|
* the outer `setProjectionWriteActive` scope.
|
|
63
68
|
*/
|
|
64
|
-
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), wrapCommit?: (write: () => void) => void, onDraftWrite?: () => void): Computed<void | T>;
|
|
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>;
|
|
65
70
|
export declare function createWriteTraps(isActive?: () => boolean, onDraftWrite?: () => void): ProxyHandler<any>;
|
|
@@ -1,3 +1,14 @@
|
|
|
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;
|
|
1
12
|
/**
|
|
2
13
|
* Returns a draft-mutating function that smart-merges `value` into a store,
|
|
3
14
|
* preserving fine-grained reactivity: only changed leaves trigger updates.
|
|
@@ -12,6 +23,9 @@
|
|
|
12
23
|
* the classic pattern for fixed-shape data that churns in place (dashboards,
|
|
13
24
|
* monitors), where no keyed diff pass is needed or wanted.
|
|
14
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
|
+
*
|
|
15
29
|
* @param value the next state to merge in
|
|
16
30
|
* @param key property name (string) or extractor function for stable
|
|
17
31
|
* identity (default `"id"`); pass `null` for positional merging
|
|
@@ -29,8 +29,8 @@ export interface StoreOptions {
|
|
|
29
29
|
}
|
|
30
30
|
/** Options for derived/projected stores created with `createStore(fn)`, `createProjection`, or `createOptimisticStore(fn)`. */
|
|
31
31
|
export interface ProjectionOptions extends StoreOptions {
|
|
32
|
-
/** Key property name or function for reconciliation identity */
|
|
33
|
-
key?: string | ((item: NonNullable<any>) => any);
|
|
32
|
+
/** Key property name or function for reconciliation identity; `null` merges positionally */
|
|
33
|
+
key?: string | ((item: NonNullable<any>) => any) | null;
|
|
34
34
|
/** Single-layer store: root keys reactive, values raw records replaced by reference */
|
|
35
35
|
shallow?: boolean;
|
|
36
36
|
}
|
package/package.json
CHANGED