@solidjs/signals 2.0.0-beta.23 → 2.0.0-beta.24
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 +171 -65
- package/dist/node.cjs +1116 -995
- package/dist/prod/core/core.js +14 -11
- package/dist/prod/core/error.js +9 -0
- package/dist/prod/core/heap.js +12 -12
- package/dist/prod/core/scheduler.js +6 -6
- package/dist/prod/core/verdict.js +1 -1
- package/dist/prod/map.js +53 -29
- package/dist/prod/store/optimistic.js +29 -27
- package/dist/prod/store/reconcile.js +92 -61
- package/dist/prod/store/store.js +142 -102
- package/dist/prod/store/utils.js +12 -0
- package/dist/types/core/error.d.ts +1 -1
- package/dist/types/store/reconcile.d.ts +17 -8
- package/dist/types-cjs/core/error.d.cts +1 -1
- package/dist/types-cjs/store/reconcile.d.cts +17 -8
- package/package.json +1 -1
package/dist/dev.js
CHANGED
|
@@ -2138,7 +2138,10 @@ function clearSnapshots() {
|
|
|
2138
2138
|
if (snapshotSources) {
|
|
2139
2139
|
for (const source of snapshotSources) {
|
|
2140
2140
|
delete source._snapshotValue;
|
|
2141
|
-
|
|
2141
|
+
// StoreNode targets share one pre-initialized hidden class (see
|
|
2142
|
+
// createStoreProxy) — assign undefined instead of deleting, and only
|
|
2143
|
+
// when present so signal-node sources don't grow the field.
|
|
2144
|
+
if (source[STORE_SNAPSHOT_PROPS] !== undefined) source[STORE_SNAPSHOT_PROPS] = undefined;
|
|
2142
2145
|
}
|
|
2143
2146
|
snapshotSources = null;
|
|
2144
2147
|
}
|
|
@@ -4763,23 +4766,18 @@ function applyStateFast(next, target, keyFn) {
|
|
|
4763
4766
|
let tracked;
|
|
4764
4767
|
if (nodes) {
|
|
4765
4768
|
tracked = nodes[$TRACK];
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
(keyFn(previousValue) != null && keyFn(previousValue) !== keyFn(nextValue))
|
|
4779
|
-
) {
|
|
4780
|
-
tracked && setSignal(tracked, void 0);
|
|
4781
|
-
node && setSignal(node, isWrappable(nextValue) ? wrap(nextValue, target) : nextValue);
|
|
4782
|
-
} else applyState(nextValue, wrap(previousValue, target), keyFn);
|
|
4769
|
+
if (tracked || symbolKeyedRecords.has(nodes)) {
|
|
4770
|
+
const keys = tracked ? getAllKeys(previous, undefined, next) : nodeKeys(nodes);
|
|
4771
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
4772
|
+
diffNodeKey(keys[i], nodes, previous, next, target, tracked, keyFn);
|
|
4773
|
+
}
|
|
4774
|
+
} else {
|
|
4775
|
+
// Untracked, string-only node records (the overwhelmingly common case)
|
|
4776
|
+
// iterate in place — nodeKeys() allocated a fresh key array per object
|
|
4777
|
+
// per pass, which dominates allocation on large-graph reconciles.
|
|
4778
|
+
for (const key in nodes) {
|
|
4779
|
+
diffNodeKey(key, nodes, previous, next, target, tracked, keyFn);
|
|
4780
|
+
}
|
|
4783
4781
|
}
|
|
4784
4782
|
}
|
|
4785
4783
|
if (!tracked && target[STORE_DESC]) applyDescendants(previous, next, target, nodes, keyFn);
|
|
@@ -4792,6 +4790,24 @@ function applyStateFast(next, target, keyFn) {
|
|
|
4792
4790
|
}
|
|
4793
4791
|
}
|
|
4794
4792
|
}
|
|
4793
|
+
// One node-key step of the fast object diff — shared by the array-iterating
|
|
4794
|
+
// (tracked / symbol-keyed) and for-in (plain) loops in applyStateFast.
|
|
4795
|
+
function diffNodeKey(key, nodes, previous, next, target, tracked, keyFn) {
|
|
4796
|
+
const node = nodes[key];
|
|
4797
|
+
const previousValue = unwrap(previous[key]);
|
|
4798
|
+
let nextValue = unwrap(next[key]);
|
|
4799
|
+
if (previousValue === nextValue) return;
|
|
4800
|
+
if (
|
|
4801
|
+
!previousValue ||
|
|
4802
|
+
!isWrappable(previousValue) ||
|
|
4803
|
+
!isWrappable(nextValue) ||
|
|
4804
|
+
Array.isArray(previousValue) !== Array.isArray(nextValue) ||
|
|
4805
|
+
(keyFn(previousValue) != null && keyFn(previousValue) !== keyFn(nextValue))
|
|
4806
|
+
) {
|
|
4807
|
+
tracked && setSignal(tracked, void 0);
|
|
4808
|
+
node && setSignal(node, isWrappable(nextValue) ? wrap(nextValue, target) : nextValue);
|
|
4809
|
+
} else applyState(nextValue, wrap(previousValue, target), keyFn);
|
|
4810
|
+
}
|
|
4795
4811
|
function applyStateSlow(next, target, keyFn) {
|
|
4796
4812
|
const previous = target[STORE_VALUE];
|
|
4797
4813
|
const override = target[STORE_OVERRIDE];
|
|
@@ -4929,17 +4945,26 @@ function applyStateSlow(next, target, keyFn) {
|
|
|
4929
4945
|
}
|
|
4930
4946
|
}
|
|
4931
4947
|
}
|
|
4948
|
+
// No-key reconcile: every item reports "no key", which routes array diffs to
|
|
4949
|
+
// the positional branch and object descent to plain per-property merging.
|
|
4950
|
+
const NOKEY = () => null;
|
|
4932
4951
|
/**
|
|
4933
4952
|
* Returns a draft-mutating function that smart-merges `value` into a store,
|
|
4934
|
-
* preserving
|
|
4935
|
-
* new states. Useful when applying server payloads or full-replacement data
|
|
4936
|
-
* onto an existing store without losing fine-grained reactivity.
|
|
4953
|
+
* preserving fine-grained reactivity: only changed leaves trigger updates.
|
|
4937
4954
|
*
|
|
4938
|
-
*
|
|
4939
|
-
*
|
|
4955
|
+
* With a `key` (default `"id"`), array items whose key matches between old
|
|
4956
|
+
* and new states keep their identity (updated in place, moves and removals
|
|
4957
|
+
* update the corresponding signals) — the shape for keyed server payloads.
|
|
4958
|
+
* Items without the key field fall back to positional matching.
|
|
4959
|
+
*
|
|
4960
|
+
* With `key: null`, matching is purely positional: index N of the new array
|
|
4961
|
+
* merges into index N of the old, and object properties merge recursively —
|
|
4962
|
+
* the classic pattern for fixed-shape data that churns in place (dashboards,
|
|
4963
|
+
* monitors), where no keyed diff pass is needed or wanted.
|
|
4940
4964
|
*
|
|
4941
4965
|
* @param value the next state to merge in
|
|
4942
|
-
* @param key property name (string) or extractor function for stable
|
|
4966
|
+
* @param key property name (string) or extractor function for stable
|
|
4967
|
+
* identity (default `"id"`); pass `null` for positional merging
|
|
4943
4968
|
*
|
|
4944
4969
|
* @example
|
|
4945
4970
|
* ```ts
|
|
@@ -4947,13 +4972,20 @@ function applyStateSlow(next, target, keyFn) {
|
|
|
4947
4972
|
*
|
|
4948
4973
|
* async function refresh() {
|
|
4949
4974
|
* const fresh = await api.getTodos();
|
|
4950
|
-
* setTodos(reconcile(fresh
|
|
4975
|
+
* setTodos(reconcile(fresh)); // diff-merge by `id`
|
|
4951
4976
|
* }
|
|
4977
|
+
*
|
|
4978
|
+
* // fixed-shape polling data — positional merge
|
|
4979
|
+
* setStats(reconcile(nextStats, null));
|
|
4952
4980
|
* ```
|
|
4953
4981
|
*/
|
|
4954
|
-
function reconcile(value, key) {
|
|
4982
|
+
function reconcile(value, key = "id") {
|
|
4955
4983
|
return state => {
|
|
4956
4984
|
if (state == null) throw new Error("Cannot reconcile null or undefined state");
|
|
4985
|
+
if (key === null) {
|
|
4986
|
+
applyState(value, state, NOKEY);
|
|
4987
|
+
return;
|
|
4988
|
+
}
|
|
4957
4989
|
const keyFn = typeof key === "string" ? item => item[key] : key;
|
|
4958
4990
|
const eq = keyFn(state);
|
|
4959
4991
|
if (eq !== undefined && keyFn(value) !== eq)
|
|
@@ -5156,13 +5188,37 @@ const STORE_VALUE = "v",
|
|
|
5156
5188
|
STORE_PARENT = "u",
|
|
5157
5189
|
STORE_DESC = "d";
|
|
5158
5190
|
const STORE_SELF_PENDING = Symbol("STORE_SELF_PENDING");
|
|
5191
|
+
// Every StoreNode field is initialized up front, in one fixed order, so all
|
|
5192
|
+
// targets share a single hidden class. The traps and reconcile read these
|
|
5193
|
+
// fields on their hottest paths; fields added lazily in varying orders made
|
|
5194
|
+
// those loads megamorphic across a large store graph (dictionary-mode probes
|
|
5195
|
+
// on every trap hit). Assignments elsewhere must never `delete` a field —
|
|
5196
|
+
// write `undefined` instead, or the shape degrades again.
|
|
5197
|
+
function initStoreFields(newTarget) {
|
|
5198
|
+
newTarget[STORE_OVERRIDE] = undefined;
|
|
5199
|
+
newTarget[STORE_OPTIMISTIC_OVERRIDE] = undefined;
|
|
5200
|
+
newTarget[STORE_OPTIMISTIC_OWNERS] = undefined;
|
|
5201
|
+
newTarget[STORE_NODE] = undefined;
|
|
5202
|
+
newTarget[STORE_HAS] = undefined;
|
|
5203
|
+
newTarget[STORE_CUSTOM_PROTO] = undefined;
|
|
5204
|
+
newTarget[STORE_WRAP] = undefined;
|
|
5205
|
+
newTarget[STORE_LOOKUP] = undefined;
|
|
5206
|
+
newTarget[STORE_FIREWALL] = undefined;
|
|
5207
|
+
newTarget[STORE_OPTIMISTIC] = undefined;
|
|
5208
|
+
newTarget[STORE_SNAPSHOT_PROPS] = undefined;
|
|
5209
|
+
newTarget[STORE_PARENT] = undefined;
|
|
5210
|
+
newTarget[STORE_DESC] = undefined;
|
|
5211
|
+
newTarget[$PROXY] = null;
|
|
5212
|
+
}
|
|
5159
5213
|
function createStoreProxy(value, traps = storeTraps, extend) {
|
|
5160
5214
|
let newTarget;
|
|
5161
5215
|
if (Array.isArray(value)) {
|
|
5162
5216
|
newTarget = [];
|
|
5163
|
-
newTarget
|
|
5217
|
+
newTarget[STORE_VALUE] = value;
|
|
5218
|
+
initStoreFields(newTarget);
|
|
5164
5219
|
} else {
|
|
5165
|
-
newTarget = {
|
|
5220
|
+
newTarget = { [STORE_VALUE]: value };
|
|
5221
|
+
initStoreFields(newTarget);
|
|
5166
5222
|
const unwrapped = value?.[$TARGET]?.[STORE_VALUE] ?? value;
|
|
5167
5223
|
const proto = Object.getPrototypeOf(unwrapped);
|
|
5168
5224
|
if (proto !== null && proto !== Object.prototype) {
|
|
@@ -5741,6 +5797,28 @@ const storeTraps = {
|
|
|
5741
5797
|
trackSelf(target);
|
|
5742
5798
|
return receiver;
|
|
5743
5799
|
}
|
|
5800
|
+
// Hot path: an existing node on a plain target — no firewall (so neither
|
|
5801
|
+
// selfRead nor the uninitialized guard can apply), no override layers,
|
|
5802
|
+
// no write scope, and a raw (non-proxy) source. This is the shape of
|
|
5803
|
+
// every effect re-read of a settled store; it pays one node read and the
|
|
5804
|
+
// wrap check, nothing else. Any exotic bit falls through to the full
|
|
5805
|
+
// resolution below, and dev strictRead keeps its warning path.
|
|
5806
|
+
if (
|
|
5807
|
+
!strictRead &&
|
|
5808
|
+
target[STORE_FIREWALL] === undefined &&
|
|
5809
|
+
target[STORE_OVERRIDE] === undefined &&
|
|
5810
|
+
target[STORE_OPTIMISTIC_OVERRIDE] === undefined &&
|
|
5811
|
+
!writeOverride &&
|
|
5812
|
+
(Writing === null || !Writing.has(receiver))
|
|
5813
|
+
) {
|
|
5814
|
+
const nodes = target[STORE_NODE];
|
|
5815
|
+
const node = nodes && nodes[property];
|
|
5816
|
+
if (node !== undefined && target[STORE_VALUE][$TARGET] === undefined) {
|
|
5817
|
+
let value = read(node);
|
|
5818
|
+
if (value === $DELETED) value = undefined;
|
|
5819
|
+
return isWrappable(value) ? wrap(value, target) : value;
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5744
5822
|
const selfRead = getObserver() === target[STORE_FIREWALL];
|
|
5745
5823
|
const nodes = getNodes(target, STORE_NODE);
|
|
5746
5824
|
const tracked = selfRead ? undefined : nodes[property];
|
|
@@ -6330,8 +6408,10 @@ function clearOptimisticOverride(target, completing) {
|
|
|
6330
6408
|
}
|
|
6331
6409
|
}
|
|
6332
6410
|
if (!remaining) {
|
|
6333
|
-
delete
|
|
6334
|
-
delete
|
|
6411
|
+
// Assignment, not delete: StoreNode targets share one pre-initialized
|
|
6412
|
+
// hidden class (see createStoreProxy) and a delete would demote it.
|
|
6413
|
+
target[STORE_OPTIMISTIC_OVERRIDE] = undefined;
|
|
6414
|
+
target[STORE_OPTIMISTIC_OWNERS] = undefined;
|
|
6335
6415
|
}
|
|
6336
6416
|
// Notify $TRACK
|
|
6337
6417
|
if (cleared && nodes?.[$TRACK]) {
|
|
@@ -6524,6 +6604,18 @@ function snapshotImpl(item, track, map, lookup) {
|
|
|
6524
6604
|
if (pendingCheckActive) witnessAffectsMark(target);
|
|
6525
6605
|
}
|
|
6526
6606
|
override = mergedOverlay(target);
|
|
6607
|
+
// A derived store's STORE_VALUE is the inner store's live proxy
|
|
6608
|
+
// (store-in-store: createOptimisticStore/createProjection over a store).
|
|
6609
|
+
// Without an overlay of its own, the fast path below would map to — and
|
|
6610
|
+
// could return — that proxy verbatim. Recurse instead so the
|
|
6611
|
+
// inner store's own target branch unwraps it (chains of any depth).
|
|
6612
|
+
// With an overlay, a fresh `result` is always built, so the walk over the
|
|
6613
|
+
// inner proxy already copies plain values.
|
|
6614
|
+
if (!override && target[STORE_VALUE][$TARGET]) {
|
|
6615
|
+
unwrapped = snapshotImpl(target[STORE_VALUE], track, map, lookup);
|
|
6616
|
+
map.set(item, unwrapped);
|
|
6617
|
+
return unwrapped;
|
|
6618
|
+
}
|
|
6527
6619
|
isArray = Array.isArray(target[STORE_VALUE]);
|
|
6528
6620
|
map.set(
|
|
6529
6621
|
item,
|
|
@@ -6937,19 +7029,7 @@ function updateKeyedMap() {
|
|
|
6937
7029
|
this._items = newItems.slice(0);
|
|
6938
7030
|
this._len = newLen;
|
|
6939
7031
|
} else {
|
|
6940
|
-
let start,
|
|
6941
|
-
end,
|
|
6942
|
-
newEnd,
|
|
6943
|
-
item,
|
|
6944
|
-
key,
|
|
6945
|
-
newIndices,
|
|
6946
|
-
newIndicesNext,
|
|
6947
|
-
removed,
|
|
6948
|
-
created,
|
|
6949
|
-
temp = new Array(newLen),
|
|
6950
|
-
tempNodes = new Array(newLen);
|
|
6951
|
-
rows = this._rows ? new Array(newLen) : undefined;
|
|
6952
|
-
indexes = this._indexes ? new Array(newLen) : undefined;
|
|
7032
|
+
let start, end, newEnd, item, key, newIndices, newIndicesNext, removed, created;
|
|
6953
7033
|
// skip common prefix
|
|
6954
7034
|
for (
|
|
6955
7035
|
start = 0, end = Math.min(this._len, newLen);
|
|
@@ -6960,7 +7040,8 @@ function updateKeyedMap() {
|
|
|
6960
7040
|
) {
|
|
6961
7041
|
if (this._rows) setSignal(this._rows[start], newItems[start]);
|
|
6962
7042
|
}
|
|
6963
|
-
// common suffix
|
|
7043
|
+
// skip common suffix — counted only; retained entries land in one pass
|
|
7044
|
+
// at commit instead of being staged and copied twice
|
|
6964
7045
|
for (
|
|
6965
7046
|
end = this._len - 1, newEnd = newLen - 1;
|
|
6966
7047
|
end >= start &&
|
|
@@ -6968,13 +7049,21 @@ function updateKeyedMap() {
|
|
|
6968
7049
|
(this._items[end] === newItems[newEnd] ||
|
|
6969
7050
|
(this._rows && compare(this._key, this._items[end], newItems[newEnd])));
|
|
6970
7051
|
end--, newEnd--
|
|
6971
|
-
)
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
7052
|
+
);
|
|
7053
|
+
// no structural change (every position matched in place at equal
|
|
7054
|
+
// length — the common post-reconcile shape): keep the same mapped
|
|
7055
|
+
// array identity so downstream consumers don't re-run at all
|
|
7056
|
+
if (start === newLen && this._len === newLen) {
|
|
7057
|
+
this._items = newItems.slice(0);
|
|
7058
|
+
return;
|
|
6976
7059
|
}
|
|
6977
|
-
|
|
7060
|
+
const dif = newLen - this._len;
|
|
7061
|
+
const temp = new Array(newLen);
|
|
7062
|
+
const tempNodes = new Array(newLen);
|
|
7063
|
+
rows = this._rows ? new Array(newLen) : undefined;
|
|
7064
|
+
indexes = this._indexes ? new Array(newLen) : undefined;
|
|
7065
|
+
// 0) prepare a map of all indices in the changed window of newItems,
|
|
7066
|
+
// scanning backwards so we encounter them in natural order
|
|
6978
7067
|
newIndices = new Map();
|
|
6979
7068
|
newIndicesNext = new Array(newEnd + 1);
|
|
6980
7069
|
for (j = newEnd; j >= start; j--) {
|
|
@@ -6984,7 +7073,9 @@ function updateKeyedMap() {
|
|
|
6984
7073
|
newIndicesNext[j] = i === undefined ? -1 : i;
|
|
6985
7074
|
newIndices.set(key, j);
|
|
6986
7075
|
}
|
|
6987
|
-
// 1) step through
|
|
7076
|
+
// 1) step through the old changed window and see if items can be found
|
|
7077
|
+
// in the new set; if so, stage them at their new positions; if not,
|
|
7078
|
+
// queue them for disposal at commit
|
|
6988
7079
|
for (i = start; i <= end; i++) {
|
|
6989
7080
|
item = this._items[i];
|
|
6990
7081
|
key = this._key ? this._key(item) : item;
|
|
@@ -7000,8 +7091,8 @@ function updateKeyedMap() {
|
|
|
7000
7091
|
}
|
|
7001
7092
|
// 2) create new rows into the temp arrays; an abort disposes only these
|
|
7002
7093
|
try {
|
|
7003
|
-
for (j = start; j
|
|
7004
|
-
if (j
|
|
7094
|
+
for (j = start; j <= newEnd; j++) {
|
|
7095
|
+
if (tempNodes[j] !== undefined) continue;
|
|
7005
7096
|
(created ??= []).push((tempNodes[j] = createOwner()));
|
|
7006
7097
|
temp[j] = runWithOwner(tempNodes[j], mapper);
|
|
7007
7098
|
}
|
|
@@ -7009,24 +7100,39 @@ function updateKeyedMap() {
|
|
|
7009
7100
|
if (created) for (i = 0; i < created.length; i++) created[i].dispose();
|
|
7010
7101
|
throw err;
|
|
7011
7102
|
}
|
|
7012
|
-
// 3) commit: land
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7103
|
+
// 3) commit: land the retained prefix and suffix plus the staged window
|
|
7104
|
+
// into the fresh arrays, swap them in (new identity for downstream
|
|
7105
|
+
// change propagation), then dispose exited rows
|
|
7106
|
+
for (i = 0; i < start; i++) {
|
|
7107
|
+
temp[i] = this._mappings[i];
|
|
7108
|
+
tempNodes[i] = this._nodes[i];
|
|
7109
|
+
rows && (rows[i] = this._rows[i]);
|
|
7110
|
+
indexes && (indexes[i] = this._indexes[i]);
|
|
7111
|
+
}
|
|
7112
|
+
for (j = start; j <= newEnd; j++) {
|
|
7113
|
+
if (rows) setSignal(rows[j], newItems[j]);
|
|
7114
|
+
if (indexes) setSignal(indexes[j], j);
|
|
7115
|
+
}
|
|
7116
|
+
for (j = newEnd + 1; j < newLen; j++) {
|
|
7117
|
+
temp[j] = this._mappings[j - dif];
|
|
7118
|
+
tempNodes[j] = this._nodes[j - dif];
|
|
7016
7119
|
if (rows) {
|
|
7017
|
-
|
|
7018
|
-
setSignal(
|
|
7120
|
+
rows[j] = this._rows[j - dif];
|
|
7121
|
+
setSignal(rows[j], newItems[j]);
|
|
7019
7122
|
}
|
|
7020
7123
|
if (indexes) {
|
|
7021
|
-
|
|
7022
|
-
setSignal(
|
|
7124
|
+
indexes[j] = this._indexes[j - dif];
|
|
7125
|
+
if (dif !== 0) setSignal(indexes[j], j);
|
|
7023
7126
|
}
|
|
7024
7127
|
}
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7128
|
+
this._mappings = temp;
|
|
7129
|
+
this._nodes = tempNodes;
|
|
7130
|
+
rows && (this._rows = rows);
|
|
7131
|
+
indexes && (this._indexes = indexes);
|
|
7132
|
+
this._len = newLen;
|
|
7133
|
+
// save a copy of the mapped items for the next update
|
|
7029
7134
|
this._items = newItems.slice(0);
|
|
7135
|
+
if (removed) for (i = 0; i < removed.length; i++) removed[i].dispose();
|
|
7030
7136
|
}
|
|
7031
7137
|
});
|
|
7032
7138
|
return this._mappings;
|