@wcstack/state 4.0.0-rc.2 → 4.0.0-rc.3
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 +16 -14
- package/README.md +16 -14
- package/dist/auto.min.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/parser.esm.js +1 -0
- package/dist/split/auto.js +1 -1
- package/dist/split/chunks/{chunk-HFKGUDDJ.js → chunk-2PWEFKRB.js} +1 -1
- package/dist/split/chunks/chunk-5PWMZ75N.js +1 -0
- package/dist/split/chunks/chunk-FED2DM4X.js +1 -0
- package/dist/split/chunks/chunk-HYEQWIFX.js +1 -0
- package/dist/split/chunks/chunk-I2V4DGEI.js +1 -0
- package/dist/split/chunks/{chunk-WWJNAZVP.js → chunk-NPPWJBGJ.js} +1 -1
- package/dist/split/chunks/chunk-WPSUT5QQ.js +1 -0
- package/dist/split/chunks/chunk-YPD4YBJO.js +1 -0
- package/dist/split/chunks/{chunk-QQH6N5CJ.js → chunk-YZ4LWPXN.js} +1 -1
- package/dist/split/chunks/{chunk-732CB4QN.js → chunk-YZUTVCIH.js} +1 -1
- package/dist/split/chunks/{chunk-YOPPVWPQ.js → chunk-Z3EEGBYA.js} +1 -1
- package/dist/split/core.js +1 -1
- package/dist/split/features/devtools.js +1 -1
- package/dist/split/features/diagnostics.js +1 -1
- package/dist/split/features/formats.js +1 -1
- package/dist/split/features/list-keys.js +1 -1
- package/dist/split/features/recursion.js +1 -1
- package/dist/split/features/scopes.js +1 -1
- package/dist/split/features/ssr.js +1 -1
- package/dist/split/features/temporal.js +1 -1
- package/package.json +1 -1
- package/dist/split/chunks/chunk-7PN5YNZZ.js +0 -1
- package/dist/split/chunks/chunk-FF6KHTYS.js +0 -1
- package/dist/split/chunks/chunk-PKDBVBAP.js +0 -1
- package/dist/split/chunks/chunk-QI7P7YPP.js +0 -1
- package/dist/split/chunks/chunk-UM3O6QXD.js +0 -1
- package/dist/split/chunks/chunk-WEL3YSG7.js +0 -1
package/README.ja.md
CHANGED
|
@@ -191,9 +191,9 @@
|
|
|
191
191
|
<script type="importmap">
|
|
192
192
|
{
|
|
193
193
|
"imports": {
|
|
194
|
-
"@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.
|
|
195
|
-
"@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.
|
|
196
|
-
"@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.
|
|
194
|
+
"@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.3/dist/split/core.js",
|
|
195
|
+
"@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.3/dist/split/features/temporal.js",
|
|
196
|
+
"@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.3/dist/split/features/scopes.js"
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
</script>
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
ページが名指したアドオンを、同じビルドから読み込みます。
|
|
253
253
|
|
|
254
254
|
```html
|
|
255
|
-
<script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.
|
|
255
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.3/dist/split/auto.js"></script>
|
|
256
256
|
<wcs-state features="scopes diagnostics">
|
|
257
257
|
<script type="module">
|
|
258
258
|
export default {
|
|
@@ -396,7 +396,7 @@
|
|
|
396
396
|
<div data-wcs="textContent: cart.total"></div>
|
|
397
397
|
```
|
|
398
398
|
|
|
399
|
-
ボリュームはデータ・getter・setter・メソッドを持ち、`$connectedCallback` / `$disconnectedCallback` を宣言できます。そのどれの中でも `this` はマウントパスを基点にします: `this.items` はツリーの `cart.items` で、パスを取る `$` API(`$getAll`・`$setAll`・`$resolve`・`$postUpdate`・`$dependOn`・`$eq`・`$eqPath`・`$eqIndex`)もマウント相対のパスを取ります。getter と setter はツリーの `cart.<キー>` のアクセサになり、メソッドはパスで呼べます —— `onclick: cart.checkout`、ルートからは `this["cart.checkout"]()`。読み込み順は自由です:
|
|
399
|
+
ボリュームはデータ・getter・setter・メソッドを持ち、`$connectedCallback` / `$disconnectedCallback` を宣言できます。そのどれの中でも `this` はマウントパスを基点にします: `this.items` はツリーの `cart.items` で、パスを取る `$` API(`$getAll`・`$setAll`・`$resolve`・`$postUpdate`・`$dependOn`・`$eq`・`$eqPath`・`$eqIndex`)もマウント相対のパスを取ります。getter と setter はツリーの `cart.<キー>` のアクセサになり、メソッドはパスで呼べます —— `onclick: cart.checkout`、ルートからは `this["cart.checkout"]()`。読み込み順は自由です: ルートより先に接続されたボリュームは、ルートのエンジンが作られるとき、ページがバインドされる前に接ぎ木されます。ルートの `<wcs-state>` がページから外れている間に接続されたボリュームは、それが戻されたときに接ぎ木されます。
|
|
400
400
|
|
|
401
401
|
ボリュームは、ツリー全体に反応する宣言を実行しません。`$watch`・`$stream`・`$listKeys`・`$renderedCallback`・`$recursion`・`$behavior`・`$features` —— そして削除された `$scan`・`$streams`・`$updatedCallback` —— を宣言したボリュームは拒否されます: 接ぎ木されず、`console.error` が要素を名指して `$watch is not run in a volume — declare it on the root state.` と伝えます。これらはルートの state にフルパスで宣言してください —— ルートのハンドラの中では `this` はルートで(`$watch: { "cart.total"(cur) { … } }`)、ルートの `$renderedCallback(paths)` はルートから書いたパス(`cart.items.*.name`)を受け取るので、接頭辞で絞り込みます。`$commandTokens`・`$eventTokens`・`$on`・`$errorCallback` は、`console.warn` を出して実行されません。ボリュームは注入も受けません: ボリューム要素に `data-wcs`(`state.taxRate: settings.taxRate`)があると、そのボリュームは拒否されます —— ルートのパスはルートの getter で読んでください(`get cartTotalWithTax() { return this["cart.subtotal"] * (1 + this["settings.taxRate"]); }`)。
|
|
402
402
|
|
|
@@ -798,7 +798,7 @@ export default {
|
|
|
798
798
|
</template>
|
|
799
799
|
```
|
|
800
800
|
|
|
801
|
-
getter
|
|
801
|
+
getter はコピーを返しますが、書き込みは両側に届きます。元のパスを通した書き込み(`this["items.1.name"] = "z"`)はコピー側の行に、行を通した書き込み(`firstTwo.1.name`、または行の中のバインディング)は元のリストの行に届きます。getter の配列が書いたオブジェクトを持つので、getter は評価し直されます —— 絞り込み(`todos` の未完了の要素を返す `get shown()` を `for: shown` で描画する場合)なら、チェックボックスが `.done` を書いた行は表示から抜けます。新しいオブジェクトを返す getter(`map`)は、こうした書き込みでは評価し直しません。届かない場合は [同じオブジェクトが 2 つの位置にあるとき](#resolve--明示的なインデックスでのアクセス) を参照してください。
|
|
802
802
|
|
|
803
803
|
#### `$listKeys` — 再取得された行の同一性
|
|
804
804
|
|
|
@@ -894,7 +894,7 @@ this.items = await (await fetch("/api/items")).json();
|
|
|
894
894
|
</wcs-route>
|
|
895
895
|
```
|
|
896
896
|
|
|
897
|
-
バインダはマークアップを理由に throw しません: 引き渡された部分木の中のエラーは `console.error` で報告され、それより前の部分はバインドされたまま残り、ナビゲーションは続きます。もう一度引き渡された部分木(router は挿入のたびに中身を引き渡します)がバインドされるのは 1
|
|
897
|
+
バインダはマークアップを理由に throw しません: 引き渡された部分木の中のエラーは `console.error` で報告され、それより前の部分はバインドされたまま残り、ナビゲーションは続きます。もう一度引き渡された部分木(router は挿入のたびに中身を引き渡します)がバインドされるのは 1 回だけで、ルートの `<wcs-state>` がページから外れている間に引き渡された部分木は、その要素が戻されたときに —— 引き渡されたときの範囲の宣言のとおりに —— バインドされます。引き渡されるのは要素だけなので、ルートの本体に要素の外で直接書いた `{{ }}` は、ナビゲーションでそのルートに入ったときにはバインドされません —— 要素で包んでください。これが問題になるのは、バインダを自分で呼ぶ場合か、`<wcs-route>` の直下に `for:` / `if:` を書く場合だけです。
|
|
898
898
|
|
|
899
899
|
## パス getter(算出プロパティ)
|
|
900
900
|
|
|
@@ -1173,7 +1173,7 @@ export default {
|
|
|
1173
1173
|
|
|
1174
1174
|
4. **直接インデックスアクセス** — 行をインデックスで指すこともできます: `this["users.0.name"]` は、ループコンテキストなしで「いま 0 番目にある行」を指します。要素への書き込み(`this["users.0"] = { ...this["users.0"], name: "z" }`)はその位置の値を置き換えます: 行はその場にとどまって新しい値を受け取り、その下で派生するもの(`users.0.name`・`$getAll("users.*.name")`・行 getter)はすべて計算し直されます —— `for:` でリストを描いているかどうかに関わらず同じです。オブジェクトをその場で書き換えて `$postUpdate("users.0")` / `$postUpdate("users")` で知らせると、行は据え置いたまま、その下を読み直します。数値の添字を含むバインディングパスも同じ意味 ——「*いま 0 番目にある行*」—— を持ちます: `textContent: users.0.name`、`{{ users.1.name }}`、`{{ users.0.fullName }}` のような行 getter、そして添字を複数含むパス(`groups.0.items.1.v`)。添字のパスを通した書き込み、`for:` の行やその行に束縛した入力欄を通した書き込み、要素の差し替え、並べ替え、行の削除、リストの丸ごと置換に追従します。数値の添字を含む `$watch` のキー(`"users.0.name"`)は、その位置の値が変わったときだけ発火します。リストの末尾より先の添字は `undefined` と読め、そこへの書き込みは `no row for "users.*.name"`(`#3`)を throw して何も変えません。
|
|
1175
1175
|
|
|
1176
|
-
|
|
1176
|
+
数値の段が添字になるのは、その前の値が配列のときだけです。配列でないオブジェクトの下では、書いたとおりの綴りのキーです: `sales.2024.total`・`usersById.42.name` は `sales["2024"].total`・`usersById["42"].name` を読み書きします —— マークアップ、`this[...]`(getter の中でも)、`$eq`、そのパスを渡した `$resolve` / `$setAll` / `$getAll`、双方向の書き戻し、`$watch` のキーのどれでも同じです。そこへの書き込みは、そのパスやその下のパスを読むバインディングと getter を更新します。`byDay.007` はキー `"007"` を読み、そのようなキーの下の配列はふたたび添字で引きます(`usersById.42.tags.0`)。それ以外(`null`・`undefined`・文字列)への数値の段には行がなく、リストの末尾より先の添字と同じ扱いです。
|
|
1177
1177
|
|
|
1178
1178
|
### getter は state に対して純粋であること
|
|
1179
1179
|
|
|
@@ -1407,7 +1407,7 @@ export default {
|
|
|
1407
1407
|
};
|
|
1408
1408
|
```
|
|
1409
1409
|
|
|
1410
|
-
**同じオブジェクトが 2 つの位置にあるとき。**
|
|
1410
|
+
**同じオブジェクトが 2 つの位置にあるとき。** 1 つのリストの 2 つの位置に同じオブジェクトがあると(`items: [o, o, …]`)、片方の行の下のキーへの書き込み(`this["items.0.name"] = "z"`)でオブジェクトは変わりますが、もう片方の行のバインディングと行 getter は古い値を表示し続けます: 1 つのリストの中では、書き込みはそれを通した行にしか届かないためです。素の読み取り・ルートの getter・`$getAll` には新しい値が見えます。普通のキーの 2 つの配列が同じオブジェクトを持つ場合(`backup = items; items = items.filter(…)` で両方を描画する形)も同じです。こうした書き込みの後に `$postUpdate("items")` を呼ぶと、そのリストのすべての行がオブジェクトを表示し直します。これはこのバージョンの既知の制約です。getter が配列を結び付ける場合 —— TodoMVC 風の絞り込み(`todos` を絞り込んだコピーを返す `get shown()` を `for: shown` で描画する形)、並べ替え、切り出し —— は、どちら側から書いても、オブジェクトを持つすべての行と `todos.*` を読むものに届き、そのオブジェクトを配列に持つ getter は評価し直されます(チェックした行は未完了の絞り込みから抜け、名前を変えた行は並べ替えた表示で動きます)。書いたオブジェクトを配列に持たない getter は評価し直しません: 配列を読んだのであって、オブジェクトは読んでいないためです —— どの行にも追従させるには、その中で `$getAll("todos.*.done")` を読んでください。
|
|
1411
1411
|
|
|
1412
1412
|
## 再帰パス(`$recursion`)
|
|
1413
1413
|
|
|
@@ -1562,7 +1562,7 @@ this.$setAll("nodes.**.selected", [], false); // 全深さの全ノード
|
|
|
1562
1562
|
|
|
1563
1563
|
行オブジェクトを作り直して `children` 配列を引き継ぐ置換 —— `this.nodes = this.nodes.map(n => ({ ...n }))` —— はふつうの更新で、集計もそのまま追従します。
|
|
1564
1564
|
|
|
1565
|
-
再帰の走査の外では、行が 1 本の配列を共有してもかまいません(走査は上のとおりそれを拒否します)。同じ配列インスタンスを持つリストはどれも、そのうちのどれを通した書き込みも表示します —— `groups.0.items.1` の下への書き込みは、`groups.2.items.1` も同じ配列を(同じキーでも別のキーでも)持っていれば、そこにも届きます。1
|
|
1565
|
+
再帰の走査の外では、行が 1 本の配列を共有してもかまいません(走査は上のとおりそれを拒否します)。同じ配列インスタンスを持つリストはどれも、そのうちのどれを通した書き込みも表示します —— `groups.0.items.1` の下への書き込みは、`groups.2.items.1` も同じ配列を(同じキーでも別のキーでも)持っていれば、そこにも届きます。1 つのオブジェクトに*別々の*配列を通して到達する場合、getter が配列を結び付けていれば(絞り込んだコピーを返す getter、並べ替え)書き込みに追従します。普通のキーの 2 つのコピーを通す場合は、[同じオブジェクトが 2 つの位置にあるとき](#resolve--明示的なインデックスでのアクセス) で説明した既知の制約です。
|
|
1566
1566
|
|
|
1567
1567
|
上限は展開後のパスの**ワイルドカード 128 段**です。上の集計 getter は評価中のノードより 1 段下を読むので、127 段の鎖までは畳めて、128 段で `wcs/recursion-depth-exceeded` になります(アンカー・到達した深さ・組み立てようとしたパス・上限を名指しします)。この検査は getter 評価スタック自身の 128 段の上限(`wcs/getter-depth-exceeded`)より先に効くので、深い木は「深い」と報告され、循環の疑いを掛けられることはありません。途中で打ち切ることもしません —— 部分的な集計は、誤った値を正しい値として返すことだからです。
|
|
1568
1568
|
|
|
@@ -1978,7 +1978,8 @@ customElements.define("my-component", MyComponent);
|
|
|
1978
1978
|
- **マウントされた**スコープは、自身のバインディングとリストに対して `$listKeys`・`$commandTokens` / `$eventTokens` / `$on`・`$errorCallback` を実行し、自身の `$behavior` / `$features` も実行する。`$watch`・`$stream`・`$renderedCallback` は実行せず(`wcs/mount-dollar-declaration` を 1 回警告)、`$recursion` / `**` getter は `[wcs/mount-dollar-declaration]` を throw する。これらはルート state に宣言すること。配線されていない Shadow DOM の子は独立したツリーを持つため、いずれも宣言できる
|
|
1979
1979
|
- コンポーネントが state を読み込んだ後に追加されたホストの配線(後からバインドした `state.a: x`)はコンポーネントに届かず、throw する —— ホストの配線は、コンポーネントが読み込む前にバインドすること
|
|
1980
1980
|
- マウントに失敗したコンポーネントは `connectedCallbackPromise` を reject する —— 初期化に失敗したルートに配線されたコンポーネントも含む(`<tag>.state will not mount: the root state failed to initialize.`)—— [初期化の失敗](#初期化の失敗) 参照
|
|
1981
|
-
- 古い `<wcs-state bind-component>` がバインド・描画したノードが残っている間に、コンポーネントがそれを新しい `<wcs-state bind-component>` に置き換えると、新しい要素がスコープを引き継ぐ: それらのバインディング・行・`{{ }}` のテキストはそのまま残り、その横に追加されたバインディング付きのノードはバインドされない(`console.warn`
|
|
1981
|
+
- 古い `<wcs-state bind-component>` がバインド・描画したノードが残っている間に、コンポーネントがそれを新しい `<wcs-state bind-component>` に置き換えると、新しい要素がスコープを引き継ぐ: それらのバインディング・行・`{{ }}` のテキストはそのまま残り、その横に追加されたバインディング付きのノードはバインドされない(`console.warn` がそう伝える)。その中身を新しい要素で描画し直すと、新しい要素が改めてバインドする。新しい要素を外して古い要素を元の場所に戻すと、古い要素が同じようにスコープを引き継ぎ直す
|
|
1982
|
+
- ページがバインドしなかったホスト —— ページの state が読み込まれる前にページから外され、あるいは後から `for:` / `if:` やルーターではなくコードで挿入されたもの —— は、その `<wcs-state bind-component>` が接続したときに配線される: ホストの `data-wcs` はそのとき、あるいはページの `<wcs-state>` がページに入ったときにバインドされる(Light DOM コンポーネントの中では、そのコンポーネントがマウントされたときに、そのコンポーネントが)
|
|
1982
1983
|
|
|
1983
1984
|
### ループ内でのコンポーネント使用
|
|
1984
1985
|
|
|
@@ -2536,7 +2537,7 @@ $renderedCallback(paths) {
|
|
|
2536
2537
|
- **行 watch も headless です** —— `for:` も `$listKeys` も要りません: watch は、自分が範囲とするリストを自分で追従させます。同じ配列やその写しの代入と `$postUpdate("items")` では発火しません —— どの行もリストに入っておらず、どの行パスも書かれていないためです —— が、行を読む getter(`$getAll("items.*.price", [])`)はその場で変えた値を読みます。
|
|
2537
2538
|
- **共有配列は位置ごとに発火します** —— 複数の外側の行が同じ配列を持っているとき、その要素の 1 つへの書き込みは、その配列を持つ外側の行ごとに 1 回ずつ、それぞれの添字で `$watch("groups.*.items.*")` を発火させます: それらのどのパスでも値が変わったからです。
|
|
2538
2539
|
- **ハンドラの例外は隔離されます** —— throw は報告され(`$watch "…" failed; the other watches still run.`)、残りの watch(と stream の restart)は続行します。
|
|
2539
|
-
- **書き込みの連鎖には上限があります** —— ハンドラの書き込みは新しいバッチを作るため、相互に書き合う watch は無限ループになり得ます。書き込みはそれぞれ連鎖の深さを持ちます: その書き込みをしたハンドラを起こした書き込みの深さに 1 を足したものです。32 段より深くで起こされたハンドラは実行されません: 連鎖は断ち切られ、`console.error`(`$watch handlers / $stream restarts kept writing for 32 batches; the chain is cut (nothing is rolled back).`)と DevTools の `state:watch-chain-limit` で 1 回報告され、そのバッチのほかのハンドラは実行されます。値と DOM は巻き戻しません。同じバッチに載る書き込み —— 描画の書き戻し —— は連鎖を伸ばしもリセットもしないので、長い有限の描画の連鎖(`$renderedCallback` が描画ごとに見出しを 1 段ずつ詰める)を見るハンドラは打ち切られません。`$stream` の restart もハンドラと同じく数えます: restart の書き込み(`initial` への戻し・status)は `args` に届いた書き込みの連鎖の続きで、上限を越えた restart
|
|
2540
|
+
- **書き込みの連鎖には上限があります** —— ハンドラの書き込みは新しいバッチを作るため、相互に書き合う watch は無限ループになり得ます。書き込みはそれぞれ連鎖の深さを持ちます: その書き込みをしたハンドラを起こした書き込みの深さに 1 を足したものです。32 段より深くで起こされたハンドラは実行されません: 連鎖は断ち切られ、`console.error`(`$watch handlers / $stream restarts kept writing for 32 batches; the chain is cut (nothing is rolled back).`)と DevTools の `state:watch-chain-limit` で 1 回報告され、そのバッチのほかのハンドラは実行されます。値と DOM は巻き戻しません。同じバッチに載る書き込み —— 描画の書き戻し —— は連鎖を伸ばしもリセットもしないので、長い有限の描画の連鎖(`$renderedCallback` が描画ごとに見出しを 1 段ずつ詰める)を見るハンドラは打ち切られません。`$stream` の restart もハンドラと同じく数えます: restart の書き込み(`initial` への戻し・status)は `args` に届いた書き込みの連鎖の続きで、上限を越えた restart は行いません。実行が(再)開始と同じタスクの中で書くもの —— source が出した値、status の `done` / `error` —— はその実行の連鎖に数え、それが起こすハンドラも restart も連鎖の続きになります。そのため、すぐに値を出す source を通るループはページを固まらせずに打ち切られます: `args` で互いの値を読む 2 つの stream も、stream の値を見る `$watch` がその `args` の読むものを進める形も。後者の 1 周(ハンドラ → restart)は 2 段なので、有限かどうかに関わらず 16 周ほどで止まります(メモリ上の source を使う自動ページ送りも)。後のタスク(ネットワークの応答、メッセージ、タイマー)から来た値と、それが起こすハンドラは、数え直しになります。
|
|
2540
2541
|
- **マウントされた `bind-component` スコープでは実行されません** —— マウントされたコンポーネントの `$watch` 宣言は、ルート state へ誘導する `wcs/mount-dollar-declaration` の警告を 1 回出して無視されます。`$stream` と `$renderedCallback` も同様です。ボリュームは `$watch` を拒否します([`mount=`](#追加の状態をマウントするmount)): ルートにフルパス(`"cart.total"`)で宣言してください。plain な(配線なし Shadow の)子は独立ツリーを持つので宣言できます。
|
|
2541
2542
|
- **SSR では実行されません** —— ハンドラの副作用がサーバーとクライアントで二重に走るためです。
|
|
2542
2543
|
|
|
@@ -2901,6 +2902,7 @@ li {
|
|
|
2901
2902
|
| 48 | | `the locale "<locale>" (<html lang> or bootstrapState's locale) is not a language tag Intl takes (en-US, not en_US): the locale filters use "en".` |
|
|
2902
2903
|
| 49 | | `<wcs-state src="…"> failed to initialize.` |
|
|
2903
2904
|
| 50 | | `<wcs-state src="…"> $connectedCallback failed.` |
|
|
2905
|
+
| 51 | | `<wcs-state src="…"> $disconnectedCallback failed.` |
|
|
2904
2906
|
| 101 | `binding-syntax` | `Invalid bindText: "<text>". Missing ':' separator between propPart and statePart.` |
|
|
2905
2907
|
| 102 | `binding-syntax` | `"<text>": "<keyword>" takes no modifiers or filters on its left side — write "<keyword>:".` |
|
|
2906
2908
|
| 103 | `binding-syntax` | `"<text>": "else" takes no value — write "else:".` |
|
|
@@ -3033,7 +3035,7 @@ this.$getAll("matrix.*.*", [row]);
|
|
|
3033
3035
|
|
|
3034
3036
|
`for:` / `if:` の行の中で、バインディングの取り付け時に見つかったエラー(未宣言の token、要素が宣言していないメンバ)は、初期化の失敗ではなく、そのバインディングの失敗になります: `$errorCallback` に届き、行はそのまま構築されます。state にないトップレベルのキーへのバインディングは、初期化の失敗ではなく適用の失敗です。初期化に失敗した要素は再始動できません —— `setInitialState()` は throw します(`#14`)。取り除いて新しい要素を作ってください。
|
|
3035
3037
|
|
|
3036
|
-
**失敗した `$connectedCallback` は初期化の失敗ではありません。** `$connectedCallback` はバインディングが構築された後に走ります。throw または reject したものは `<wcs-state …> $connectedCallback failed.`(`#50`)に続けてエラーとして報告され、`connectedCallbackPromise` はそのエラーで reject します —— ただしページはバインドされています: `getBindingsReady()` は resolve
|
|
3038
|
+
**失敗した `$connectedCallback` は初期化の失敗ではありません。** `$connectedCallback` はバインディングが構築された後に走ります。throw または reject したものは `<wcs-state …> $connectedCallback failed.`(`#50`)に続けてエラーとして報告され、`connectedCallbackPromise` はそのエラーで reject します —— ただしページはバインドされています: `getBindingsReady()` は resolve し、要素は再設定もできます。再接続時に throw または reject したルートの `$connectedCallback` も同じ形で報告され、throw または reject したルートの `$disconnectedCallback` は `<wcs-state …> $disconnectedCallback failed.`(`#51`)に続けてエラーとして報告されます —— どちらも要素のコールバックの外へは漏れません。マウントされたコンポーネントの再接続時に失敗した `$connectedCallback` は `console.error` で報告されます。
|
|
3037
3039
|
|
|
3038
3040
|
**各 Promise が約束すること:**
|
|
3039
3041
|
|
|
@@ -3433,7 +3435,7 @@ bootstrapState(config?, registry?);
|
|
|
3433
3435
|
| `createStateAsync(mutability, callback)` | `createState` の非同期版。readonly のプロキシはコールバックの `await` をまたいでも readonly のままで、`$setAll` と `$resolve(path, indexes, value)` の書き込みも拒否する |
|
|
3434
3436
|
| `setInitialState(state)` | プログラムから状態を設定 — 下記参照 |
|
|
3435
3437
|
|
|
3436
|
-
`setInitialState(state)` は、初期化前なら初期 state を渡します。初期化済みの要素では state 全体を入れ替え、戻る前にすべてのバインディングを新しい state で適用し直します。再設定は書き込みではないので、`$watch` のハンドラも `$renderedCallback` も呼びません。新しい state に無いパスは、深さによらず `undefined` と読みます。そこへのバインディングは空になり(要素の入力は、ほかの `undefined` の値と同じく自分の値を保ちます)、新しい state にキーが無いリストは行を描かず、それを読む getter は `undefined` を得ます。古い state
|
|
3438
|
+
`setInitialState(state)` は、初期化前なら初期 state を渡します。初期化済みの要素では state 全体を入れ替え、戻る前にすべてのバインディングを新しい state で適用し直します。再設定は書き込みではないので、`$watch` のハンドラも `$renderedCallback` も呼びません。新しい state に無いパスは、深さによらず `undefined` と読みます。そこへのバインディングは空になり(要素の入力は、ほかの `undefined` の値と同じく自分の値を保ちます)、新しい state にキーが無いリストは行を描かず、それを読む getter は `undefined` を得ます。古い state にあったトップレベルのキーは、クラスの state のプロトタイプにある getter も含めて、書き込まれるまでこのままです。どの state にも無かったキーは、どこでも同じく読むと失敗します(`#301`)。この扱いは要素の側で覚えていて、新しい state のオブジェクトには書き込まないので、凍結した state(Immer・Redux Toolkit)でも再設定できます。読めないリスト(getter が throw する)はその `for` の失敗として報告され、再設定は続行します。リストは配列の同一性で突き合わせるので、長さが変わったリストは新しい配列で渡してください(push や splice でその場で長さを変えた同じ配列インスタンスでの再設定は非対応です)。新しい state の `$behavior` が違う場合(`#45`)や、宣言の形が誤っている場合は throw し、古い state をそのまま残します。読み込み済みのボリューム(`<wcs-state mount="…">` —— データはルートの木へ複製済みなので、ルートのマウントパスの下へ書いてください)、コンポーネント(`bind-component`)や DCC の定義、ボリュームやマウント済みコンポーネントのあるツリー、初期化に失敗した要素(`#14`)では throw します —— 失敗した要素は再始動できないので、取り除いて作り直してください。バインディングの構築後に `$connectedCallback` が失敗したルートは再設定できます。再設定の後の `Object.keys(this)`・`in`・`delete`・`for…in`・スプレッド(`{ ...this }`)・`JSON.stringify(this)` は、新しい state に対して働きます。
|
|
3437
3439
|
|
|
3438
3440
|
## アーキテクチャ
|
|
3439
3441
|
|
package/README.md
CHANGED
|
@@ -192,9 +192,9 @@ leaves features out, it can compose them instead:
|
|
|
192
192
|
<script type="importmap">
|
|
193
193
|
{
|
|
194
194
|
"imports": {
|
|
195
|
-
"@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.
|
|
196
|
-
"@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.
|
|
197
|
-
"@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.
|
|
195
|
+
"@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.3/dist/split/core.js",
|
|
196
|
+
"@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.3/dist/split/features/temporal.js",
|
|
197
|
+
"@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.3/dist/split/features/scopes.js"
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
</script>
|
|
@@ -253,7 +253,7 @@ receptacle the owning add-on fills on install).
|
|
|
253
253
|
core, and the add-ons the page names from the same build.
|
|
254
254
|
|
|
255
255
|
```html
|
|
256
|
-
<script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.
|
|
256
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.3/dist/split/auto.js"></script>
|
|
257
257
|
<wcs-state features="scopes diagnostics">
|
|
258
258
|
<script type="module">
|
|
259
259
|
export default {
|
|
@@ -401,7 +401,7 @@ There is **one state tree per root**. To split state across modules, mount a vol
|
|
|
401
401
|
<div data-wcs="textContent: cart.total"></div>
|
|
402
402
|
```
|
|
403
403
|
|
|
404
|
-
A volume holds data, getters, setters and methods, and may declare `$connectedCallback` / `$disconnectedCallback`. Inside all of them `this` is rooted at the mount path: `this.items` is the tree's `cart.items`, and the `$` APIs that take a path (`$getAll`, `$setAll`, `$resolve`, `$postUpdate`, `$dependOn`, `$eq`, `$eqPath`, `$eqIndex`) take it relative to the mount. Its getters and setters become accessors of the tree at `cart.<key>`, and its methods are reachable by path — `onclick: cart.checkout`, `this["cart.checkout"]()` from the root. Load order does not matter: a volume connected before the root is grafted when the root's engine is created, before the page is bound.
|
|
404
|
+
A volume holds data, getters, setters and methods, and may declare `$connectedCallback` / `$disconnectedCallback`. Inside all of them `this` is rooted at the mount path: `this.items` is the tree's `cart.items`, and the `$` APIs that take a path (`$getAll`, `$setAll`, `$resolve`, `$postUpdate`, `$dependOn`, `$eq`, `$eqPath`, `$eqIndex`) take it relative to the mount. Its getters and setters become accessors of the tree at `cart.<key>`, and its methods are reachable by path — `onclick: cart.checkout`, `this["cart.checkout"]()` from the root. Load order does not matter: a volume connected before the root is grafted when the root's engine is created, before the page is bound, and one connected while the root `<wcs-state>` is out of the page is grafted when it is put back.
|
|
405
405
|
|
|
406
406
|
A volume does not run the declarations that react to the whole tree. `$watch`, `$stream`, `$listKeys`, `$renderedCallback`, `$recursion`, `$behavior` and `$features` — and the removed `$scan`, `$streams` and `$updatedCallback` — refuse the volume: it is not grafted, and `console.error` names the element and says `$watch is not run in a volume — declare it on the root state.` Declare them on the root state with full paths — inside the root's handlers `this` is the root (`$watch: { "cart.total"(cur) { … } }`), and the root's `$renderedCallback(paths)` receives paths written from the root (`cart.items.*.name`), so filter them by the prefix. `$commandTokens`, `$eventTokens`, `$on` and `$errorCallback` are not run, with a `console.warn`. A volume takes no injections either: a `data-wcs` on the volume element (`state.taxRate: settings.taxRate`) refuses it — read the root path in a root getter (`get cartTotalWithTax() { return this["cart.subtotal"] * (1 + this["settings.taxRate"]); }`).
|
|
407
407
|
|
|
@@ -804,7 +804,7 @@ The exception is data that arrives as **freshly created objects** — `fetch(...
|
|
|
804
804
|
</template>
|
|
805
805
|
```
|
|
806
806
|
|
|
807
|
-
The getter returns a copy,
|
|
807
|
+
The getter returns a copy, and a write reaches both sides: a write through the original path (`this["items.1.name"] = "z"`) reaches the copy's row, and a write through the row (`firstTwo.1.name`, or a binding inside the row) reaches the original list's row. The getter is evaluated again, because its array holds the written object — with a filter (`get shown()` returning the undone items of `todos`, rendered with `for: shown`), a row whose checkbox writes `.done` leaves the view. A getter that returns new objects (`map`) is not evaluated again by such a write. See [One object at two positions](#resolve--access-by-explicit-index) for what is not reached.
|
|
808
808
|
|
|
809
809
|
#### `$listKeys` — identity for refetched rows
|
|
810
810
|
|
|
@@ -900,7 +900,7 @@ Content another package inserts after the page was bound — a route body that `
|
|
|
900
900
|
</wcs-route>
|
|
901
901
|
```
|
|
902
902
|
|
|
903
|
-
The binder never throws for markup reasons: an error in a handed-over subtree is reported with `console.error`, what comes before it stays bound, and the navigation goes on. A subtree handed over again (the router hands its content over on every insertion) is bound once. Only elements are handed over, so a `{{ }}` written directly in a route body, outside any element, is not bound when the route is entered by navigation — wrap it in an element. This matters only if you call the binder yourself, or write `for:` / `if:` directly under `<wcs-route>`.
|
|
903
|
+
The binder never throws for markup reasons: an error in a handed-over subtree is reported with `console.error`, what comes before it stays bound, and the navigation goes on. A subtree handed over again (the router hands its content over on every insertion) is bound once, and one handed over while the root `<wcs-state>` is out of the page is bound — with the range it was handed over with — when that element is put back. Only elements are handed over, so a `{{ }}` written directly in a route body, outside any element, is not bound when the route is entered by navigation — wrap it in an element. This matters only if you call the binder yourself, or write `for:` / `if:` directly under `<wcs-route>`.
|
|
904
904
|
|
|
905
905
|
## Path Getters (Computed Properties)
|
|
906
906
|
|
|
@@ -1179,7 +1179,7 @@ Two-way binding works with path setters — editing the input calls the setter,
|
|
|
1179
1179
|
|
|
1180
1180
|
4. **Direct index access** — You can also address a row by its index: `this["users.0.name"]` is the row at index 0 now, without needing loop context. Writing an element (`this["users.0"] = { ...this["users.0"], name: "z" }`) replaces the value at that position: the row stays where it is, takes the new value, and everything derived below it (`users.0.name`, `$getAll("users.*.name")`, row getters) is computed again — whether or not a `for:` renders the list. Changing the object in place and announcing it with `$postUpdate("users.0")` / `$postUpdate("users")` keeps the row and re-reads what is below it. A binding path with numeric indexes means the same thing — *whatever row is at index 0 now*: `textContent: users.0.name`, `{{ users.1.name }}`, a row getter such as `{{ users.0.fullName }}`, and paths with several indexes (`groups.0.items.1.v`). It follows writes through an index path, through a `for:` row or an input bound to that row, element replacement, reordering, removals and list replacement. A `$watch` key with a numeric index (`"users.0.name"`) fires only when the value at that index changes. An index past the end of a list reads as `undefined`, and a write there throws `no row for "users.*.name"` (`#3`) and changes nothing.
|
|
1181
1181
|
|
|
1182
|
-
|
|
1182
|
+
A numeric segment is an index only where the value before it is an array. Under any other object it is a key, spelled as written: `sales.2024.total` and `usersById.42.name` read and write `sales["2024"].total` and `usersById["42"].name` — in markup, through `this[...]` (also inside a getter), `$eq`, `$resolve` / `$setAll` / `$getAll` given that path, a two-way write-back and a `$watch` key alike — and a write there updates the bindings and getters that read that path or a path under it. `byDay.007` reads the key `"007"`, and an array under such a key is indexed again (`usersById.42.tags.0`). A numeric segment into anything else (`null`, `undefined`, a string) has no row, as one past the end of a list.
|
|
1183
1183
|
|
|
1184
1184
|
### Getters must be pure with respect to state
|
|
1185
1185
|
|
|
@@ -1413,7 +1413,7 @@ export default {
|
|
|
1413
1413
|
};
|
|
1414
1414
|
```
|
|
1415
1415
|
|
|
1416
|
-
**One object at two positions.** When the same object sits at two positions of
|
|
1416
|
+
**One object at two positions.** When the same object sits at two positions of one list (`items: [o, o, …]`), a write to a key below one of those rows (`this["items.0.name"] = "z"`) changes the object, but the other row's bindings and row getters keep showing the old value: within one list, a write reaches only the row it was made through. Plain reads, root getters and `$getAll` see the new value. The same holds for two arrays at plain keys that share an object (`backup = items; items = items.filter(…)`, both rendered). Call `$postUpdate("items")` after such a write, and every row of that list shows the object again. This is a known limitation of this version. When a getter relates the arrays — a TodoMVC-style filter (`get shown()` returning a filtered copy of `todos`, rendered with `for: shown`), a sort, a slice — a write from either side reaches every row holding the object and the `todos.*` readers, and the getters whose array holds it are evaluated again (a checked row leaves an "active" filter, a renamed row moves in a sorted view). A getter whose array does not hold the written object is not evaluated again: it read the array, not the object — read `$getAll("todos.*.done")` in it to follow every row.
|
|
1417
1417
|
|
|
1418
1418
|
## Recursive Paths (`$recursion`)
|
|
1419
1419
|
|
|
@@ -1568,7 +1568,7 @@ The walk descends by depth and checks the shape it needs as it goes: reaching th
|
|
|
1568
1568
|
|
|
1569
1569
|
Replacing a row object while keeping its `children` array — `this.nodes = this.nodes.map(n => ({ ...n }))` — is an ordinary update, and the aggregates follow it.
|
|
1570
1570
|
|
|
1571
|
-
Outside a recursive walk, rows may share one array (the walk refuses it, as above). Every list over the same array instance shows a write made through any one of them — a write below `groups.0.items.1` also reaches `groups.2.items.1` when both hold that array, under the same key or another. One object reached through two *different* arrays
|
|
1571
|
+
Outside a recursive walk, rows may share one array (the walk refuses it, as above). Every list over the same array instance shows a write made through any one of them — a write below `groups.0.items.1` also reaches `groups.2.items.1` when both hold that array, under the same key or another. One object reached through two *different* arrays follows writes when a getter relates them (a getter returning a filtered copy, a sort); through two copies at plain keys it is the known limitation described under [One object at two positions](#resolve--access-by-explicit-index).
|
|
1572
1572
|
|
|
1573
1573
|
The ceiling is **128 wildcard levels** on the expanded path. The aggregate above reads one level below the node it is evaluating, so it folds a chain 127 deep and stops at 128 with `wcs/recursion-depth-exceeded`, naming the anchor, the depth reached, the path it was building, and the limit. That check trips before the getter stack's own 128-frame limit (`wcs/getter-depth-exceeded`), so a deep tree is reported as deep instead of being accused of a cycle. Nothing is truncated on the way: a partial aggregate would be a wrong number reported as a right one.
|
|
1574
1574
|
|
|
@@ -1984,7 +1984,8 @@ customElements.define("my-component", MyComponent);
|
|
|
1984
1984
|
- A **mounted** scope runs `$listKeys`, `$commandTokens` / `$eventTokens` / `$on` and `$errorCallback` on its own bindings and lists, and its own `$behavior` / `$features`. It does not run `$watch`, `$stream` and `$renderedCallback` (one `wcs/mount-dollar-declaration` warning), and `$recursion` / `**` getters throw `[wcs/mount-dollar-declaration]`. Declare those on the root state. An unwired Shadow DOM child owns an independent tree and can declare all of them
|
|
1985
1985
|
- A host wiring added after the component loaded its state (`state.a: x` bound later) cannot reach it, and throws — bind the host's wiring before the component loads
|
|
1986
1986
|
- A component whose mount fails rejects its `connectedCallbackPromise` — including a component wired to a root that failed to initialize (`<tag>.state will not mount: the root state failed to initialize.`) — see [Initialization failures](#initialization-failures)
|
|
1987
|
-
- When a component replaces its `<wcs-state bind-component>` with a new one while nodes the old one bound or rendered are still there, the new element takes the scope over: those bindings, rows and `{{ }}` text stay as they are, and nodes with bindings added beside them are not bound (a `console.warn` says so). When the content is rendered again with it, the new element binds it afresh
|
|
1987
|
+
- When a component replaces its `<wcs-state bind-component>` with a new one while nodes the old one bound or rendered are still there, the new element takes the scope over: those bindings, rows and `{{ }}` text stay as they are, and nodes with bindings added beside them are not bound (a `console.warn` says so). When the content is rendered again with it, the new element binds it afresh. Taking the new element out and putting the old one back where it was makes the old one take the scope over again the same way
|
|
1988
|
+
- A host the page did not bind — taken out of the page before the page's state loaded, or inserted by code afterwards rather than by `for:` / `if:` or the router — is wired when its `<wcs-state bind-component>` connects: the host's `data-wcs` is bound then, or once the page's `<wcs-state>` is in the page (inside a Light DOM component, by that component once it is mounted)
|
|
1988
1989
|
|
|
1989
1990
|
### Loop with Components
|
|
1990
1991
|
|
|
@@ -2546,7 +2547,7 @@ Key rules:
|
|
|
2546
2547
|
- **A row watch is headless too** — it needs no `for:` and no `$listKeys`: the watch keeps the lists it ranges over in step itself. Reassigning the same array or a copy of it, and `$postUpdate("items")`, do not fire it — no row entered the list and no row path was written — but a getter over the rows (`$getAll("items.*.price", [])`) sees values changed in place.
|
|
2547
2548
|
- **Shared arrays fire per position** — when several outer rows hold the same array, writing one of its elements fires `$watch("groups.*.items.*")` once for every outer row that holds it, each with its own indexes: the value changed at every one of those paths.
|
|
2548
2549
|
- **Handler exceptions are isolated** — a throw is reported (`$watch "…" failed; the other watches still run.`) and the remaining watches (and stream restarts) still run.
|
|
2549
|
-
- **Write chains are bounded** — a handler's writes form a new batch, so mutually-writing watches would loop forever. Each write carries a chain depth: one more than that of the write that fired the handler making it. A handler fired more than 32 deep does not run: the chain is cut, reported once with `console.error` (`$watch handlers / $stream restarts kept writing for 32 batches; the chain is cut (nothing is rolled back).`) and to DevTools as `state:watch-chain-limit`, while the batch's other handlers still run. Values and DOM are not rolled back. Writes riding in the same batch — a render's write-back — neither lengthen nor reset a chain, so a handler that follows a long finite render chain — `$renderedCallback` shrinking a heading one step per render — is not cut off. A `$stream` restart counts like a handler: its writes (the reset to `initial`, the status) continue the chain of the write that reached its `args`, and a restart past the limit is not made.
|
|
2550
|
+
- **Write chains are bounded** — a handler's writes form a new batch, so mutually-writing watches would loop forever. Each write carries a chain depth: one more than that of the write that fired the handler making it. A handler fired more than 32 deep does not run: the chain is cut, reported once with `console.error` (`$watch handlers / $stream restarts kept writing for 32 batches; the chain is cut (nothing is rolled back).`) and to DevTools as `state:watch-chain-limit`, while the batch's other handlers still run. Values and DOM are not rolled back. Writes riding in the same batch — a render's write-back — neither lengthen nor reset a chain, so a handler that follows a long finite render chain — `$renderedCallback` shrinking a heading one step per render — is not cut off. A `$stream` restart counts like a handler: its writes (the reset to `initial`, the status) continue the chain of the write that reached its `args`, and a restart past the limit is not made. What a run writes in the same task as its (re)start — a value its source yields, its status `done` / `error` — counts toward that run's chain, for the handlers it fires as for the restarts it causes. A loop through a source that yields at once is therefore cut instead of freezing the page: two streams whose `args` read each other's values, or a `$watch` on a stream's value that moves what its `args` reads. One lap of the latter — the handler, then the restart — is two links, so it stops after about 16 laps, finite or not (auto-paging over an in-memory source included). A value from a later task (a network response, a message, a timer), and the handlers it fires, start afresh.
|
|
2550
2551
|
- **Not run on a mounted `bind-component` scope** — the `$watch` declaration of a mounted component is ignored with a one-time `wcs/mount-dollar-declaration` warning that points to the root state; this applies to `$stream` and `$renderedCallback` too. A volume refuses `$watch` ([`mount=`](#mounting-additional-state-mount)): declare it on the root with the full path (`"cart.total"`). A plain (unwired Shadow) child owns an independent tree and can declare it.
|
|
2551
2552
|
- **SSR does not run watches** — handler side effects would otherwise execute on both server and client.
|
|
2552
2553
|
|
|
@@ -2911,6 +2912,7 @@ The first line is what `@wcstack/state` and `/auto` print, the second what `/cor
|
|
|
2911
2912
|
| 48 | | `the locale "<locale>" (<html lang> or bootstrapState's locale) is not a language tag Intl takes (en-US, not en_US): the locale filters use "en".` |
|
|
2912
2913
|
| 49 | | `<wcs-state src="…"> failed to initialize.` |
|
|
2913
2914
|
| 50 | | `<wcs-state src="…"> $connectedCallback failed.` |
|
|
2915
|
+
| 51 | | `<wcs-state src="…"> $disconnectedCallback failed.` |
|
|
2914
2916
|
| 101 | `binding-syntax` | `Invalid bindText: "<text>". Missing ':' separator between propPart and statePart.` |
|
|
2915
2917
|
| 102 | `binding-syntax` | `"<text>": "<keyword>" takes no modifiers or filters on its left side — write "<keyword>:".` |
|
|
2916
2918
|
| 103 | `binding-syntax` | `"<text>": "else" takes no value — write "else:".` |
|
|
@@ -3043,7 +3045,7 @@ What fails initialization:
|
|
|
3043
3045
|
|
|
3044
3046
|
Inside a `for:` / `if:` row, an error found while a binding is attached (an undeclared token, a member the element does not declare) is that binding's failure instead: it goes to `$errorCallback`, and the row is still built. A binding to a top-level key the state does not have is a failed apply, not an initialization failure. An element that failed to initialize cannot be re-armed — `setInitialState()` throws (`#14`); remove it and create a new one.
|
|
3045
3047
|
|
|
3046
|
-
**A failing `$connectedCallback` is not an initialization failure.** `$connectedCallback` runs once the bindings are built; one that throws or rejects is reported as `<wcs-state …> $connectedCallback failed.` (`#50`) followed by the error, and `connectedCallbackPromise` rejects with it — but the page is bound: `getBindingsReady()` resolves, and the element can still be re-set. On a reconnect, a failing `$connectedCallback` is reported with `console.error`.
|
|
3048
|
+
**A failing `$connectedCallback` is not an initialization failure.** `$connectedCallback` runs once the bindings are built; one that throws or rejects is reported as `<wcs-state …> $connectedCallback failed.` (`#50`) followed by the error, and `connectedCallbackPromise` rejects with it — but the page is bound: `getBindingsReady()` resolves, and the element can still be re-set. On a reconnect, a root's `$connectedCallback` that throws or rejects is reported the same way, and a root's `$disconnectedCallback` that throws or rejects as `<wcs-state …> $disconnectedCallback failed.` (`#51`) followed by the error — neither escapes the element's callback. A mounted component's failing `$connectedCallback` on a reconnect is reported with `console.error`.
|
|
3047
3049
|
|
|
3048
3050
|
**What the promises promise:**
|
|
3049
3051
|
|
|
@@ -3447,7 +3449,7 @@ Installs every add-on (from `@wcstack/state`; the `/core` entry's `bootstrapStat
|
|
|
3447
3449
|
| `createStateAsync(mutability, callback)` | Async version of `createState`. A readonly proxy stays readonly across the callback's `await`s, and refuses the writes of `$setAll` and `$resolve(path, indexes, value)` too |
|
|
3448
3450
|
| `setInitialState(state)` | Set state programmatically — see below |
|
|
3449
3451
|
|
|
3450
|
-
`setInitialState(state)` before initialization supplies the initial state. On an initialized element it replaces the whole state and re-applies every binding to the new state before it returns. A re-set is not a write: it fires no `$watch` handler and no `$renderedCallback`. A path the new state lacks reads `undefined`, at any depth: a binding to it is emptied (an element input keeps its own value, as for any `undefined` value), a list whose key the new state lacks renders no rows, and a getter that reads it gets `undefined`. A top-level key the old state had stays that way until it is written; a key no state had fails to read, as anywhere (`#301`). This is kept on the element: the new state object is not written to, so a frozen state (Immer, Redux Toolkit) can be re-set too.
|
|
3452
|
+
`setInitialState(state)` before initialization supplies the initial state. On an initialized element it replaces the whole state and re-applies every binding to the new state before it returns. A re-set is not a write: it fires no `$watch` handler and no `$renderedCallback`. A path the new state lacks reads `undefined`, at any depth: a binding to it is emptied (an element input keeps its own value, as for any `undefined` value), a list whose key the new state lacks renders no rows, and a getter that reads it gets `undefined`. A top-level key the old state had — a getter on a class state's prototype included — stays that way until it is written; a key no state had fails to read, as anywhere (`#301`). This is kept on the element: the new state object is not written to, so a frozen state (Immer, Redux Toolkit) can be re-set too. A list that cannot be read (its getter throws) is reported as its `for` failure, and the re-set goes on. Lists are matched by array identity, so pass a new array when a list's length changed — re-setting with the same array instance after pushing to or splicing it in place is not supported. It throws, and leaves the old state in place, when the new state's `$behavior` differs (`#45`) or a declaration has the wrong shape. It throws on a loaded volume (`<wcs-state mount="…">` — its data was copied into the root tree, so write the paths under the mount path on the root instead), on a component (`bind-component`) or DCC definition, on a tree with grafted volumes or mounted components, and on an element that failed to initialize (`#14`) — such an element cannot be re-armed; remove it and create a new one. A root whose `$connectedCallback` failed after its bindings were built can be re-set. After a re-set, `Object.keys(this)`, `in`, `delete`, `for…in`, spreading (`{ ...this }`) and `JSON.stringify(this)` work on the new state.
|
|
3451
3453
|
|
|
3452
3454
|
## Architecture
|
|
3453
3455
|
|