@wcstack/state 2.0.0 → 2.1.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 +85 -17
- package/README.md +88 -19
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.esm.js +89 -22
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -91,7 +91,6 @@ interface IStateProxy extends IState {
|
|
|
91
91
|
type Mutability = "readonly" | "writable";
|
|
92
92
|
|
|
93
93
|
interface IStateElement {
|
|
94
|
-
readonly name: string;
|
|
95
94
|
/**
|
|
96
95
|
* state のロードが完了しているか。`initializePromise` の同期版で、
|
|
97
96
|
* DCC のアクセサが「今すぐ読み書きしてよいか」を判断するのに使う。
|
|
@@ -867,6 +866,13 @@ type DevtoolsEvent = {
|
|
|
867
866
|
readonly tokenName: string;
|
|
868
867
|
readonly args: readonly unknown[];
|
|
869
868
|
readonly subscriberCount: number;
|
|
869
|
+
/**
|
|
870
|
+
* 発火元ツリーの state 要素(protocol v2 追補 2026-09-05・additive)。
|
|
871
|
+
* registry(getOrCreate*Token)経由で作られた token だけが持つ — 直接生成された
|
|
872
|
+
* token や旧ランタイムの payload には無い(optional)。無い emit の実測は
|
|
873
|
+
* ツリー別に分けられないため、消費側は全ツリーの照会へ合算で残す。
|
|
874
|
+
*/
|
|
875
|
+
readonly stateElement?: IStateElement;
|
|
870
876
|
} | {
|
|
871
877
|
readonly type: "state:watch-error";
|
|
872
878
|
/** throw 元。cur の評価(getter)とハンドラ本体では原因も直し方も違う */
|
|
@@ -883,6 +889,13 @@ type DevtoolsEvent = {
|
|
|
883
889
|
readonly type: "state:watch-fired";
|
|
884
890
|
/** `$watch` の宣言キー(ワイルドカードを含む生のパス) */
|
|
885
891
|
readonly path: string;
|
|
892
|
+
/**
|
|
893
|
+
* 発火元ツリーの state 要素(protocol v2 追補 2026-09-05・additive)。
|
|
894
|
+
* 複数ツリーが同名の watch パスを宣言するページで実測台帳をツリー別に
|
|
895
|
+
* 分けるための識別。旧ランタイムの payload には無い(optional)— 無い発火は
|
|
896
|
+
* 消費側が全ツリーの照会へ合算で残す。値を載せない契約(§4.3.1)は不変。
|
|
897
|
+
*/
|
|
898
|
+
readonly stateElement?: IStateElement;
|
|
886
899
|
} | {
|
|
887
900
|
readonly type: "state:path-unresolved";
|
|
888
901
|
/** 書き手が書いた面。診断 code が binding / watch で変わる */
|
|
@@ -988,13 +1001,19 @@ declare function analyzeContract(manifest: IContractManifest): readonly Contract
|
|
|
988
1001
|
declare class State extends HTMLElementBase implements IStateElement {
|
|
989
1002
|
static hasConnectedCallbackPromise: boolean;
|
|
990
1003
|
static getBindingsReady(rootNode: Node): Promise<void>;
|
|
1004
|
+
/**
|
|
1005
|
+
* `mount` の動的変更は未サポート(再マウントは非目標 — 設計書 §4-7)。
|
|
1006
|
+
* 初期化済み要素での変更は無言で捨てず warn で知らせる。初期化前の属性設定
|
|
1007
|
+
* (パース時・接続前の setAttribute)は正規の使い方なので黙る。
|
|
1008
|
+
* `name` は connectedCallback 冒頭で fail-fast 済みなので観測しない。
|
|
1009
|
+
*/
|
|
1010
|
+
static get observedAttributes(): string[];
|
|
991
1011
|
private __state;
|
|
992
1012
|
private _hasUpdatedCallback;
|
|
993
1013
|
/** enable-ssr のスナップショットから初期化された(D14: ボリュームはデータを採用する) */
|
|
994
1014
|
private _hydratedFromSsr;
|
|
995
1015
|
private _crossRowListPaths;
|
|
996
1016
|
private _indexDependentGetterPaths;
|
|
997
|
-
private _name;
|
|
998
1017
|
private _initialized;
|
|
999
1018
|
private _initializePromise;
|
|
1000
1019
|
private _resolveInitialize;
|
|
@@ -1036,7 +1055,7 @@ declare class State extends HTMLElementBase implements IStateElement {
|
|
|
1036
1055
|
constructor();
|
|
1037
1056
|
private get _state();
|
|
1038
1057
|
private set _state(value);
|
|
1039
|
-
|
|
1058
|
+
attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
|
|
1040
1059
|
private _loadFromSsrElement;
|
|
1041
1060
|
/** state / src / json / inner <script> / API set のソース解決(_initialize とボリュームで共用)。 */
|
|
1042
1061
|
private _loadStateFromSource;
|
package/dist/index.esm.js
CHANGED
|
@@ -3655,8 +3655,15 @@ class Token {
|
|
|
3655
3655
|
// EventToken は共有 pub/sub プリミティブ Token の薄い特化(element→state 方向)。
|
|
3656
3656
|
// instanceof による型判別を成立させるため独立クラスとして維持する。
|
|
3657
3657
|
class EventToken extends Token {
|
|
3658
|
-
|
|
3658
|
+
/**
|
|
3659
|
+
* 属する state 要素(devtools のツリー識別 — protocol v2 追補)。
|
|
3660
|
+
* registry(getOrCreateEventToken)経由の生成でのみ渡る optional 参照。
|
|
3661
|
+
* 寿命は registry 側の WeakMap が管理する(CommandToken と同じ位置づけ)。
|
|
3662
|
+
*/
|
|
3663
|
+
_stateElement;
|
|
3664
|
+
constructor(name, stateElement) {
|
|
3659
3665
|
super(name);
|
|
3666
|
+
this._stateElement = stateElement;
|
|
3660
3667
|
}
|
|
3661
3668
|
emit(...args) {
|
|
3662
3669
|
if (devtoolsSink !== null) {
|
|
@@ -3666,6 +3673,7 @@ class EventToken extends Token {
|
|
|
3666
3673
|
tokenName: this.name,
|
|
3667
3674
|
args,
|
|
3668
3675
|
subscriberCount: this.size,
|
|
3676
|
+
stateElement: this._stateElement,
|
|
3669
3677
|
});
|
|
3670
3678
|
}
|
|
3671
3679
|
return super.emit(...args);
|
|
@@ -3681,7 +3689,8 @@ function getOrCreateEventToken(stateElement, name) {
|
|
|
3681
3689
|
}
|
|
3682
3690
|
let token = registry.get(name);
|
|
3683
3691
|
if (typeof token === "undefined") {
|
|
3684
|
-
|
|
3692
|
+
// stateElement を渡すのは devtools のツリー識別(protocol v2 追補)のため
|
|
3693
|
+
token = new EventToken(name, stateElement);
|
|
3685
3694
|
registry.set(name, token);
|
|
3686
3695
|
}
|
|
3687
3696
|
return token;
|
|
@@ -3806,8 +3815,16 @@ function detachEventTokenHandler(binding) {
|
|
|
3806
3815
|
// instanceof による型判別を成立させるため独立クラスとして維持する。
|
|
3807
3816
|
//
|
|
3808
3817
|
class CommandToken extends Token {
|
|
3809
|
-
|
|
3818
|
+
/**
|
|
3819
|
+
* 属する state 要素(devtools のツリー識別 — protocol v2 追補)。
|
|
3820
|
+
* registry(getOrCreateCommandToken)経由の生成でのみ渡る optional 参照で、
|
|
3821
|
+
* token の寿命は registry 側の WeakMap が state 要素に紐づけている(ここが
|
|
3822
|
+
* 寿命を延ばす新しい経路にはならない)。emit の payload にだけ載せる。
|
|
3823
|
+
*/
|
|
3824
|
+
_stateElement;
|
|
3825
|
+
constructor(name, stateElement) {
|
|
3810
3826
|
super(name);
|
|
3827
|
+
this._stateElement = stateElement;
|
|
3811
3828
|
}
|
|
3812
3829
|
emit(...args) {
|
|
3813
3830
|
if (devtoolsSink !== null) {
|
|
@@ -3819,6 +3836,7 @@ class CommandToken extends Token {
|
|
|
3819
3836
|
tokenName: this.name,
|
|
3820
3837
|
args,
|
|
3821
3838
|
subscriberCount: this.size,
|
|
3839
|
+
stateElement: this._stateElement,
|
|
3822
3840
|
});
|
|
3823
3841
|
}
|
|
3824
3842
|
return super.emit(...args);
|
|
@@ -9164,7 +9182,7 @@ async function buildBindings(root) {
|
|
|
9164
9182
|
}
|
|
9165
9183
|
}
|
|
9166
9184
|
|
|
9167
|
-
var version = "2.
|
|
9185
|
+
var version = "2.1.1";
|
|
9168
9186
|
var pkg = {
|
|
9169
9187
|
version: version};
|
|
9170
9188
|
|
|
@@ -11186,7 +11204,8 @@ function getOrCreateCommandToken(stateElement, name) {
|
|
|
11186
11204
|
}
|
|
11187
11205
|
let token = registry.get(name);
|
|
11188
11206
|
if (typeof token === "undefined") {
|
|
11189
|
-
|
|
11207
|
+
// stateElement を渡すのは devtools のツリー識別(protocol v2 追補)のため
|
|
11208
|
+
token = new CommandToken(name, stateElement);
|
|
11190
11209
|
registry.set(name, token);
|
|
11191
11210
|
}
|
|
11192
11211
|
return token;
|
|
@@ -12774,8 +12793,10 @@ function fireOne(hit) {
|
|
|
12774
12793
|
}
|
|
12775
12794
|
// 正常発火の観測(設計書 §11 の予約イベント・配線カバレッジの実測面)。
|
|
12776
12795
|
// 値は載せない。イベント生成は sink 接続時のみ(ゼロコスト契約)。
|
|
12796
|
+
// stateElement は発火元ツリーの識別(protocol v2 追補)— これが無いと
|
|
12797
|
+
// 複数ツリーが同名 watch パスを宣言するページで実測が合算される。
|
|
12777
12798
|
if (devtoolsSink !== null) {
|
|
12778
|
-
devtoolsSink({ type: "state:watch-fired", path: entry.path });
|
|
12799
|
+
devtoolsSink({ type: "state:watch-fired", path: entry.path, stateElement });
|
|
12779
12800
|
}
|
|
12780
12801
|
entry.handler.call(state, cur, prev, ...indexes);
|
|
12781
12802
|
});
|
|
@@ -14654,6 +14675,34 @@ function notifyWrite(address, absAddress, receiver, handler, keyedMergePath) {
|
|
|
14654
14675
|
// $postUpdate の手動リフレッシュは従来通り全行展開のまま)
|
|
14655
14676
|
{ listExpansion: "diff", keyedMergePath });
|
|
14656
14677
|
}
|
|
14678
|
+
/**
|
|
14679
|
+
* 書き込み完了後のキャッシュ整合(Issue #234)。
|
|
14680
|
+
*
|
|
14681
|
+
* ワイルドカードのデータパス(リスト行)は代入値がそのまま格納値なので、
|
|
14682
|
+
* 代入値を dirty:false で載せて次回の読みを省く。
|
|
14683
|
+
*
|
|
14684
|
+
* アクセサペア(getterPaths に載るパス)は getter が正本であり、setter は
|
|
14685
|
+
* 命令的な代入に過ぎない。代入値を getter の評価結果として固定すると
|
|
14686
|
+
* - setter が正規化・分配した結果と読みが食い違う
|
|
14687
|
+
* - getter が一度も評価されず動的依存が張られない → 依存先を書いても
|
|
14688
|
+
* walkDependency がこのキャッシュを dirty にできず、永続的に stale になる
|
|
14689
|
+
* (プリミティブ代入は同値ガードの旧値読みで偶然 getter が走るが、
|
|
14690
|
+
* オブジェクト代入は同値ガードを素通りするため救済がない)
|
|
14691
|
+
* ため、キャッシュを dirty にして次回の読みで getter を再評価させる。
|
|
14692
|
+
*/
|
|
14693
|
+
function commitWriteCache(stateElement, path, absAddress, value, cacheable) {
|
|
14694
|
+
if (!cacheable) {
|
|
14695
|
+
return;
|
|
14696
|
+
}
|
|
14697
|
+
if (stateElement.getterPaths.has(path)) {
|
|
14698
|
+
dirtyCacheEntryByAbsoluteStateAddress(absAddress);
|
|
14699
|
+
return;
|
|
14700
|
+
}
|
|
14701
|
+
setCacheEntryByAbsoluteStateAddress(absAddress, {
|
|
14702
|
+
value: value,
|
|
14703
|
+
dirty: false
|
|
14704
|
+
});
|
|
14705
|
+
}
|
|
14657
14706
|
function _setByAddress(target, address, absAddress, value, receiver, handler, keyedMergePath) {
|
|
14658
14707
|
try {
|
|
14659
14708
|
if (address.pathInfo.path in target) {
|
|
@@ -14875,12 +14924,7 @@ function setByAddressCore(target, address, value, receiver, handler, keyedMergeP
|
|
|
14875
14924
|
}
|
|
14876
14925
|
finally {
|
|
14877
14926
|
notifyWrite(address, absAddress, receiver, handler, keyedMergePath);
|
|
14878
|
-
|
|
14879
|
-
setCacheEntryByAbsoluteStateAddress(absAddress, {
|
|
14880
|
-
value: value,
|
|
14881
|
-
dirty: false
|
|
14882
|
-
});
|
|
14883
|
-
}
|
|
14927
|
+
commitWriteCache(stateElement, path, absAddress, value, cacheable);
|
|
14884
14928
|
// DCC bindable イベントディスパッチ(完全一致 + サブパス → 先頭セグメント、§2.1)
|
|
14885
14929
|
dispatchBindableEvent(stateElement, address.pathInfo, { value });
|
|
14886
14930
|
}
|
|
@@ -14927,12 +14971,7 @@ function setByAddressCore(target, address, value, receiver, handler, keyedMergeP
|
|
|
14927
14971
|
}
|
|
14928
14972
|
}
|
|
14929
14973
|
finally {
|
|
14930
|
-
|
|
14931
|
-
setCacheEntryByAbsoluteStateAddress(absAddress, {
|
|
14932
|
-
value: value,
|
|
14933
|
-
dirty: false
|
|
14934
|
-
});
|
|
14935
|
-
}
|
|
14974
|
+
commitWriteCache(stateElement, path, absAddress, value, cacheable);
|
|
14936
14975
|
// DCC bindable イベントディスパッチ(完全一致 + サブパス → 先頭セグメント、§2.1)
|
|
14937
14976
|
dispatchBindableEvent(stateElement, address.pathInfo, { value });
|
|
14938
14977
|
}
|
|
@@ -15317,6 +15356,14 @@ function updatedCallback(target, refs, receiver, handler) {
|
|
|
15317
15356
|
}
|
|
15318
15357
|
const pathInfo = ref.absolutePathInfo.pathInfo;
|
|
15319
15358
|
const pathName = pathInfo.path;
|
|
15359
|
+
// D20/D21: マーカーパス(`#m<id>` セグメント = マウント私有キーの内部アドレス)は
|
|
15360
|
+
// マウントインスタンスの私有語彙。ルートの $updatedCallback へ素通しすると、
|
|
15361
|
+
// 作者に解釈不能で再初期化のたびに変わる内部 id が漏れるため配送しない
|
|
15362
|
+
// (`#` はパス文法で書けない文字 — ツリーパスとは構造的に衝突しない)。
|
|
15363
|
+
// 私有キーの可視化は devtools の overlays() 経由(プロトコル v2)。
|
|
15364
|
+
if (pathName.indexOf("#") !== -1) {
|
|
15365
|
+
continue;
|
|
15366
|
+
}
|
|
15320
15367
|
paths.add(pathName);
|
|
15321
15368
|
if (pathInfo.wildcardCount > 0) {
|
|
15322
15369
|
const indexes = getScopedIndexes(ref.listIndex, pathInfo.wildcardCount);
|
|
@@ -15349,6 +15396,10 @@ function updatedCallback(target, refs, receiver, handler) {
|
|
|
15349
15396
|
if (path !== volume.mountPath && !path.startsWith(prefix)) {
|
|
15350
15397
|
continue;
|
|
15351
15398
|
}
|
|
15399
|
+
// マーカーパス(マウント私有キー)はボリューム相対配送にも漏らさない(上と同じ D20/D21)
|
|
15400
|
+
if (path.indexOf("#") !== -1) {
|
|
15401
|
+
continue;
|
|
15402
|
+
}
|
|
15352
15403
|
const relative = path === volume.mountPath ? "" : path.slice(prefix.length);
|
|
15353
15404
|
if (relative === "") {
|
|
15354
15405
|
continue; // マウントポイント自身(接ぎ木そのもの)は相対で表せない
|
|
@@ -16386,6 +16437,15 @@ class State extends HTMLElementBase {
|
|
|
16386
16437
|
static getBindingsReady(rootNode) {
|
|
16387
16438
|
return getBindingsReady(rootNode);
|
|
16388
16439
|
}
|
|
16440
|
+
/**
|
|
16441
|
+
* `mount` の動的変更は未サポート(再マウントは非目標 — 設計書 §4-7)。
|
|
16442
|
+
* 初期化済み要素での変更は無言で捨てず warn で知らせる。初期化前の属性設定
|
|
16443
|
+
* (パース時・接続前の setAttribute)は正規の使い方なので黙る。
|
|
16444
|
+
* `name` は connectedCallback 冒頭で fail-fast 済みなので観測しない。
|
|
16445
|
+
*/
|
|
16446
|
+
static get observedAttributes() {
|
|
16447
|
+
return ["mount"];
|
|
16448
|
+
}
|
|
16389
16449
|
__state;
|
|
16390
16450
|
_hasUpdatedCallback = false;
|
|
16391
16451
|
/** enable-ssr のスナップショットから初期化された(D14: ボリュームはデータを採用する) */
|
|
@@ -16396,7 +16456,6 @@ class State extends HTMLElementBase {
|
|
|
16396
16456
|
// $1 等のインデックスを読んだ getter パス(実行時検出)。位置のみ変わった行の
|
|
16397
16457
|
// 静的子展開はこの集合の subtree に限定される。追加のみ・クリアしない(安全側)。
|
|
16398
16458
|
_indexDependentGetterPaths = new Set();
|
|
16399
|
-
_name = 'default';
|
|
16400
16459
|
_initialized = false;
|
|
16401
16460
|
_initializePromise;
|
|
16402
16461
|
_resolveInitialize = null;
|
|
@@ -16530,8 +16589,14 @@ class State extends HTMLElementBase {
|
|
|
16530
16589
|
}
|
|
16531
16590
|
this._resolveLoading?.();
|
|
16532
16591
|
}
|
|
16533
|
-
|
|
16534
|
-
|
|
16592
|
+
attributeChangedCallback(_name, oldValue, newValue) {
|
|
16593
|
+
// observedAttributes は "mount" のみ。同値 set(oldValue === newValue)は変更ではない
|
|
16594
|
+
if (!this._initialized || oldValue === newValue) {
|
|
16595
|
+
return;
|
|
16596
|
+
}
|
|
16597
|
+
console.warn(`[@wcstack/state] Changing the "mount" attribute after initialization is not supported and is ignored ` +
|
|
16598
|
+
`(was ${oldValue === null ? "absent" : `"${oldValue}"`}, now ${newValue === null ? "absent" : `"${newValue}"`}). ` +
|
|
16599
|
+
`Remove this <${config.tagNames.state}> element and create a new one with the desired mount path instead.`);
|
|
16535
16600
|
}
|
|
16536
16601
|
_loadFromSsrElement() {
|
|
16537
16602
|
if (!this.hasAttribute('enable-ssr'))
|
|
@@ -16571,7 +16636,9 @@ class State extends HTMLElementBase {
|
|
|
16571
16636
|
else {
|
|
16572
16637
|
const script = this.querySelector('script[type="module"]');
|
|
16573
16638
|
if (script) {
|
|
16574
|
-
|
|
16639
|
+
// sourceURL ラベル。v2 はルートに 1 ツリーなので名前次元は無く、要素の
|
|
16640
|
+
// タグ名(DCC 経路が host のタグ名を渡すのと同じ流儀)で特定十分
|
|
16641
|
+
return await loadFromInnerScript(script, config.tagNames.state);
|
|
16575
16642
|
}
|
|
16576
16643
|
else {
|
|
16577
16644
|
const timerId = setTimeout(() => {
|