@rohal12/spindle 0.59.5 → 0.59.6
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/pkg/format.js +1 -1
- package/dist/pkg/headless.js +220 -96
- package/package.json +1 -1
- package/src/components/macros/Unset.tsx +5 -1
- package/src/execute-mutation.ts +56 -18
- package/src/store.ts +19 -8
- package/src/structural.ts +224 -114
- package/src/utils/object-path.ts +6 -3
package/dist/pkg/headless.js
CHANGED
|
@@ -13244,13 +13244,14 @@ function atomicName(value) {
|
|
|
13244
13244
|
* Members of Object.prototype (`constructor`, `toString`, `__proto__`, ...)
|
|
13245
13245
|
* are not story state: unless an object holds one as its own property, it
|
|
13246
13246
|
* reads as missing, as setByPath() treats it. Other inherited properties
|
|
13247
|
-
* (class getters, `size` of a Map) are read
|
|
13247
|
+
* (class getters, `size` of a Map) are read, and so are those of a string,
|
|
13248
|
+
* number or boolean (`length` of a string), as JavaScript boxes them.
|
|
13248
13249
|
*/
|
|
13249
13250
|
function getByPath(obj, segments) {
|
|
13250
13251
|
let current = obj;
|
|
13251
13252
|
for (const seg of segments) {
|
|
13252
|
-
if (current == null
|
|
13253
|
-
if (seg in Object.prototype && !hasOwn(current, seg)) return
|
|
13253
|
+
if (current == null) return void 0;
|
|
13254
|
+
if (seg in Object.prototype && !hasOwn(Object(current), seg)) return;
|
|
13254
13255
|
current = current[seg];
|
|
13255
13256
|
}
|
|
13256
13257
|
return current;
|
|
@@ -13407,14 +13408,21 @@ function deepClone(value, options = {}) {
|
|
|
13407
13408
|
if (isTemporal(val)) return val;
|
|
13408
13409
|
if (val instanceof Error) {
|
|
13409
13410
|
const copy = keep(val, Object.create(Object.getPrototypeOf(val)));
|
|
13410
|
-
for (const key of [...ERROR_HIDDEN_KEYS, "stack"]) if (hasOwn(val, key))
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13411
|
+
for (const key of [...ERROR_HIDDEN_KEYS, "stack"]) if (hasOwn(val, key)) {
|
|
13412
|
+
const held = val[key];
|
|
13413
|
+
Object.defineProperty(copy, key, {
|
|
13414
|
+
value: settled(clone(held), held),
|
|
13415
|
+
writable: true,
|
|
13416
|
+
configurable: true
|
|
13417
|
+
});
|
|
13418
|
+
}
|
|
13415
13419
|
return copyKeys(val, copy);
|
|
13416
13420
|
}
|
|
13417
13421
|
}
|
|
13422
|
+
function settled(copy, original) {
|
|
13423
|
+
const settle = options.settle;
|
|
13424
|
+
return settle && isObjectValue(copy) && isObjectValue(original) ? settle(copy, original) : copy;
|
|
13425
|
+
}
|
|
13418
13426
|
function clone(val) {
|
|
13419
13427
|
if (val === null || typeof val !== "object") return val;
|
|
13420
13428
|
const obj = val;
|
|
@@ -13426,16 +13434,12 @@ function deepClone(value, options = {}) {
|
|
|
13426
13434
|
}
|
|
13427
13435
|
if (val instanceof Map) {
|
|
13428
13436
|
const copy = keep(obj, /* @__PURE__ */ new Map());
|
|
13429
|
-
for (const [k, v] of val)
|
|
13430
|
-
const key = clone(k);
|
|
13431
|
-
const settle = options.mapKey;
|
|
13432
|
-
copy.set(settle && isObjectValue(key) && isObjectValue(k) ? settle(key, k) : key, clone(v));
|
|
13433
|
-
}
|
|
13437
|
+
for (const [k, v] of val) copy.set(settled(clone(k), k), clone(v));
|
|
13434
13438
|
return copy;
|
|
13435
13439
|
}
|
|
13436
13440
|
if (val instanceof Set) {
|
|
13437
13441
|
const copy = keep(obj, /* @__PURE__ */ new Set());
|
|
13438
|
-
for (const v of val) copy.add(clone(v));
|
|
13442
|
+
for (const v of val) copy.add(settled(clone(v), v));
|
|
13439
13443
|
return copy;
|
|
13440
13444
|
}
|
|
13441
13445
|
const builtin = cloneBuiltin(obj);
|
|
@@ -13635,7 +13639,9 @@ function mergesWith(a, b, arrays) {
|
|
|
13635
13639
|
* is none either. Values that merge are reported as 'nested', to be
|
|
13636
13640
|
* compared key by key; they may still be equal. With `arrays`, arrays
|
|
13637
13641
|
* merge too: elements at the indices both hold are compared, and the
|
|
13638
|
-
* elements one has beyond the other's length are added or deleted.
|
|
13642
|
+
* elements one has beyond the other's length are added or deleted. An
|
|
13643
|
+
* index that holds no element (a hole) is no element, not an undefined one,
|
|
13644
|
+
* and holes the length gained at the end are the change of `length`, by how many.
|
|
13639
13645
|
*/
|
|
13640
13646
|
function keyChanges(before, after, arrays) {
|
|
13641
13647
|
const changes = [];
|
|
@@ -13656,16 +13662,41 @@ function keyChanges(before, after, arrays) {
|
|
|
13656
13662
|
if (Array.isArray(after)) {
|
|
13657
13663
|
const b = before;
|
|
13658
13664
|
const a = after;
|
|
13659
|
-
for (let i = 0; i < Math.min(b.length, a.length); i++)
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
13664
|
-
|
|
13665
|
+
for (let i = 0; i < Math.min(b.length, a.length); i++) {
|
|
13666
|
+
const key = String(i);
|
|
13667
|
+
if (i in b && i in a) compare(key, b[i], a[i]);
|
|
13668
|
+
else if (i in b) changes.push({
|
|
13669
|
+
key,
|
|
13670
|
+
kind: "deleted",
|
|
13671
|
+
hole: true
|
|
13672
|
+
});
|
|
13673
|
+
else if (i in a) changes.push({
|
|
13674
|
+
key,
|
|
13675
|
+
kind: "added",
|
|
13676
|
+
after: a[i]
|
|
13677
|
+
});
|
|
13678
|
+
}
|
|
13679
|
+
let next = b.length;
|
|
13680
|
+
for (let i = b.length; i < a.length; i++) {
|
|
13681
|
+
if (!(i in a)) continue;
|
|
13682
|
+
const gap = i - next;
|
|
13683
|
+
changes.push({
|
|
13684
|
+
key: String(i),
|
|
13685
|
+
kind: "added",
|
|
13686
|
+
after: a[i],
|
|
13687
|
+
...gap ? { gap } : {}
|
|
13688
|
+
});
|
|
13689
|
+
next = i + 1;
|
|
13690
|
+
}
|
|
13665
13691
|
for (let i = a.length; i < b.length; i++) changes.push({
|
|
13666
13692
|
key: String(i),
|
|
13667
13693
|
kind: "deleted"
|
|
13668
13694
|
});
|
|
13695
|
+
if (a.length > next) changes.push({
|
|
13696
|
+
key: "length",
|
|
13697
|
+
kind: "holes",
|
|
13698
|
+
after: a.length - next
|
|
13699
|
+
});
|
|
13669
13700
|
return changes;
|
|
13670
13701
|
}
|
|
13671
13702
|
for (const key of Object.keys(after)) if (hasOwn(before, key)) compare(key, before[key], after[key]);
|
|
@@ -13689,22 +13720,39 @@ function keyChanges(before, after, arrays) {
|
|
|
13689
13720
|
function diffPaths(before, after, arrays = false) {
|
|
13690
13721
|
const changes = [];
|
|
13691
13722
|
const ancestors = /* @__PURE__ */ new Set();
|
|
13723
|
+
const unchanged = /* @__PURE__ */ new WeakMap();
|
|
13724
|
+
let cuts = 0;
|
|
13692
13725
|
(function walk(b, a, path) {
|
|
13693
|
-
if (ancestors.has(a))
|
|
13726
|
+
if (ancestors.has(a)) {
|
|
13727
|
+
cuts++;
|
|
13728
|
+
return;
|
|
13729
|
+
}
|
|
13730
|
+
if (unchanged.get(a)?.has(b)) return;
|
|
13694
13731
|
ancestors.add(a);
|
|
13732
|
+
const found = changes.length;
|
|
13733
|
+
const cutsBefore = cuts;
|
|
13695
13734
|
for (const change of keyChanges(b, a, arrays)) {
|
|
13696
13735
|
const at = [...path, change.key];
|
|
13697
13736
|
if (change.kind === "nested") walk(change.before, change.after, at);
|
|
13698
13737
|
else changes.push(change.kind === "deleted" ? {
|
|
13699
13738
|
path: at,
|
|
13700
|
-
deleted: true
|
|
13739
|
+
deleted: true,
|
|
13740
|
+
...change.hole ? { hole: true } : {}
|
|
13701
13741
|
} : {
|
|
13702
13742
|
path: at,
|
|
13703
13743
|
deleted: false,
|
|
13704
13744
|
value: change.after,
|
|
13705
|
-
...change.kind === "added" && Array.isArray(a)
|
|
13745
|
+
...change.kind === "added" && Array.isArray(a) && Number(change.key) >= b.length ? {
|
|
13746
|
+
appended: true,
|
|
13747
|
+
...change.gap ? { gap: change.gap } : {}
|
|
13748
|
+
} : {}
|
|
13706
13749
|
});
|
|
13707
13750
|
}
|
|
13751
|
+
if (changes.length === found && cuts === cutsBefore) {
|
|
13752
|
+
const seen = unchanged.get(a) ?? /* @__PURE__ */ new Set();
|
|
13753
|
+
seen.add(b);
|
|
13754
|
+
unchanged.set(a, seen);
|
|
13755
|
+
}
|
|
13708
13756
|
ancestors.delete(a);
|
|
13709
13757
|
})(before, after, []);
|
|
13710
13758
|
return changes;
|
|
@@ -13715,8 +13763,62 @@ function underChange(path, keys) {
|
|
|
13715
13763
|
for (let n = 1; n <= path.length; n++) if (keys.has(pathKey(path.slice(0, n)))) return true;
|
|
13716
13764
|
return false;
|
|
13717
13765
|
}
|
|
13718
|
-
/**
|
|
13766
|
+
/**
|
|
13767
|
+
* The segment standing for the entry `index` of a Map or Set in a path, or
|
|
13768
|
+
* for the `cause` or `errors` of an Error ('c', 'e').
|
|
13769
|
+
*/
|
|
13719
13770
|
var entryKey = (kind, index) => `\0${kind}${index}`;
|
|
13771
|
+
var isEntryKey = (segment) => segment.startsWith("\0");
|
|
13772
|
+
/**
|
|
13773
|
+
* What a Map, Set or Error holds in place of properties (keys and values,
|
|
13774
|
+
* members, `cause` and `errors`), each with the segment standing for it in
|
|
13775
|
+
* a path (see entryKey); undefined for any other value.
|
|
13776
|
+
*/
|
|
13777
|
+
function entryChildren(value) {
|
|
13778
|
+
const children = [];
|
|
13779
|
+
if (value instanceof Map) {
|
|
13780
|
+
let index = 0;
|
|
13781
|
+
for (const [k, v] of value) {
|
|
13782
|
+
children.push([entryKey("k", index), k], [entryKey("v", index), v]);
|
|
13783
|
+
index++;
|
|
13784
|
+
}
|
|
13785
|
+
} else if (value instanceof Set) {
|
|
13786
|
+
let index = 0;
|
|
13787
|
+
for (const member of value) children.push([entryKey("s", index++), member]);
|
|
13788
|
+
} else if (value instanceof Error) {
|
|
13789
|
+
for (const key of ["cause", "errors"]) if (hasOwn(value, key)) children.push([entryKey(key === "cause" ? "c" : "e", 0), value[key]]);
|
|
13790
|
+
} else return;
|
|
13791
|
+
return children;
|
|
13792
|
+
}
|
|
13793
|
+
/**
|
|
13794
|
+
* The value at `path` below `root`, where a path may go through the entries
|
|
13795
|
+
* of Maps, Sets and Errors (see objectPaths) as well as properties.
|
|
13796
|
+
*/
|
|
13797
|
+
function getByEntryPath(root, path) {
|
|
13798
|
+
let current = root;
|
|
13799
|
+
let start = 0;
|
|
13800
|
+
for (let i = 0; i < path.length; i++) {
|
|
13801
|
+
if (!isEntryKey(path[i])) continue;
|
|
13802
|
+
current = getByPath(current, path.slice(start, i));
|
|
13803
|
+
if (!isObjectValue(current)) return void 0;
|
|
13804
|
+
current = entryChildren(current)?.find(([at]) => at === path[i])?.[1];
|
|
13805
|
+
start = i + 1;
|
|
13806
|
+
}
|
|
13807
|
+
return start === path.length ? current : getByPath(current, path.slice(start));
|
|
13808
|
+
}
|
|
13809
|
+
/**
|
|
13810
|
+
* Write `value` at the path of an object a Map holds as a value (see
|
|
13811
|
+
* objectPaths); other entries are not replaced, as a key or member cannot
|
|
13812
|
+
* change without the entry moving. Returns whether it wrote.
|
|
13813
|
+
*/
|
|
13814
|
+
function setEntryValue(root, path, value) {
|
|
13815
|
+
const holder = getByEntryPath(root, path.slice(0, -1));
|
|
13816
|
+
const match = /^\0v(\d+)$/.exec(path[path.length - 1]);
|
|
13817
|
+
if (!(holder instanceof Map) || !match) return false;
|
|
13818
|
+
const key = [...holder.keys()][Number(match[1])];
|
|
13819
|
+
holder.set(key, value);
|
|
13820
|
+
return true;
|
|
13821
|
+
}
|
|
13720
13822
|
/**
|
|
13721
13823
|
* Every path each object (or array, or other value) of `root` is at, the
|
|
13722
13824
|
* first appearance first, in depth-first order. Plain objects and arrays are
|
|
@@ -13727,7 +13829,8 @@ var entryKey = (kind, index) => `\0${kind}${index}`;
|
|
|
13727
13829
|
* With `entries`, the objects held in a Map or Set (as key, value or member)
|
|
13728
13830
|
* are placed too, after everything else: one met nowhere else is at the path
|
|
13729
13831
|
* of its Map or Set and the entry's segment (see entryKey), which is no path
|
|
13730
|
-
* a value can be read at, so only to tell where an
|
|
13832
|
+
* a value can be read at (see getByEntryPath), so only to tell where an
|
|
13833
|
+
* object is from; so are the `cause` and `errors` of an Error.
|
|
13731
13834
|
*/
|
|
13732
13835
|
function objectPaths(root, skip = /* @__PURE__ */ new Set(), entries = false) {
|
|
13733
13836
|
const all = /* @__PURE__ */ new Map();
|
|
@@ -13740,7 +13843,7 @@ function objectPaths(root, skip = /* @__PURE__ */ new Set(), entries = false) {
|
|
|
13740
13843
|
}
|
|
13741
13844
|
all.set(value, [at]);
|
|
13742
13845
|
if (isMergeable(value) || Array.isArray(value)) walk(value, at);
|
|
13743
|
-
else if (entries && (value
|
|
13846
|
+
else if (entries && entryChildren(value)) collections.push([value, at]);
|
|
13744
13847
|
}
|
|
13745
13848
|
function walk(node, path) {
|
|
13746
13849
|
for (const key of Object.keys(node)) {
|
|
@@ -13752,15 +13855,7 @@ function objectPaths(root, skip = /* @__PURE__ */ new Set(), entries = false) {
|
|
|
13752
13855
|
}
|
|
13753
13856
|
}
|
|
13754
13857
|
walk(root, []);
|
|
13755
|
-
for (const [collection, path] of collections)
|
|
13756
|
-
let index = 0;
|
|
13757
|
-
for (const entry of collection.entries()) {
|
|
13758
|
-
const [k, v] = entry;
|
|
13759
|
-
const parts = collection instanceof Set ? [[k, entryKey("s", index)]] : [[k, entryKey("k", index)], [v, entryKey("v", index)]];
|
|
13760
|
-
for (const [value, segment] of parts) if (isObjectValue(value) && !all.has(value)) place(value, [...path, segment]);
|
|
13761
|
-
index++;
|
|
13762
|
-
}
|
|
13763
|
-
}
|
|
13858
|
+
for (const [collection, path] of collections) for (const [segment, value] of entryChildren(collection)) if (isObjectValue(value)) place(value, [...path, segment]);
|
|
13764
13859
|
return all;
|
|
13765
13860
|
}
|
|
13766
13861
|
/** Where each object of `root` first appears (see objectPaths). */
|
|
@@ -13773,27 +13868,44 @@ function firstPaths(root, skip, entries) {
|
|
|
13773
13868
|
* parents before children.
|
|
13774
13869
|
*/
|
|
13775
13870
|
function sharedPaths(root) {
|
|
13776
|
-
return [...objectPaths(root).values()].filter((paths) => paths.length > 1);
|
|
13777
|
-
}
|
|
13778
|
-
/**
|
|
13779
|
-
* Whether the
|
|
13780
|
-
* object than `x` did
|
|
13781
|
-
* `$map.set("k", $a)` over an equal object
|
|
13782
|
-
*
|
|
13783
|
-
|
|
13784
|
-
|
|
13785
|
-
|
|
13786
|
-
|
|
13787
|
-
|
|
13788
|
-
|
|
13789
|
-
const
|
|
13790
|
-
|
|
13791
|
-
|
|
13792
|
-
|
|
13793
|
-
|
|
13794
|
-
|
|
13871
|
+
return [...objectPaths(root, void 0, true).values()].filter((paths) => paths.length > 1);
|
|
13872
|
+
}
|
|
13873
|
+
/**
|
|
13874
|
+
* Whether `y`, which takes the place of `x` at `at` (the first appearance of
|
|
13875
|
+
* both), holds an object elsewhere than `x` did, below an entry of a Map, Set
|
|
13876
|
+
* or Error (see aliasChanges): `$map.set("k", $a)` over an equal object, or
|
|
13877
|
+
* `$map.get("k").child = $a`. Entries are compared by position, and only
|
|
13878
|
+
* where both hold as many; other differences are changes of content. Below
|
|
13879
|
+
* an entry, objects that merge are compared key by key.
|
|
13880
|
+
*/
|
|
13881
|
+
function aliasMoved(x, y, at, earlier, later) {
|
|
13882
|
+
const pairs = [];
|
|
13883
|
+
const xs = entryChildren(x);
|
|
13884
|
+
const ys = entryChildren(y);
|
|
13885
|
+
if (xs && ys) {
|
|
13886
|
+
if (Object.getPrototypeOf(x) !== Object.getPrototypeOf(y)) return false;
|
|
13887
|
+
if (x instanceof Map || x instanceof Set) {
|
|
13888
|
+
if (x.size !== y.size) return false;
|
|
13889
|
+
}
|
|
13890
|
+
const held = new Map(ys);
|
|
13891
|
+
for (const [segment, a] of xs) if (held.has(segment)) pairs.push([
|
|
13892
|
+
segment,
|
|
13893
|
+
a,
|
|
13894
|
+
held.get(segment)
|
|
13895
|
+
]);
|
|
13896
|
+
} else if ((isMergeable(x) || Array.isArray(x)) && mergesWith(x, y, true)) {
|
|
13897
|
+
for (const key of Object.keys(y)) if (hasOwn(x, key)) pairs.push([
|
|
13898
|
+
key,
|
|
13899
|
+
x[key],
|
|
13900
|
+
y[key]
|
|
13901
|
+
]);
|
|
13795
13902
|
}
|
|
13796
|
-
return
|
|
13903
|
+
return pairs.some(([segment, a, b]) => {
|
|
13904
|
+
if (!isObjectValue(a) || !isObjectValue(b)) return false;
|
|
13905
|
+
const here = [...at, segment];
|
|
13906
|
+
const isAt = later.get(b);
|
|
13907
|
+
return pathKey(earlier.get(a)) !== pathKey(isAt) || pathKey(isAt) === pathKey(here) && aliasMoved(a, b, here, earlier, later);
|
|
13908
|
+
});
|
|
13797
13909
|
}
|
|
13798
13910
|
/**
|
|
13799
13911
|
* The paths whose object is another one in the references of `after` than in
|
|
@@ -13823,7 +13935,7 @@ function aliasChanges(before, after) {
|
|
|
13823
13935
|
value: y,
|
|
13824
13936
|
alias: true
|
|
13825
13937
|
});
|
|
13826
|
-
else if (pathKey(isAt) === k &&
|
|
13938
|
+
else if (pathKey(isAt) === k && entryChildren(x) && aliasMoved(x, y, at, earlier, later)) changes.push({
|
|
13827
13939
|
path: at,
|
|
13828
13940
|
deleted: false,
|
|
13829
13941
|
value: y,
|
|
@@ -13857,7 +13969,7 @@ function changesBetween(before, after, arrays = false) {
|
|
|
13857
13969
|
* `changes` (which write something new there), for `existingObjects`.
|
|
13858
13970
|
*/
|
|
13859
13971
|
function locateObjects(source, changes) {
|
|
13860
|
-
return firstPaths(source, new Set(changes.map((c) => pathKey(c.path))));
|
|
13972
|
+
return firstPaths(source, new Set(changes.map((c) => pathKey(c.path))), true);
|
|
13861
13973
|
}
|
|
13862
13974
|
/**
|
|
13863
13975
|
* A map for `deepClone`'s `seen`: an object of the source `paths` locate
|
|
@@ -13872,7 +13984,7 @@ function existingObjects(paths, target) {
|
|
|
13872
13984
|
if (super.has(obj)) return true;
|
|
13873
13985
|
const path = paths.get(obj);
|
|
13874
13986
|
if (!path) return false;
|
|
13875
|
-
const found =
|
|
13987
|
+
const found = getByEntryPath(target, path);
|
|
13876
13988
|
if (!(isObjectValue(found) && (Array.isArray(found) ? Array.isArray(obj) : Object.getPrototypeOf(found) === Object.getPrototypeOf(obj)))) return false;
|
|
13877
13989
|
super.set(obj, found);
|
|
13878
13990
|
return true;
|
|
@@ -13887,32 +13999,20 @@ function isApplied(target, change) {
|
|
|
13887
13999
|
if (!hasOwn(parent, key)) return change.deleted;
|
|
13888
14000
|
return !change.deleted && deepEqual(parent[key], change.value);
|
|
13889
14001
|
}
|
|
14002
|
+
/**
|
|
14003
|
+
* Make `path` below `root` hold `object` (an object that `sharedPaths` found
|
|
14004
|
+
* at several paths): a property is written like setByPath() does, a Map's
|
|
14005
|
+
* value replaced; an entry that is a key or member is left as it is.
|
|
14006
|
+
*/
|
|
14007
|
+
function relinkPath(root, path, object) {
|
|
14008
|
+
if (path.some(isEntryKey)) setEntryValue(root, path, object);
|
|
14009
|
+
else setByPath(root, path, object);
|
|
14010
|
+
}
|
|
13890
14011
|
function applyChange(target, change) {
|
|
13891
14012
|
const root = target;
|
|
13892
14013
|
if (change.deleted) deleteByPath(root, change.path);
|
|
13893
14014
|
else setByPath(root, change.path, change.value);
|
|
13894
14015
|
}
|
|
13895
|
-
/**
|
|
13896
|
-
* Write how `after` differs from `before` (see keyChanges; arrays merge by
|
|
13897
|
-
* index) into `target`, another version of the same object or array.
|
|
13898
|
-
* Values that changed are written whole, as deep copies; for values that
|
|
13899
|
-
* merge, `mergeNested` may merge them into `target` instead, returning
|
|
13900
|
-
* whether it did. Into an array, elements removed from the end are removed
|
|
13901
|
-
* at the same indices and elements added are appended, and changes at
|
|
13902
|
-
* indices `target` lacks are dropped: where `target` was resized, its
|
|
13903
|
-
* indices do not line up with `before`'s.
|
|
13904
|
-
*/
|
|
13905
|
-
function mergeKeys(target, before, after, mergeNested) {
|
|
13906
|
-
const list = Array.isArray(target) ? target : void 0;
|
|
13907
|
-
for (const change of keyChanges(before, after, true)) {
|
|
13908
|
-
const { key } = change;
|
|
13909
|
-
if (change.kind === "deleted") if (list) list.length = Math.min(list.length, Number(key));
|
|
13910
|
-
else delete target[key];
|
|
13911
|
-
else if (list && change.kind === "added") list.push(deepClone(change.after));
|
|
13912
|
-
else if (list && Number(key) >= list.length) continue;
|
|
13913
|
-
else if (change.kind !== "nested" || !(mergeNested?.(key, change.before, change.after) || deepEqual(change.before, change.after))) target[key] = deepClone(change.after);
|
|
13914
|
-
}
|
|
13915
|
-
}
|
|
13916
14016
|
//#endregion
|
|
13917
14017
|
//#region src/event-emitter.ts
|
|
13918
14018
|
var VALID_EVENTS = new Set([
|
|
@@ -14672,18 +14772,18 @@ var cloneValue = (value) => deepClone(value, { keepUnregistered: true });
|
|
|
14672
14772
|
*/
|
|
14673
14773
|
function clonerFor(target, paths) {
|
|
14674
14774
|
const seen = paths ? existingObjects(paths, target) : /* @__PURE__ */ new Map();
|
|
14675
|
-
const
|
|
14775
|
+
const settle = (copy, original) => {
|
|
14676
14776
|
const path = paths?.get(original);
|
|
14677
14777
|
if (!path || !isDraft(copy)) return copy;
|
|
14678
14778
|
const plain = current(copy);
|
|
14679
|
-
setByPath(target, path, plain);
|
|
14779
|
+
if (!path.some(isEntryKey)) setByPath(target, path, plain);
|
|
14680
14780
|
seen.set(original, plain);
|
|
14681
14781
|
return plain;
|
|
14682
14782
|
};
|
|
14683
14783
|
return (value) => deepClone(value, {
|
|
14684
14784
|
keepUnregistered: true,
|
|
14685
14785
|
seen,
|
|
14686
|
-
|
|
14786
|
+
settle
|
|
14687
14787
|
});
|
|
14688
14788
|
}
|
|
14689
14789
|
/** One cloner per target copy, made when first used. */
|
|
@@ -14870,9 +14970,9 @@ function relink(draft, work, changes) {
|
|
|
14870
14970
|
const prefixes = [first, ...others].map((p) => pathKey(p).slice(0, -1));
|
|
14871
14971
|
if (!written.some((w) => prefixes.some((p) => w.startsWith(p)))) continue;
|
|
14872
14972
|
try {
|
|
14873
|
-
const object =
|
|
14973
|
+
const object = getByEntryPath(draft, first);
|
|
14874
14974
|
if (object === null || typeof object !== "object") continue;
|
|
14875
|
-
for (const path of others)
|
|
14975
|
+
for (const path of others) relinkPath(draft, path, object);
|
|
14876
14976
|
} catch {}
|
|
14877
14977
|
}
|
|
14878
14978
|
}
|
|
@@ -14969,12 +15069,26 @@ var running = () => activeScopes.map((scope) => ({
|
|
|
14969
15069
|
/**
|
|
14970
15070
|
* Bring a suspended mutation's copies up to the store after an action
|
|
14971
15071
|
* replaced or changed state under it (navigation clears temporaries, back
|
|
14972
|
-
* and restart replace variables).
|
|
14973
|
-
* the code
|
|
15072
|
+
* and restart replace variables). What the action changed (see
|
|
15073
|
+
* changesBetween) is written into the code's working copy, which keeps the
|
|
15074
|
+
* objects the code may hold where nothing changed, and the references among
|
|
15075
|
+
* the values written, and to the objects it holds already, as a commit does.
|
|
14974
15076
|
*/
|
|
14975
15077
|
function resync(scope) {
|
|
14976
15078
|
const state = useStoryStore.getState();
|
|
14977
|
-
|
|
15079
|
+
const now = namespacesOf(state);
|
|
15080
|
+
const changes = changesBetween(scope.base, now);
|
|
15081
|
+
const copy = clonerFor(scope.work, locateObjects(now, changes));
|
|
15082
|
+
for (const change of changes) try {
|
|
15083
|
+
applyChange(scope.work, change.deleted ? change : {
|
|
15084
|
+
...change,
|
|
15085
|
+
value: copy(change.value)
|
|
15086
|
+
});
|
|
15087
|
+
} catch {
|
|
15088
|
+
const [ns, root] = change.path;
|
|
15089
|
+
if (hasOwn(now[ns], root)) scope.work[ns][root] = copy(now[ns][root]);
|
|
15090
|
+
else delete scope.work[ns][root];
|
|
15091
|
+
}
|
|
14978
15092
|
scope.base = cloneNamespaces(state);
|
|
14979
15093
|
}
|
|
14980
15094
|
/**
|
|
@@ -15038,7 +15152,11 @@ function executeMutation(code, mergedLocals, scopeUpdate) {
|
|
|
15038
15152
|
scope,
|
|
15039
15153
|
keepRunning: false
|
|
15040
15154
|
}]);
|
|
15041
|
-
|
|
15155
|
+
const changed = new Set(changesBetween(mergedLocals, localsClone, true).map((c) => c.path[0]));
|
|
15156
|
+
for (const group of sharedPaths(localsClone)) if (group.some((path) => changed.has(path[0]))) {
|
|
15157
|
+
for (const path of group) if (localsClone[path[0]] !== mergedLocals[path[0]]) changed.add(path[0]);
|
|
15158
|
+
}
|
|
15159
|
+
for (const key of Object.keys(localsClone)) if (changed.has(key)) scopeUpdate(key, localsClone[key]);
|
|
15042
15160
|
for (const key of Object.keys(mergedLocals)) if (!hasOwn(localsClone, key)) scopeUpdate(key, void 0);
|
|
15043
15161
|
}
|
|
15044
15162
|
//#endregion
|
|
@@ -15799,20 +15917,25 @@ function writeHookChange(work, after, change, own) {
|
|
|
15799
15917
|
const child = hasOwn(holder, key) ? holder[key] : void 0;
|
|
15800
15918
|
const held = getByPath(after, path.slice(0, depth + 1));
|
|
15801
15919
|
if (!mergesWith(child, held, true)) {
|
|
15802
|
-
writeKey(holder, key,
|
|
15920
|
+
writeKey(holder, key, own(held));
|
|
15803
15921
|
return;
|
|
15804
15922
|
}
|
|
15805
15923
|
holder = child;
|
|
15806
15924
|
}
|
|
15807
15925
|
const key = path[depth];
|
|
15808
|
-
if (change.deleted) writeKey(holder, key,
|
|
15809
|
-
else writeKey(holder, key,
|
|
15926
|
+
if (change.deleted) writeKey(holder, key, void 0, change);
|
|
15927
|
+
else writeKey(holder, key, own(change.value), change);
|
|
15810
15928
|
}
|
|
15811
|
-
function writeKey(holder, key,
|
|
15929
|
+
function writeKey(holder, key, value, change) {
|
|
15930
|
+
const deleted = change?.deleted ?? false;
|
|
15812
15931
|
if (Array.isArray(holder)) {
|
|
15813
15932
|
const index = Number(key);
|
|
15814
|
-
if (deleted)
|
|
15815
|
-
else
|
|
15933
|
+
if (deleted) if (change && "hole" in change) delete holder[index];
|
|
15934
|
+
else holder.length = Math.min(holder.length, index);
|
|
15935
|
+
else if (change && "appended" in change) {
|
|
15936
|
+
holder.length += change.gap ?? 0;
|
|
15937
|
+
holder.push(value);
|
|
15938
|
+
} else if (key === "length" && change) holder.length += value;
|
|
15816
15939
|
else if (index < holder.length) holder[index] = value;
|
|
15817
15940
|
} else if (deleted) delete holder[key];
|
|
15818
15941
|
else setOwn(holder, key, value);
|
|
@@ -19995,7 +20118,8 @@ defineMacro({
|
|
|
19995
20118
|
return useRunOnce("unset", rawArgs, () => {
|
|
19996
20119
|
const state = useStoryStore.getState();
|
|
19997
20120
|
const name = ctx.args.variable ?? "";
|
|
19998
|
-
if (name
|
|
20121
|
+
if (/^[$_%@][^.]*\./.test(name)) ctx.mutate(`delete ${name}`);
|
|
20122
|
+
else if (name.startsWith("$")) state.deleteVariable(name.slice(1));
|
|
19999
20123
|
else if (name.startsWith("_")) state.deleteTemporary(name.slice(1));
|
|
20000
20124
|
else if (name.startsWith("%")) state.deleteTransient(name.slice(1));
|
|
20001
20125
|
else if (name.startsWith("@")) ctx.update(name.slice(1), void 0);
|
package/package.json
CHANGED
|
@@ -10,7 +10,11 @@ defineMacro({
|
|
|
10
10
|
const state = useStoryStore.getState();
|
|
11
11
|
const name = ctx.args.variable ?? '';
|
|
12
12
|
|
|
13
|
-
if (name
|
|
13
|
+
if (/^[$_%@][^.]*\./.test(name)) {
|
|
14
|
+
// A field of a variable: delete it as mutation code does, which
|
|
15
|
+
// resolves the path in every namespace
|
|
16
|
+
ctx.mutate(`delete ${name}`);
|
|
17
|
+
} else if (name.startsWith('$')) {
|
|
14
18
|
state.deleteVariable(name.slice(1));
|
|
15
19
|
} else if (name.startsWith('_')) {
|
|
16
20
|
state.deleteTemporary(name.slice(1));
|
package/src/execute-mutation.ts
CHANGED
|
@@ -13,12 +13,13 @@ import {
|
|
|
13
13
|
changesBetween,
|
|
14
14
|
applyChange,
|
|
15
15
|
deepClone,
|
|
16
|
-
deepEqual,
|
|
17
16
|
existingObjects,
|
|
17
|
+
getByEntryPath,
|
|
18
|
+
isEntryKey,
|
|
18
19
|
isApplied,
|
|
19
20
|
isMergeable,
|
|
20
21
|
locateObjects,
|
|
21
|
-
|
|
22
|
+
relinkPath,
|
|
22
23
|
pathKey,
|
|
23
24
|
sharedPaths,
|
|
24
25
|
type PathChange,
|
|
@@ -94,18 +95,23 @@ function clonerFor(
|
|
|
94
95
|
paths?: ReadonlyMap<object, string[]>,
|
|
95
96
|
): Cloner {
|
|
96
97
|
const seen = paths ? existingObjects(paths, target) : new Map();
|
|
97
|
-
// Immer does not finalize a draft held as a Map key, so an
|
|
98
|
-
// met there is put in the draft as the object itself: its
|
|
99
|
-
// code did not change it, which other references to it then
|
|
100
|
-
|
|
98
|
+
// Immer does not finalize a draft held as a Map key or Set member, so an
|
|
99
|
+
// existing object met there is put in the draft as the object itself: its
|
|
100
|
+
// base while the code did not change it, which other references to it then
|
|
101
|
+
// agree with.
|
|
102
|
+
const settle = (copy: object, original: object): object => {
|
|
101
103
|
const path = paths?.get(original);
|
|
102
104
|
if (!path || !isDraft(copy)) return copy;
|
|
103
105
|
const plain = currentDraft(copy) as object;
|
|
104
|
-
|
|
106
|
+
// Where the object is in an entry of a Map or Set the path cannot be
|
|
107
|
+
// written (see getByEntryPath): that entry keeps its own
|
|
108
|
+
if (!path.some(isEntryKey)) {
|
|
109
|
+
setByPath(target as Record<string, unknown>, path, plain);
|
|
110
|
+
}
|
|
105
111
|
seen.set(original, plain);
|
|
106
112
|
return plain;
|
|
107
113
|
};
|
|
108
|
-
return (value) => deepClone(value, { keepUnregistered: true, seen,
|
|
114
|
+
return (value) => deepClone(value, { keepUnregistered: true, seen, settle });
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
/** One cloner per target copy, made when first used. */
|
|
@@ -358,10 +364,10 @@ function relink(
|
|
|
358
364
|
const prefixes = [first!, ...others].map((p) => pathKey(p).slice(0, -1));
|
|
359
365
|
if (!written.some((w) => prefixes.some((p) => w.startsWith(p)))) continue;
|
|
360
366
|
try {
|
|
361
|
-
const object =
|
|
367
|
+
const object = getByEntryPath(draft, first!);
|
|
362
368
|
if (object === null || typeof object !== 'object') continue;
|
|
363
369
|
for (const path of others)
|
|
364
|
-
|
|
370
|
+
relinkPath(draft as unknown as Record<string, unknown>, path, object);
|
|
365
371
|
} catch {
|
|
366
372
|
// A path the store does not hold: the code's view is not the store's
|
|
367
373
|
}
|
|
@@ -506,14 +512,31 @@ const running = (): Commit[] =>
|
|
|
506
512
|
/**
|
|
507
513
|
* Bring a suspended mutation's copies up to the store after an action
|
|
508
514
|
* replaced or changed state under it (navigation clears temporaries, back
|
|
509
|
-
* and restart replace variables).
|
|
510
|
-
* the code
|
|
515
|
+
* and restart replace variables). What the action changed (see
|
|
516
|
+
* changesBetween) is written into the code's working copy, which keeps the
|
|
517
|
+
* objects the code may hold where nothing changed, and the references among
|
|
518
|
+
* the values written, and to the objects it holds already, as a commit does.
|
|
511
519
|
*/
|
|
512
520
|
function resync(scope: MutationScope): void {
|
|
513
521
|
const state = useStoryStore.getState();
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
522
|
+
const now = namespacesOf(state);
|
|
523
|
+
const changes = changesBetween(
|
|
524
|
+
scope.base as unknown as Record<string, unknown>,
|
|
525
|
+
now as unknown as Record<string, unknown>,
|
|
526
|
+
);
|
|
527
|
+
const copy = clonerFor(scope.work, locateObjects(now, changes));
|
|
528
|
+
for (const change of changes) {
|
|
529
|
+
try {
|
|
530
|
+
applyChange(
|
|
531
|
+
scope.work,
|
|
532
|
+
change.deleted ? change : { ...change, value: copy(change.value) },
|
|
533
|
+
);
|
|
534
|
+
} catch {
|
|
535
|
+
// A path the working copy does not hold: replace its root instead
|
|
536
|
+
const [ns, root] = change.path as [NamespaceName, string];
|
|
537
|
+
if (hasOwn(now[ns], root)) scope.work[ns][root] = copy(now[ns][root]);
|
|
538
|
+
else delete scope.work[ns][root];
|
|
539
|
+
}
|
|
517
540
|
}
|
|
518
541
|
scope.base = cloneNamespaces(state);
|
|
519
542
|
}
|
|
@@ -611,11 +634,26 @@ export function executeMutation(
|
|
|
611
634
|
// came before its own
|
|
612
635
|
commitScopes([...running(), { scope, keepRunning: false }]);
|
|
613
636
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
637
|
+
// Locals the code changed, as content or as the objects they refer to
|
|
638
|
+
// (`@a = @b` over an equal object), and the locals that share an object
|
|
639
|
+
// with one of those: the updater takes each one's value, so they must all
|
|
640
|
+
// be written for the scope to hold one object again
|
|
641
|
+
const changed = new Set(
|
|
642
|
+
changesBetween(mergedLocals, localsClone, true).map((c) => c.path[0]!),
|
|
643
|
+
);
|
|
644
|
+
for (const group of sharedPaths(localsClone)) {
|
|
645
|
+
if (group.some((path) => changed.has(path[0]!))) {
|
|
646
|
+
// Not a value kept by reference (an unregistered class instance)
|
|
647
|
+
for (const path of group) {
|
|
648
|
+
if (localsClone[path[0]!] !== mergedLocals[path[0]!]) {
|
|
649
|
+
changed.add(path[0]!);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
617
652
|
}
|
|
618
653
|
}
|
|
654
|
+
for (const key of Object.keys(localsClone)) {
|
|
655
|
+
if (changed.has(key)) scopeUpdate(key, localsClone[key]);
|
|
656
|
+
}
|
|
619
657
|
|
|
620
658
|
// Detect deleted locals
|
|
621
659
|
for (const key of Object.keys(mergedLocals)) {
|