@wcstack/state 2.6.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +11 -0
- package/README.md +11 -0
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.esm.js +149 -14
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -7335,11 +7335,36 @@ const indexKeyedByListIndex = new WeakMap();
|
|
|
7335
7335
|
let anyRegistered = false;
|
|
7336
7336
|
const watchersByElement = new WeakMap();
|
|
7337
7337
|
const watchersByIndexes = new WeakMap();
|
|
7338
|
+
// 祖先パス → その配下の鍵付きパス(祖先への書き込みを配下の鍵付き購読へ届ける)
|
|
7339
|
+
const descendantsByElement = new WeakMap();
|
|
7338
7340
|
const EMPTY$1 = [];
|
|
7341
|
+
/** `path` の祖先を逆引きに載せる(同じ path は 1 回だけ)。祖先の無いトップレベルの path は載せない */
|
|
7342
|
+
function recordAncestors(stateElement, path) {
|
|
7343
|
+
const ledger = ledgerOf(stateElement);
|
|
7344
|
+
if (ledger.recorded.has(path)) {
|
|
7345
|
+
return;
|
|
7346
|
+
}
|
|
7347
|
+
ledger.recorded.add(path);
|
|
7348
|
+
const cumulativePaths = getPathInfo(path).cumulativePaths;
|
|
7349
|
+
if (cumulativePaths.length < 2) {
|
|
7350
|
+
return;
|
|
7351
|
+
}
|
|
7352
|
+
let byAncestor = descendantsByElement.get(stateElement);
|
|
7353
|
+
if (typeof byAncestor === "undefined") {
|
|
7354
|
+
descendantsByElement.set(stateElement, byAncestor = new Map());
|
|
7355
|
+
}
|
|
7356
|
+
for (let i = 0; i < cumulativePaths.length - 1; i++) {
|
|
7357
|
+
let descendants = byAncestor.get(cumulativePaths[i]);
|
|
7358
|
+
if (typeof descendants === "undefined") {
|
|
7359
|
+
byAncestor.set(cumulativePaths[i], descendants = new Set());
|
|
7360
|
+
}
|
|
7361
|
+
descendants.add(path);
|
|
7362
|
+
}
|
|
7363
|
+
}
|
|
7339
7364
|
function ledgerOf(stateElement) {
|
|
7340
7365
|
let ledger = ledgerByElement.get(stateElement);
|
|
7341
7366
|
if (typeof ledger === "undefined") {
|
|
7342
|
-
ledgerByElement.set(stateElement, ledger = { byPath: new Map(), lastValue: new Map() });
|
|
7367
|
+
ledgerByElement.set(stateElement, ledger = { byPath: new Map(), lastValue: new Map(), recorded: new Set() });
|
|
7343
7368
|
}
|
|
7344
7369
|
return ledger;
|
|
7345
7370
|
}
|
|
@@ -7357,6 +7382,14 @@ function addToKey(keyMap, key, absAddress) {
|
|
|
7357
7382
|
}
|
|
7358
7383
|
set.add(absAddress);
|
|
7359
7384
|
}
|
|
7385
|
+
/** 鍵から行を外し、空になった鍵は Map から消す(オブジェクトの鍵を行の退役後まで握らない) */
|
|
7386
|
+
function removeFromKey(keyMap, key, absAddress) {
|
|
7387
|
+
const set = keyMap.get(key);
|
|
7388
|
+
set.delete(absAddress);
|
|
7389
|
+
if (set.size === 0) {
|
|
7390
|
+
keyMap.delete(key);
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7360
7393
|
/** 台帳へ登録する。既に同じ (アドレス, path) が登録済みなら鍵を移すだけで、false を返す */
|
|
7361
7394
|
function subscribe(ledger, path, key, absAddress) {
|
|
7362
7395
|
const keyMap = keyMapOf(ledger, path);
|
|
@@ -7367,7 +7400,7 @@ function subscribe(ledger, path, key, absAddress) {
|
|
|
7367
7400
|
if (previous.has(path)) {
|
|
7368
7401
|
const oldKey = previous.get(path);
|
|
7369
7402
|
if (!Object.is(oldKey, key)) {
|
|
7370
|
-
keyMap
|
|
7403
|
+
removeFromKey(keyMap, oldKey, absAddress);
|
|
7371
7404
|
previous.set(path, key);
|
|
7372
7405
|
addToKey(keyMap, key, absAddress);
|
|
7373
7406
|
}
|
|
@@ -7403,6 +7436,7 @@ function record(ledger, path, current, absAddress, isNew, entry) {
|
|
|
7403
7436
|
function registerKeyedDependency(stateElement, path, key, absAddress, current) {
|
|
7404
7437
|
anyRegistered = true;
|
|
7405
7438
|
const ledger = ledgerOf(stateElement);
|
|
7439
|
+
recordAncestors(stateElement, path);
|
|
7406
7440
|
const isNew = subscribe(ledger, path, key, absAddress);
|
|
7407
7441
|
record(ledger, path, current, absAddress, isNew, { stateElement, path, absAddress, levelListIndex: null });
|
|
7408
7442
|
}
|
|
@@ -7410,6 +7444,7 @@ function registerKeyedDependency(stateElement, path, key, absAddress, current) {
|
|
|
7410
7444
|
function registerIndexKeyedDependency(stateElement, path, levelListIndex, absAddress, current) {
|
|
7411
7445
|
anyRegistered = true;
|
|
7412
7446
|
const ledger = ledgerOf(stateElement);
|
|
7447
|
+
recordAncestors(stateElement, path);
|
|
7413
7448
|
const isNew = subscribe(ledger, path, levelListIndex.index, absAddress);
|
|
7414
7449
|
record(ledger, path, current, absAddress, isNew, { stateElement, path, absAddress, levelListIndex });
|
|
7415
7450
|
}
|
|
@@ -7421,6 +7456,7 @@ function hasKeyedDependents(stateElement, path) {
|
|
|
7421
7456
|
function registerIndexWatcher(stateElement, path, getterPathInfo, indexes, current) {
|
|
7422
7457
|
anyRegistered = true;
|
|
7423
7458
|
ledgerOf(stateElement).lastValue.set(path, current);
|
|
7459
|
+
recordAncestors(stateElement, path);
|
|
7424
7460
|
let byPath = watchersByElement.get(stateElement);
|
|
7425
7461
|
if (typeof byPath === "undefined") {
|
|
7426
7462
|
watchersByElement.set(stateElement, byPath = new Map());
|
|
@@ -7505,6 +7541,7 @@ function collect(set, out) {
|
|
|
7505
7541
|
}
|
|
7506
7542
|
/**
|
|
7507
7543
|
* `path` への書き込み: 旧値(同値ガードが読めたとき)と新値の鍵に登録された行アドレス。
|
|
7544
|
+
* 控えていた最後の値が旧値と違う(旧値を読めなかった)ときは、その鍵の行も加える。
|
|
7508
7545
|
* 新値を `path` の最後の値として控える。呼び出し側は hasKeyedDependents で門を通す。
|
|
7509
7546
|
*/
|
|
7510
7547
|
function keyedDependents(stateElement, path, hasOldKey, oldKey, newKey) {
|
|
@@ -7514,13 +7551,20 @@ function keyedDependents(stateElement, path, hasOldKey, oldKey, newKey) {
|
|
|
7514
7551
|
if (typeof ledger === "undefined" || (typeof keyMap === "undefined" && typeof watchers === "undefined")) {
|
|
7515
7552
|
return EMPTY$1;
|
|
7516
7553
|
}
|
|
7554
|
+
const hasLast = ledger.lastValue.has(path);
|
|
7555
|
+
const last = ledger.lastValue.get(path);
|
|
7517
7556
|
ledger.lastValue.set(path, newKey);
|
|
7518
7557
|
const out = [];
|
|
7519
7558
|
const newDiffers = !hasOldKey || !Object.is(oldKey, newKey);
|
|
7559
|
+
// 行が最後に見た値。旧値を読めなかったときに真のまま残る行はここにいる
|
|
7560
|
+
const lastDiffers = hasLast && (!hasOldKey || !Object.is(last, oldKey)) && !Object.is(last, newKey);
|
|
7520
7561
|
if (typeof keyMap !== "undefined") {
|
|
7521
7562
|
if (hasOldKey) {
|
|
7522
7563
|
collect(keyMap.get(oldKey), out);
|
|
7523
7564
|
}
|
|
7565
|
+
if (lastDiffers) {
|
|
7566
|
+
collect(keyMap.get(last), out);
|
|
7567
|
+
}
|
|
7524
7568
|
if (newDiffers) {
|
|
7525
7569
|
collect(keyMap.get(newKey), out);
|
|
7526
7570
|
}
|
|
@@ -7530,6 +7574,9 @@ function keyedDependents(stateElement, path, hasOldKey, oldKey, newKey) {
|
|
|
7530
7574
|
if (hasOldKey) {
|
|
7531
7575
|
watchedRowsAt(watcher, oldKey, out);
|
|
7532
7576
|
}
|
|
7577
|
+
if (lastDiffers) {
|
|
7578
|
+
watchedRowsAt(watcher, last, out);
|
|
7579
|
+
}
|
|
7533
7580
|
if (newDiffers) {
|
|
7534
7581
|
watchedRowsAt(watcher, newKey, out);
|
|
7535
7582
|
}
|
|
@@ -7537,6 +7584,57 @@ function keyedDependents(stateElement, path, hasOldKey, oldKey, newKey) {
|
|
|
7537
7584
|
}
|
|
7538
7585
|
return out;
|
|
7539
7586
|
}
|
|
7587
|
+
function hasKeyedDescendants(stateElement, path) {
|
|
7588
|
+
return descendantsByElement.get(stateElement)?.has(path) === true;
|
|
7589
|
+
}
|
|
7590
|
+
/** `value` から `segments` を辿った値(途中がオブジェクトでなければ undefined) */
|
|
7591
|
+
function valueAt(value, segments) {
|
|
7592
|
+
let current = value;
|
|
7593
|
+
for (const segment of segments) {
|
|
7594
|
+
if (typeof current !== "object" || current === null) {
|
|
7595
|
+
return undefined;
|
|
7596
|
+
}
|
|
7597
|
+
current = current[segment];
|
|
7598
|
+
}
|
|
7599
|
+
return current;
|
|
7600
|
+
}
|
|
7601
|
+
/**
|
|
7602
|
+
* `path`(鍵付きパスの祖先)への書き込み: 配下の鍵付きパスごとに、旧い親と新しい親から鍵を辿り、
|
|
7603
|
+
* その鍵の行を返す(`keyedDependents` と同じ扱い)。残りのパスにワイルドカードがあると行ごとの鍵が
|
|
7604
|
+
* 辿れないので、そのパスに購読している行をすべて返す。呼び出し側は hasKeyedDescendants で門を通す。
|
|
7605
|
+
*/
|
|
7606
|
+
function keyedDescendantDependents(stateElement, path, oldValue, newValue) {
|
|
7607
|
+
const depth = getPathInfo(path).segments.length;
|
|
7608
|
+
const out = [];
|
|
7609
|
+
for (const descendant of descendantsByElement.get(stateElement).get(path)) {
|
|
7610
|
+
const rest = getPathInfo(descendant).segments.slice(depth);
|
|
7611
|
+
if (rest.includes(WILDCARD)) {
|
|
7612
|
+
allDependents(stateElement, descendant, out);
|
|
7613
|
+
continue;
|
|
7614
|
+
}
|
|
7615
|
+
for (const address of keyedDependents(stateElement, descendant, true, valueAt(oldValue, rest), valueAt(newValue, rest))) {
|
|
7616
|
+
out.push(address);
|
|
7617
|
+
}
|
|
7618
|
+
}
|
|
7619
|
+
return out;
|
|
7620
|
+
}
|
|
7621
|
+
/** `path` に鍵付きで購読しているすべての行 */
|
|
7622
|
+
function allDependents(stateElement, path, out) {
|
|
7623
|
+
const keyMap = ledgerByElement.get(stateElement).byPath.get(path);
|
|
7624
|
+
if (typeof keyMap !== "undefined") {
|
|
7625
|
+
for (const set of keyMap.values()) {
|
|
7626
|
+
collect(set, out);
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7629
|
+
const watchers = watchersByElement.get(stateElement)?.get(path);
|
|
7630
|
+
if (typeof watchers !== "undefined") {
|
|
7631
|
+
for (const watcher of watchers) {
|
|
7632
|
+
for (let index = 0; index < watcher.indexes.length; index++) {
|
|
7633
|
+
watchedRowsAt(watcher, index, out);
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7540
7638
|
function removeIndexEntry(entry) {
|
|
7541
7639
|
const entries = indexKeyedByListIndex.get(entry.levelListIndex);
|
|
7542
7640
|
const at = entries.indexOf(entry);
|
|
@@ -7557,7 +7655,7 @@ function dropKeyedSubscriptionsByListIndex(listIndex) {
|
|
|
7557
7655
|
const keys = keyByPathByAddress.get(absAddress);
|
|
7558
7656
|
const key = keys.get(path);
|
|
7559
7657
|
keys.delete(path);
|
|
7560
|
-
ledgerByElement.get(stateElement).byPath.get(path)
|
|
7658
|
+
removeFromKey(ledgerByElement.get(stateElement).byPath.get(path), key, absAddress);
|
|
7561
7659
|
if (entry.levelListIndex !== null) {
|
|
7562
7660
|
removeIndexEntry(entry);
|
|
7563
7661
|
}
|
|
@@ -7580,7 +7678,7 @@ function rekeyIndexSubscriptions(listIndex, oldIndex, newIndex) {
|
|
|
7580
7678
|
for (const { stateElement, path, absAddress } of entries) {
|
|
7581
7679
|
const ledger = ledgerByElement.get(stateElement);
|
|
7582
7680
|
const keyMap = ledger.byPath.get(path);
|
|
7583
|
-
keyMap
|
|
7681
|
+
removeFromKey(keyMap, oldIndex, absAddress);
|
|
7584
7682
|
addToKey(keyMap, newIndex, absAddress);
|
|
7585
7683
|
keyByPathByAddress.get(absAddress).set(path, newIndex);
|
|
7586
7684
|
const last = ledger.lastValue.get(path);
|
|
@@ -11440,7 +11538,7 @@ async function buildBindings(root) {
|
|
|
11440
11538
|
}
|
|
11441
11539
|
}
|
|
11442
11540
|
|
|
11443
|
-
var version = "2.6.
|
|
11541
|
+
var version = "2.6.1";
|
|
11444
11542
|
var pkg = {
|
|
11445
11543
|
version: version};
|
|
11446
11544
|
|
|
@@ -18763,16 +18861,26 @@ function recordDeclaredPrevValue(stateElement, path, absAddress, oldValue, hasOl
|
|
|
18763
18861
|
// binding 外からの API update は新しい transaction を開始する(設計書 §4 規則 1)。
|
|
18764
18862
|
// 依存 walk で enqueue される派生アドレスも同じ書き込みの因果に属する。
|
|
18765
18863
|
// 鍵付き購読(`$eq` 系、dependency/keyedDependency.ts)への通知。同値ガードが読んだ旧値と
|
|
18766
|
-
// 新値の鍵に登録された行だけを dirty 化して enqueue する。購読の無いパスは Map 参照
|
|
18767
|
-
|
|
18768
|
-
|
|
18864
|
+
// 新値の鍵に登録された行だけを dirty 化して enqueue する。購読の無いパスは Map 参照 2 回で抜ける。
|
|
18865
|
+
// 祖先への書き込み(`$eq("sel.id")` に対する `sel = {…}`)は配下の鍵付きパスへ知らせる。その旧値は
|
|
18866
|
+
// 同値ガードが読めなかったときだけ `readOld` で書き込み前に読む
|
|
18867
|
+
function notifyKeyed(stateElement, path, hasOldValue, oldValue, value, readOld) {
|
|
18868
|
+
const direct = hasKeyedDependents(stateElement, path);
|
|
18869
|
+
const descendants = hasKeyedDescendants(stateElement, path);
|
|
18870
|
+
if (!direct && !descendants) {
|
|
18769
18871
|
return;
|
|
18770
18872
|
}
|
|
18771
18873
|
const updater = getUpdater();
|
|
18772
18874
|
const context = config.enablePropagationContext ? (getCurrentPropagationContext() ?? null) : null;
|
|
18773
|
-
|
|
18875
|
+
const enqueue = (absAddress) => {
|
|
18774
18876
|
dirtyCacheEntryByAbsoluteStateAddress(absAddress);
|
|
18775
18877
|
updater.enqueueAbsoluteAddress(absAddress, context);
|
|
18878
|
+
};
|
|
18879
|
+
if (direct) {
|
|
18880
|
+
keyedDependents(stateElement, path, hasOldValue, oldValue, value).forEach(enqueue);
|
|
18881
|
+
}
|
|
18882
|
+
if (descendants) {
|
|
18883
|
+
keyedDescendantDependents(stateElement, path, hasOldValue ? oldValue : readOld(), value).forEach(enqueue);
|
|
18776
18884
|
}
|
|
18777
18885
|
}
|
|
18778
18886
|
function notifyWrite(address, absAddress, receiver, handler, keyedMergePath, cacheable, listExpansion = "diff") {
|
|
@@ -19114,7 +19222,8 @@ function setByAddressCore(target, address, value, receiver, handler, keyedMergeP
|
|
|
19114
19222
|
devOldValue = oldValue;
|
|
19115
19223
|
devHasOldValue = true;
|
|
19116
19224
|
}
|
|
19117
|
-
|
|
19225
|
+
// key が undefined(listIndex の無い不正アドレス)なら読みは undefined — 書き込みが下で投げる
|
|
19226
|
+
notifyKeyed(stateElement, path, devHasOldValue, devOldValue, value, () => parentValue[key]);
|
|
19118
19227
|
const cacheable = isCacheable(stateElement, address);
|
|
19119
19228
|
const absAddress = liftAddress(stateElement, address);
|
|
19120
19229
|
if (devtoolsSink !== null) {
|
|
@@ -19178,7 +19287,7 @@ function setByAddressCore(target, address, value, receiver, handler, keyedMergeP
|
|
|
19178
19287
|
devOldValue = oldValue;
|
|
19179
19288
|
devHasOldValue = true;
|
|
19180
19289
|
}
|
|
19181
|
-
notifyKeyed(stateElement, path, devHasOldValue, devOldValue, value);
|
|
19290
|
+
notifyKeyed(stateElement, path, devHasOldValue, devOldValue, value, () => getByAddress(target, address, receiver, handler));
|
|
19182
19291
|
// --- end same-value guard ---
|
|
19183
19292
|
const isSwappable = stateElement.elementPaths.has(address.pathInfo.path);
|
|
19184
19293
|
const cacheable = isCacheable(stateElement, address);
|
|
@@ -20179,6 +20288,23 @@ function setLoopContext(handler, loopContext, callback) {
|
|
|
20179
20288
|
* - 通常のプロパティアクセスもバインディングや多重ループに対応
|
|
20180
20289
|
* - シンボルAPIやReflect.getで拡張性・互換性も確保
|
|
20181
20290
|
*/
|
|
20291
|
+
/**
|
|
20292
|
+
* 鍵付き購読の `path` が getter か getter の配下か。getter の値は `path` への書き込みを経ずに変わるので
|
|
20293
|
+
* 鍵付き購読では知らせられない — そのときは依存を張る普通の読み取りに戻す(正しいが、変化で全行が
|
|
20294
|
+
* 再評価される。鍵付きの形を使わないのと同じ)。
|
|
20295
|
+
*/
|
|
20296
|
+
function derivesFromGetter(stateElement, path) {
|
|
20297
|
+
const getterPaths = stateElement.getterPaths;
|
|
20298
|
+
if (getterPaths.size === 0) {
|
|
20299
|
+
return false;
|
|
20300
|
+
}
|
|
20301
|
+
for (const cumulativePath of getPathInfo(path).cumulativePaths) {
|
|
20302
|
+
if (getterPaths.has(cumulativePath)) {
|
|
20303
|
+
return true;
|
|
20304
|
+
}
|
|
20305
|
+
}
|
|
20306
|
+
return false;
|
|
20307
|
+
}
|
|
20182
20308
|
/** `$` + 数字だけの prop(`$1` / `$129`)。範囲外を無言で通さないための判別。 */
|
|
20183
20309
|
const INDEX_PARAM_RE = /^\$\d+$/;
|
|
20184
20310
|
// `$streamStatus.<name>` / `$streamError.<name>` の dotted パス判定用プレフィックス
|
|
@@ -20282,6 +20408,9 @@ function get(target, prop, receiver, handler) {
|
|
|
20282
20408
|
return (path, key) => {
|
|
20283
20409
|
// getter の外(メソッド・コールバック)ではアドレススタックが空: 比較だけ返す
|
|
20284
20410
|
const lastAddress = handler.addressStackLength > 0 ? handler.lastAddressStack : null;
|
|
20411
|
+
if (derivesFromGetter(handler.stateElement, path)) {
|
|
20412
|
+
return Object.is(receiver[path], key);
|
|
20413
|
+
}
|
|
20285
20414
|
handler.beginUntrack();
|
|
20286
20415
|
let current;
|
|
20287
20416
|
try {
|
|
@@ -20301,17 +20430,20 @@ function get(target, prop, receiver, handler) {
|
|
|
20301
20430
|
// 追跡付きで行 id を読むと動的辺がリスト置換で全行に展開されるので、ここで抑止する
|
|
20302
20431
|
return (path, keyPath) => {
|
|
20303
20432
|
const lastAddress = handler.addressStackLength > 0 ? handler.lastAddressStack : null;
|
|
20433
|
+
const tracked = derivesFromGetter(handler.stateElement, path);
|
|
20434
|
+
let current = tracked ? receiver[path] : undefined;
|
|
20304
20435
|
handler.beginUntrack();
|
|
20305
|
-
let current;
|
|
20306
20436
|
let key;
|
|
20307
20437
|
try {
|
|
20308
|
-
|
|
20438
|
+
if (!tracked) {
|
|
20439
|
+
current = receiver[path];
|
|
20440
|
+
}
|
|
20309
20441
|
key = receiver[keyPath];
|
|
20310
20442
|
}
|
|
20311
20443
|
finally {
|
|
20312
20444
|
handler.endUntrack();
|
|
20313
20445
|
}
|
|
20314
|
-
if (lastAddress !== null && handler.stateElement.getterPaths.has(lastAddress.pathInfo.path)) {
|
|
20446
|
+
if (!tracked && lastAddress !== null && handler.stateElement.getterPaths.has(lastAddress.pathInfo.path)) {
|
|
20315
20447
|
registerKeyedDependency(handler.stateElement, path, key, liftAddress(handler.stateElement, lastAddress), current);
|
|
20316
20448
|
}
|
|
20317
20449
|
return Object.is(current, key);
|
|
@@ -20330,6 +20462,9 @@ function get(target, prop, receiver, handler) {
|
|
|
20330
20462
|
if (levelListIndex === null) {
|
|
20331
20463
|
raiseError(`$eqIndex("${path}", ${level}): no list index at that level.`);
|
|
20332
20464
|
}
|
|
20465
|
+
if (derivesFromGetter(handler.stateElement, path)) {
|
|
20466
|
+
return Object.is(receiver[path], levelListIndex.index);
|
|
20467
|
+
}
|
|
20333
20468
|
// 最内段(getter 自身の行の段)はリスト単位の監視で O(1)、外側の段は行ごとの購読
|
|
20334
20469
|
const innermost = level === lastAddress.pathInfo.wildcardCount;
|
|
20335
20470
|
handler.beginUntrack();
|