@solidjs/signals 2.0.0-beta.26 → 2.0.0-beta.28
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 +258 -67
- package/dist/node.cjs +1117 -915
- package/dist/prod/core/action.js +11 -6
- package/dist/prod/core/async.js +42 -11
- package/dist/prod/core/core.js +83 -69
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +4 -4
- package/dist/prod/core/lanes.js +1 -1
- package/dist/prod/core/optimistic.js +5 -5
- package/dist/prod/core/owner.js +1 -1
- package/dist/prod/core/scheduler.js +53 -32
- package/dist/prod/core/verdict.js +26 -26
- package/dist/prod/map.js +2 -2
- package/dist/prod/store/optimistic.js +52 -33
- package/dist/prod/store/projection.js +38 -33
- package/dist/prod/store/reconcile.js +299 -243
- package/dist/prod/store/store.js +98 -47
- package/dist/types/core/async.d.ts +1 -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 +1 -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
|
|
|
@@ -181,11 +181,39 @@ function wrapShallow(e) {
|
|
|
181
181
|
return r;
|
|
182
182
|
}
|
|
183
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
|
+
|
|
184
191
|
function isWrappable(e) {
|
|
185
192
|
if (e == null || typeof e !== "object" || Object.isFrozen(e)) return false;
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
|
|
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;
|
|
189
217
|
}
|
|
190
218
|
|
|
191
219
|
let writeOverride = false;
|
|
@@ -263,7 +291,7 @@ function ownEnumerableKeysPlain(e) {
|
|
|
263
291
|
* The value a store leaf's backing signal currently shows to readers: active
|
|
264
292
|
* override, else held pending value, else committed value.
|
|
265
293
|
*/ function visibleNodeValue(e) {
|
|
266
|
-
return e.Ae !== undefined && e.Ae !== NOT_PENDING ? unwrapOverride(e.Ae) : e.
|
|
294
|
+
return e.Ae !== undefined && e.Ae !== NOT_PENDING ? unwrapOverride(e.Ae) : e.De !== NOT_PENDING ? e.De : e.Ue;
|
|
267
295
|
}
|
|
268
296
|
|
|
269
297
|
function hasOwnStoreProperty(e, t) {
|
|
@@ -436,11 +464,11 @@ o) {
|
|
|
436
464
|
// Callers guard on `pendingCheckActive`, which only flips inside
|
|
437
465
|
// isPending() — the verdict layer is loaded and its hook installed.
|
|
438
466
|
const r = e[STORE_NODE]?.[$AFFECTS];
|
|
439
|
-
if (r?.t) GlobalQueue.
|
|
467
|
+
if (r?.t) GlobalQueue.Lt(r);
|
|
440
468
|
if (affectsScopes.size) {
|
|
441
469
|
const n = e[STORE_VALUE];
|
|
442
470
|
for (const [e, o] of affectsScopes) {
|
|
443
|
-
if (e !== r && e.t && o.scope.has(n) && (o.key === undefined || o.key === t)) GlobalQueue.
|
|
471
|
+
if (e !== r && e.t && o.scope.has(n) && (o.key === undefined || o.key === t)) GlobalQueue.Lt(e);
|
|
444
472
|
}
|
|
445
473
|
}
|
|
446
474
|
}
|
|
@@ -626,8 +654,11 @@ function prepareStoreWrite(e, t, r) {
|
|
|
626
654
|
* concurrent actions writing disjoint keys must revert independently, exactly
|
|
627
655
|
* like optimistic signal nodes do via the transition's _optimisticNodes.
|
|
628
656
|
* `activeTransition` is the write's transaction (action() opens it before the
|
|
629
|
-
* body runs); null marks an ambient write
|
|
630
|
-
*
|
|
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.
|
|
631
662
|
*/ function stampOptimisticOwner(e, t, r) {
|
|
632
663
|
if (t === STORE_OPTIMISTIC_OVERRIDE) (e[STORE_OPTIMISTIC_OWNERS] ??= Object.create(null))[r] = activeTransition;
|
|
633
664
|
}
|
|
@@ -680,16 +711,36 @@ function notifyStoreProperty(e, t, r, n, o, i) {
|
|
|
680
711
|
let Writing = null;
|
|
681
712
|
|
|
682
713
|
/**
|
|
683
|
-
* A derived store
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
|
|
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) {
|
|
691
740
|
const t = e[STORE_FIREWALL];
|
|
692
|
-
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);
|
|
693
744
|
}
|
|
694
745
|
|
|
695
746
|
const storeTraps = {
|
|
@@ -739,20 +790,20 @@ const storeTraps = {
|
|
|
739
790
|
const s = getOverlayLayer(e, t);
|
|
740
791
|
const E = !!s;
|
|
741
792
|
const S = !!e[STORE_VALUE][$TARGET];
|
|
742
|
-
const
|
|
793
|
+
const T = s ?? e[STORE_VALUE];
|
|
743
794
|
if (!i) {
|
|
744
|
-
const n = Object.getOwnPropertyDescriptor(
|
|
795
|
+
const n = Object.getOwnPropertyDescriptor(T, t);
|
|
745
796
|
if (n && n.get) return n.get.call(r);
|
|
746
797
|
if (!n && !E && e[STORE_CUSTOM_PROTO]) {
|
|
747
|
-
const e = unwrapStoreValue(
|
|
798
|
+
const e = unwrapStoreValue(T);
|
|
748
799
|
if (hasInheritedAccessor(e, t)) {
|
|
749
|
-
return Reflect.get(
|
|
800
|
+
return Reflect.get(T, t, r);
|
|
750
801
|
}
|
|
751
802
|
}
|
|
752
803
|
}
|
|
753
804
|
if (writeOnly(r)) {
|
|
754
805
|
if (isPrototypePollutionKey(t) && !hasOwnStoreProperty(e, t)) return undefined;
|
|
755
|
-
let r = i && (E || !S) ? visibleNodeValue(i) :
|
|
806
|
+
let r = i && (E || !S) ? visibleNodeValue(i) : T[t];
|
|
756
807
|
r === $DELETED && (r = undefined);
|
|
757
808
|
if (!isWrappable(r)) return r;
|
|
758
809
|
// Shallow boundary: records are replaced, never edited in place. Reads
|
|
@@ -764,14 +815,14 @@ const storeTraps = {
|
|
|
764
815
|
Writing?.add(n);
|
|
765
816
|
return n;
|
|
766
817
|
}
|
|
767
|
-
let
|
|
768
|
-
|
|
818
|
+
let c = i ? E || !S ? read(o[t]) : (read(o[t]), T[t]) : T[t];
|
|
819
|
+
c === $DELETED && (c = undefined);
|
|
769
820
|
if (!i) {
|
|
770
|
-
if (!E && typeof
|
|
821
|
+
if (!E && typeof c === "function" && !Object.prototype.hasOwnProperty.call(T, t)) {
|
|
771
822
|
let t;
|
|
772
|
-
return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ?
|
|
823
|
+
return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? c.bind(T) : c;
|
|
773
824
|
} else if (getObserver() && !n) {
|
|
774
|
-
return read(getNode(e, o, t, isWrappable(
|
|
825
|
+
return read(getNode(e, o, t, isWrappable(c) ? wrap(c, e) : c, isEqual, e[STORE_SNAPSHOT_PROPS]));
|
|
775
826
|
}
|
|
776
827
|
}
|
|
777
828
|
// Untracked fall-through (tracked reads already threw via their node in
|
|
@@ -783,8 +834,8 @@ const storeTraps = {
|
|
|
783
834
|
// threw a fresh NotReadyError for an already-settled source, which no
|
|
784
835
|
// sweep would ever release (#2938: projection over an async store wedged
|
|
785
836
|
// its Loading boundary on `undefined`).
|
|
786
|
-
if (!n && !getObserver())
|
|
787
|
-
return isWrappable(
|
|
837
|
+
if (!n && !getObserver()) throwIfUnreadable(e);
|
|
838
|
+
return isWrappable(c) ? wrap(c, e) : c;
|
|
788
839
|
},
|
|
789
840
|
has(e, t) {
|
|
790
841
|
if (t === $PROXY || t === $TRACK || t === "__proto__") return true;
|
|
@@ -803,7 +854,7 @@ const storeTraps = {
|
|
|
803
854
|
if (getObserver()) {
|
|
804
855
|
return read(getNode(e, o, t, n));
|
|
805
856
|
}
|
|
806
|
-
|
|
857
|
+
throwIfUnreadable(e);
|
|
807
858
|
return n;
|
|
808
859
|
},
|
|
809
860
|
set(e, t, r) {
|
|
@@ -818,45 +869,45 @@ const storeTraps = {
|
|
|
818
869
|
// Shallow slots hold store proxies verbatim (pass-through reference,
|
|
819
870
|
// never raw-marked — see markRawOne/#2932); everything else unwraps
|
|
820
871
|
// and marks as usual.
|
|
821
|
-
const
|
|
822
|
-
const
|
|
823
|
-
if (e[STORE_SHALLOW] && !
|
|
872
|
+
const T = !!e[STORE_SHALLOW] && r?.[$TARGET] !== undefined;
|
|
873
|
+
const c = T ? r : unwrapStoreValue(r);
|
|
874
|
+
if (e[STORE_SHALLOW] && !T && isWrappable(c)) {
|
|
824
875
|
// Flip the live gate too: a bare add was inert unless something else
|
|
825
876
|
// had already marked a raw somewhere (wrap() checks rawValuesUsed
|
|
826
877
|
// first), so the documented set-trap ingest mark silently no-oped in
|
|
827
878
|
// apps whose only shallow data arrived through writes.
|
|
828
879
|
rawValuesUsed = true;
|
|
829
|
-
rawValues.add(
|
|
880
|
+
rawValues.add(c);
|
|
830
881
|
}
|
|
831
882
|
// Symbol-keyed writes on arrays are metadata, not index writes — never run
|
|
832
883
|
// them through the numeric index/length machinery (`parseInt` on a symbol
|
|
833
884
|
// throws). #2769
|
|
834
|
-
const
|
|
835
|
-
const
|
|
836
|
-
const u =
|
|
837
|
-
const a =
|
|
838
|
-
const l =
|
|
839
|
-
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;
|
|
840
891
|
armOptimisticStoreWrite(e, n);
|
|
841
|
-
if (
|
|
892
|
+
if (c !== undefined && c === o && l === undefined) {
|
|
842
893
|
delete e[i]?.[t];
|
|
843
894
|
if (i === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t];
|
|
844
895
|
} else {
|
|
845
896
|
const r = e[i] || (e[i] = Object.create(null));
|
|
846
|
-
r[t] =
|
|
897
|
+
r[t] = c;
|
|
847
898
|
stampOptimisticOwner(e, i, t);
|
|
848
899
|
if (l !== undefined) {
|
|
849
900
|
r.length = l;
|
|
850
901
|
stampOptimisticOwner(e, i, "length");
|
|
851
902
|
}
|
|
852
903
|
}
|
|
853
|
-
notifyStoreProperty(e, t, "set",
|
|
904
|
+
notifyStoreProperty(e, t, "set", c, E, S);
|
|
854
905
|
// Shrinking an array's length must remove the truncated indices, otherwise
|
|
855
906
|
// they leak through `has`, `ownKeys`, and (tracked) index reads from the
|
|
856
907
|
// underlying value. Mark each as deleted and notify so reactive reads update. #2768
|
|
857
|
-
if (Array.isArray(O) && t === "length" && typeof
|
|
908
|
+
if (Array.isArray(O) && t === "length" && typeof c === "number" && typeof E === "number" && c < E) {
|
|
858
909
|
const t = e[i] || (e[i] = Object.create(null));
|
|
859
|
-
for (let r =
|
|
910
|
+
for (let r = c; r < E; r++) {
|
|
860
911
|
if (t[r] === $DELETED) continue;
|
|
861
912
|
const n = r in t ? t[r] : O[r];
|
|
862
913
|
if (!(r in t) && !(r in O)) continue;
|
|
@@ -933,7 +984,7 @@ const storeTraps = {
|
|
|
933
984
|
// path is exempt (like the get/has traps' writeOnly early returns):
|
|
934
985
|
// the first landing's reconcile enumerates the store while
|
|
935
986
|
// STATUS_UNINITIALIZED is still set — it IS the initialization.
|
|
936
|
-
if (!getObserver() && !writeOnly(e[$PROXY]))
|
|
987
|
+
if (!getObserver() && !writeOnly(e[$PROXY])) throwIfUnreadable(e);
|
|
937
988
|
}
|
|
938
989
|
// Merge optimistic override with regular override for key enumeration
|
|
939
990
|
let t = getKeys(e[STORE_VALUE], e[STORE_OVERRIDE], false);
|
|
@@ -4,6 +4,7 @@ export declare function addPendingSource(el: Computed<any>, source: Computed<any
|
|
|
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
6
|
export declare function releaseSettledDependents(el: Computed<any>): void;
|
|
7
|
+
export declare function settleErroredDependents(el: Computed<any>, error: any): void;
|
|
7
8
|
export declare function settlePendingSource(el: Computed<any>): void;
|
|
8
9
|
export declare function isThenable<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
|
|
9
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
|
}
|
|
@@ -4,6 +4,7 @@ export declare function addPendingSource(el: Computed<any>, source: Computed<any
|
|
|
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
6
|
export declare function releaseSettledDependents(el: Computed<any>): void;
|
|
7
|
+
export declare function settleErroredDependents(el: Computed<any>, error: any): void;
|
|
7
8
|
export declare function settlePendingSource(el: Computed<any>): void;
|
|
8
9
|
export declare function isThenable<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
|
|
9
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