@wcstack/state 4.0.0-rc.2 → 4.0.0-rc.4
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 +28 -26
- package/README.md +28 -26
- package/dist/auto.min.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/manifest.esm.js +69 -78
- package/dist/parser.esm.js +41 -26
- package/dist/split/auto.js +1 -1
- package/dist/split/chunks/chunk-372Y2FIC.js +1 -0
- package/dist/split/chunks/chunk-5D34CGCF.js +1 -0
- package/dist/split/chunks/{chunk-QQH6N5CJ.js → chunk-5LIOXC7W.js} +1 -1
- package/dist/split/chunks/chunk-6GES2BK3.js +1 -0
- package/dist/split/chunks/chunk-7644KQ3T.js +1 -0
- package/dist/split/chunks/{chunk-YOPPVWPQ.js → chunk-G2ZO6SXV.js} +1 -1
- package/dist/split/chunks/chunk-HSOCDTWX.js +1 -0
- package/dist/split/chunks/chunk-ROX5KEJX.js +1 -0
- package/dist/split/chunks/chunk-SEPY6OHJ.js +1 -0
- package/dist/split/chunks/chunk-Z7B3ZZE4.js +1 -0
- package/dist/split/chunks/chunk-ZOINIOIM.js +1 -0
- package/dist/split/core.js +1 -1
- package/dist/split/features/devtools.js +1 -1
- package/dist/split/features/diagnostics.d.ts +3 -3
- package/dist/split/features/diagnostics.js +1 -1
- package/dist/split/features/formats.d.ts +4 -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/dist/wcs-manifest.json +15 -15
- package/package.json +1 -1
- package/dist/split/chunks/chunk-732CB4QN.js +0 -1
- package/dist/split/chunks/chunk-7PN5YNZZ.js +0 -1
- package/dist/split/chunks/chunk-FF6KHTYS.js +0 -1
- package/dist/split/chunks/chunk-HFKGUDDJ.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/dist/split/chunks/chunk-WWJNAZVP.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.4/dist/split/core.js",
|
|
195
|
+
"@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.4/dist/split/features/temporal.js",
|
|
196
|
+
"@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.4/dist/split/features/scopes.js"
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
</script>
|
|
@@ -226,21 +226,21 @@
|
|
|
226
226
|
|
|
227
227
|
| エントリ | 足されるもの |
|
|
228
228
|
|---|---|
|
|
229
|
-
| `@wcstack/state/core` | バインディングの本体: `data-wcs`・`{{ }}` とコメントバインディング・`for` / `if`・パス getter・イベント・`$command` / `$on
|
|
229
|
+
| `@wcstack/state/core` | バインディングの本体: `data-wcs`・`{{ }}` とコメントバインディング・`for` / `if`・パス getter・イベント・`$command` / `$on`・条件の 10 個のフィルタ(`eq` `ne` `not` `lt` `le` `gt` `ge` `truthy` `falsy` `boolean`)・`bootstrapState`・`installFeatures` |
|
|
230
230
|
| `@wcstack/state/features/temporal` | `$watch`・`$stream` |
|
|
231
231
|
| `@wcstack/state/features/list-keys` | `$listKeys` |
|
|
232
232
|
| `@wcstack/state/features/scopes` | `bind-component`・`mount=` のボリューム・オーバーレイの公開 getter・DCC(`data-wc-definition`) |
|
|
233
233
|
| `@wcstack/state/features/recursion` | `$recursion` と `**` パス |
|
|
234
234
|
| `@wcstack/state/features/ssr` | `enable-ssr`: サーバー描画とハイドレーション |
|
|
235
|
-
| `@wcstack/state/features/formats` |
|
|
235
|
+
| `@wcstack/state/features/formats` | 残りの 37 個のフィルタ: 算術・型変換・デフォルト(`add`・`int`・`coalesce` …)と書式(`upper`・`date`・`round`・`truncate` …) |
|
|
236
236
|
| `@wcstack/state/features/devtools` | DevTools Hook Protocol への source 登録 |
|
|
237
|
-
| `@wcstack/state/features/diagnostics` | エラーメッセージの文章(did-you-mean と lint への案内を含む)と、開発時の警告(束縛や `$watch` のパスが解決しない、`wcs/default-getter-mismatch
|
|
237
|
+
| `@wcstack/state/features/diagnostics` | エラーメッセージの文章(did-you-mean と lint への案内を含む)と、開発時の警告(束縛や `$watch` のパスが解決しない、`wcs/default-getter-mismatch`)。入れなければ、メッセージは番号と値だけになり([エラーメッセージ](#エラーメッセージと番号))、これらの警告は出ず、4.0 で外した 3.x の名前(`$scan`・`$streams`・`$updatedCallback`・`$trackDependency`・`$untrackDependency`)も見つけない |
|
|
238
238
|
| `@wcstack/state/define` | `defineState` と型だけ — ランタイムは 0 |
|
|
239
239
|
|
|
240
240
|
アドオンが入っていない宣言は黙って無視されません。state の読み込み時に
|
|
241
241
|
`[wcs/feature-not-installed] $watch needs the add-on @wcstack/state/features/temporal` を throw し
|
|
242
242
|
(`mount=`・`bind-component`・`data-wc-definition` は同じ形で `scopes` を、`enable-ssr` は `ssr` を
|
|
243
|
-
名指します)、`formats`
|
|
243
|
+
名指します)、`formats` なしの `formats` のフィルタは束縛計画の段で、アドオン名を挙げた `[wcs/filter-unknown]`
|
|
244
244
|
を throw します。install は冪等で — 2 回目の install は読み飛ばされます — どのエントリも core の
|
|
245
245
|
チャンクを 1 つだけ共有します。アドオンがエンジンの 2 つ目のコピーを抱えることも、**別のアドオン**の
|
|
246
246
|
コピーを抱えることもありません(アドオンをまたぐ呼び出しは、持ち主のアドオンが install 時に埋める
|
|
@@ -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.4/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
|
|
|
@@ -1671,7 +1671,7 @@ export default {
|
|
|
1671
1671
|
|
|
1672
1672
|
## フィルタ
|
|
1673
1673
|
|
|
1674
|
-
47 種類の組み込みフィルタが入力(DOM → 状態)と出力(状態 → DOM)の両方向で利用できます: core
|
|
1674
|
+
47 種類の組み込みフィルタが入力(DOM → 状態)と出力(状態 → DOM)の両方向で利用できます: core に条件の 10 個のフィルタ(`eq`・`ne`・`not`・`lt`・`le`・`gt`・`ge`・`truthy`・`falsy`・`boolean`)、`formats` アドオンに残りの 37 個(算術・数値フォーマット・文字列・型変換・日付 / 時刻・デフォルト)があり、全部入りのエントリには両方が含まれます。`formats` なしの `/core` で `formats` のフィルタを使うと、束縛計画の段でアドオン名を挙げて失敗します。
|
|
1675
1675
|
|
|
1676
1676
|
3.x が 3.2 の改名の別名として残していた名前(`inc`・`dec`・`fix`・`uc`・`lc`・`cap`・`rep`・`rev`・`pad`・`null`)と `substr` フィルタは存在しません: 未知の名前と同じく失敗し、メッセージが代わりに書くものを示します(`"uc" was renamed "upper" in 3.2 and removed in 4.0 — write "upper".`。`substr(start, length)` は `slice(start, start + length)`)。
|
|
1677
1677
|
|
|
@@ -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
|
|
|
@@ -2352,7 +2353,7 @@ event token は command token と同じ `Token` pub/sub プリミティブを共
|
|
|
2352
2353
|
|
|
2353
2354
|
## Stream(`$stream`)
|
|
2354
2355
|
|
|
2355
|
-
command token / event token が運ぶのは離散的なやり取りです。**`$stream`** は残る形 —— 連続的なフローをカバーします。非同期 producer(async iterable / async generator / `ReadableStream`)を宣言すると、フレームワークがそれを **fold して単一の reactive プロパティに畳み込みます** —— 各チャンクは通常のパス代入を通るため、バインディング・パス getter・`$renderedCallback` は自分で値を代入した場合とまったく同じように反応します。`args` 関数が読んだ state パスが変化すると、実行中の producer は abort され、新しい引数で source が張り直されます(switchMap 型の依存駆動 restart)。stream は `$connectedCallback` 完了後に eager に起動し、要素の disconnect で abort されます。`$stream` は `temporal` アドオンです(全部入りのエントリに含まれる)。3.x の名前 `$streams`
|
|
2356
|
+
command token / event token が運ぶのは離散的なやり取りです。**`$stream`** は残る形 —— 連続的なフローをカバーします。非同期 producer(async iterable / async generator / `ReadableStream`)を宣言すると、フレームワークがそれを **fold して単一の reactive プロパティに畳み込みます** —— 各チャンクは通常のパス代入を通るため、バインディング・パス getter・`$renderedCallback` は自分で値を代入した場合とまったく同じように反応します。`args` 関数が読んだ state パスが変化すると、実行中の producer は abort され、新しい引数で source が張り直されます(switchMap 型の依存駆動 restart)。stream は `$connectedCallback` 完了後に eager に起動し、要素の disconnect で abort されます。`$stream` は `temporal` アドオンです(全部入りのエントリに含まれる)。3.x の名前 `$streams` は、`diagnostics` アドオンが入っていれば(`@wcstack/state`・`/auto`)`[wcs/declaration-alias] $streams was removed: write $stream.`(`#1601`)を throw します。入っていなければ、その宣言は無視されます。
|
|
2356
2357
|
|
|
2357
2358
|
`$renderedCallback` は引き続き binding 駆動です。stream 宣言だけでは headless な購読にならず、その value/status/error の live DOM binding が実際に適用されたときだけ callback の path に現れます。描画せずに stream の値へ反応したい場合は、そのパスに [`$watch`](#watchwatch) を宣言してください。観測契約は [stream リファレンス](docs/streams.md) を参照してください。
|
|
2358
2359
|
|
|
@@ -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
|
|
|
@@ -2753,7 +2754,7 @@ export default {
|
|
|
2753
2754
|
|
|
2754
2755
|
状態オブジェクトに `$connectedCallback` / `$disconnectedCallback` / `$renderedCallback` / `$errorCallback` を定義すると、初期化・クリーンアップ・更新時・バインディング失敗時のフックとして利用できます。
|
|
2755
2756
|
|
|
2756
|
-
> `$renderedCallback` が受けるのは**適用されたバインディング**の更新で、state の更新全体ではありません(それには `$watch`)。3.x の名前 `$updatedCallback`
|
|
2757
|
+
> `$renderedCallback` が受けるのは**適用されたバインディング**の更新で、state の更新全体ではありません(それには `$watch`)。3.x の名前 `$updatedCallback` は、`diagnostics` アドオンが入っていれば(`@wcstack/state`・`/auto`)`[wcs/declaration-alias] $updatedCallback was removed: write $renderedCallback.`(`#1601`)を throw します。入っていなければ、その宣言は無視されます。
|
|
2757
2758
|
|
|
2758
2759
|
```html
|
|
2759
2760
|
<wcs-state>
|
|
@@ -2835,16 +2836,16 @@ li {
|
|
|
2835
2836
|
|
|
2836
2837
|
### エラーメッセージと番号
|
|
2837
2838
|
|
|
2838
|
-
メッセージはすべて `[@wcstack/state]`
|
|
2839
|
+
メッセージはすべて `[@wcstack/state]` で始まります。ほとんどのメッセージは番号(`#501`)を持ち、`@wcstack/lint` と VS Code 拡張も検査する事柄についてのメッセージは同じ診断 code(`[wcs/filter-unknown]`)を持ちます。番号の百の位がその code を表します —— 1xx `binding-syntax`、2xx `template-syntax`、3xx `binding-path-missing`、4xx `binding-type-expectation`、5xx `filter-unknown`、6xx `filter-arity`、7xx `getter-cycle`、8xx `getter-depth-exceeded`、9xx `index-arity`、10xx `index-param-range`、11xx `recursion-unsupported`、12xx `token-misconfigured`、13xx `token-undeclared`、14xx `wildcard-rank`、15xx `spread-no-bindable`、16xx `declaration-alias`、17xx `name-alias`。1〜99 は code を持ちません。番号は追加されるだけなので、番号の意味はバージョンをまたいで変わりません。
|
|
2839
2840
|
|
|
2840
|
-
|
|
2841
|
+
番号付きメッセージの code と文章 —— いちばん近い名前(lint と同じ規則による did-you-mean)、直し方、そして lint が検出できる場合は lint への案内 —— は、`@wcstack/state` と `/auto` に含まれる `diagnostics` アドオンが出します。それを入れていない `/core` のページでは、メッセージは番号と表示するはずだった値だけになります:
|
|
2841
2842
|
|
|
2842
2843
|
```
|
|
2843
2844
|
[@wcstack/state] [wcs/filter-unknown] filter not found: uc. "uc" was renamed "upper" in 3.2 and removed in 4.0 — write "upper". Validate statically: npx @wcstack/lint <file>.
|
|
2844
|
-
[@wcstack/state]
|
|
2845
|
+
[@wcstack/state] #501 "uc"
|
|
2845
2846
|
```
|
|
2846
2847
|
|
|
2847
|
-
1 行目が `@wcstack/state` と `/auto` の出力、2 行目が `features/diagnostics` なしの `/core` の出力です。`/core` で開発するときは `diagnostics`
|
|
2848
|
+
1 行目が `@wcstack/state` と `/auto` の出力、2 行目が `features/diagnostics` なしの `/core` の出力です。`/core` で開発するときは `diagnostics` を入れてください: 4.0 で外した 3.x の名前(#1・#1601・#1701)を見つけるのもこのアドオンで、入れていないページはそれらを無視します(その名前の宣言は何もせず、読むと `undefined`)。アドオンなしのページが意図して突き当たる障壁(`[wcs/feature-not-installed]`、`formats` なしの `formats` のフィルタ)は、core の中でも完全な文章です。
|
|
2848
2849
|
|
|
2849
2850
|
<details>
|
|
2850
2851
|
<summary>番号付きメッセージ</summary>
|
|
@@ -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:".` |
|
|
@@ -3027,13 +3029,13 @@ this.$getAll("matrix.*.*", [row]);
|
|
|
3027
3029
|
初期化を失敗させるもの:
|
|
3028
3030
|
|
|
3029
3031
|
- 読めないソース([状態の初期化](#状態の初期化))。
|
|
3030
|
-
- 不正な宣言: 削除された名前(`$scan`・`$streams`・`$updatedCallback
|
|
3032
|
+
- 不正な宣言: 削除された名前(`$scan`・`$streams`・`$updatedCallback`。`diagnostics` アドオンがあるとき)、形の誤った `$behavior` / `$features` / `$commandTokens` / `$eventTokens` / `$on`、アドオンが入っていない宣言(`[wcs/feature-not-installed]`)。
|
|
3031
3033
|
- ページのバインド中に見つかったマークアップの誤り: 構文エラー、未知のフィルタや誤った引数の個数、wildcard-rank のエラー、フィルタ付きの `for:`、テンプレートの中の `outerHTML:`、クラスが定義済みの要素での wc-bindable の設定ミス。走査はそのエラーで止まります: 文書順でそれより前はバインドされ、後はバインドされません。デプロイの前に `npx @wcstack/lint` を実行してください。
|
|
3032
3034
|
- 別の `<wcs-state>` がすでにバインドしているルートの上の、2 つ目のルート `<wcs-state>`(`#47`)。state ツリーはルートごとに 1 本です。部分木は `mount=` で接ぎ木してください。
|
|
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.4/dist/split/core.js",
|
|
196
|
+
"@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.4/dist/split/features/temporal.js",
|
|
197
|
+
"@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.0.0-rc.4/dist/split/features/scopes.js"
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
</script>
|
|
@@ -227,21 +227,21 @@ The file names under `dist/split/chunks/` carry a content hash: if you list chun
|
|
|
227
227
|
|
|
228
228
|
| Entry | What it adds |
|
|
229
229
|
|---|---|
|
|
230
|
-
| `@wcstack/state/core` | The binding engine: `data-wcs`, `{{ }}` and comment bindings, `for` / `if`, path getters, events, `$command` / `$on`, the
|
|
230
|
+
| `@wcstack/state/core` | The binding engine: `data-wcs`, `{{ }}` and comment bindings, `for` / `if`, path getters, events, `$command` / `$on`, the 10 condition filters (`eq` `ne` `not` `lt` `le` `gt` `ge` `truthy` `falsy` `boolean`), `bootstrapState`, `installFeatures` |
|
|
231
231
|
| `@wcstack/state/features/temporal` | `$watch`, `$stream` |
|
|
232
232
|
| `@wcstack/state/features/list-keys` | `$listKeys` |
|
|
233
233
|
| `@wcstack/state/features/scopes` | `bind-component`, `mount=` volumes, overlay exports, DCC (`data-wc-definition`) |
|
|
234
234
|
| `@wcstack/state/features/recursion` | `$recursion` and `**` paths |
|
|
235
235
|
| `@wcstack/state/features/ssr` | `enable-ssr`: server rendering and hydration |
|
|
236
|
-
| `@wcstack/state/features/formats` | The
|
|
236
|
+
| `@wcstack/state/features/formats` | The other 37 filters: arithmetic, conversion and defaults (`add`, `int`, `coalesce`, …) and formatting (`upper`, `date`, `round`, `truncate`, …) |
|
|
237
237
|
| `@wcstack/state/features/devtools` | The DevTools hook protocol source |
|
|
238
|
-
| `@wcstack/state/features/diagnostics` | The sentences of error messages, with the did-you-mean and the pointer to lint, and the development-time warnings (a bound or `$watch` path that does not resolve, `wcs/default-getter-mismatch`). Without it a message is its
|
|
238
|
+
| `@wcstack/state/features/diagnostics` | The sentences of error messages, with the did-you-mean and the pointer to lint, and the development-time warnings (a bound or `$watch` path that does not resolve, `wcs/default-getter-mismatch`). Without it a message is its number and its values ([Error messages](#error-messages-and-numbers)), those warnings are not printed, and the 3.x names 4.0 removed (`$scan`, `$streams`, `$updatedCallback`, `$trackDependency`, `$untrackDependency`) are not detected |
|
|
239
239
|
| `@wcstack/state/define` | `defineState` and the types only — no runtime at all |
|
|
240
240
|
|
|
241
241
|
A declaration whose add-on is missing does not fail quietly: it throws
|
|
242
242
|
`[wcs/feature-not-installed] $watch needs the add-on @wcstack/state/features/temporal` when the state
|
|
243
243
|
is loaded (`mount=`, `bind-component` and `data-wc-definition` name `scopes` the same way, and
|
|
244
|
-
`enable-ssr` names `ssr`), and a
|
|
244
|
+
`enable-ssr` names `ssr`), and a filter of `formats` without it throws `[wcs/filter-unknown]`
|
|
245
245
|
naming the add-on when the bindings are planned. Installing is idempotent — an add-on installed
|
|
246
246
|
again is skipped — and every entry shares one core chunk: an add-on never carries a second copy of
|
|
247
247
|
the engine, and never a copy of another add-on either (a call across that seam goes through a
|
|
@@ -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.4/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
|
|
|
@@ -1677,7 +1677,7 @@ Handlers run where the DOM puts them. An outer `#direct` handler runs **before**
|
|
|
1677
1677
|
|
|
1678
1678
|
## Filters
|
|
1679
1679
|
|
|
1680
|
-
47 built-in filters are available for both input (DOM → state) and output (state → DOM) directions:
|
|
1680
|
+
47 built-in filters are available for both input (DOM → state) and output (state → DOM) directions: the 10 condition filters in the core (`eq`, `ne`, `not`, `lt`, `le`, `gt`, `ge`, `truthy`, `falsy`, `boolean`) and the other 37 in the `formats` add-on (arithmetic, number formatting, string, type conversion, date / time, defaults), which the full entries include. On `/core` without `formats`, a filter of `formats` fails when the bindings are planned, naming the add-on.
|
|
1681
1681
|
|
|
1682
1682
|
The names 3.x kept as aliases of the 3.2 renames (`inc`, `dec`, `fix`, `uc`, `lc`, `cap`, `rep`, `rev`, `pad`, `null`) and the `substr` filter do not exist: they fail like any unknown name, and the message names what to write instead (`"uc" was renamed "upper" in 3.2 and removed in 4.0 — write "upper".`; `substr(start, length)` is `slice(start, start + length)`).
|
|
1683
1683
|
|
|
@@ -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
|
|
|
@@ -2362,7 +2363,7 @@ Two rules cut most of the confusion:
|
|
|
2362
2363
|
|
|
2363
2364
|
## Streams (`$stream`)
|
|
2364
2365
|
|
|
2365
|
-
Command tokens and event tokens carry discrete interactions. **`$stream`** covers the remaining shape: a continuous flow. Declare an async producer (async iterable / async generator / `ReadableStream`) and the framework **folds it into a single reactive property** — each chunk goes through normal path assignment, so bindings, path getters, and `$renderedCallback` react exactly as if you had assigned the value yourself. When a state path read by the `args` function changes, the running producer is aborted and the source is restarted with the new arguments (switchMap-style dependency-driven restart). Streams start eagerly after `$connectedCallback` completes and are aborted when the element disconnects. `$stream` is the `temporal` add-on (in the full entries). The 3.x name `$streams` throws `[wcs/declaration-alias] $streams was removed: write $stream.` (`#1601`).
|
|
2366
|
+
Command tokens and event tokens carry discrete interactions. **`$stream`** covers the remaining shape: a continuous flow. Declare an async producer (async iterable / async generator / `ReadableStream`) and the framework **folds it into a single reactive property** — each chunk goes through normal path assignment, so bindings, path getters, and `$renderedCallback` react exactly as if you had assigned the value yourself. When a state path read by the `args` function changes, the running producer is aborted and the source is restarted with the new arguments (switchMap-style dependency-driven restart). Streams start eagerly after `$connectedCallback` completes and are aborted when the element disconnects. `$stream` is the `temporal` add-on (in the full entries). The 3.x name `$streams` throws `[wcs/declaration-alias] $streams was removed: write $stream.` (`#1601`) where the `diagnostics` add-on is installed (`@wcstack/state`, `/auto`); without it the declaration is ignored.
|
|
2366
2367
|
|
|
2367
2368
|
`$renderedCallback` remains binding-driven: a stream declaration alone is not a headless subscription. Its path appears in the callback only when a live DOM binding for that value/status/error is actually applied. To react to a stream's value without rendering it, declare [`$watch`](#watch-watch) on that path; see the [stream reference](docs/streams.md) for the observation contract.
|
|
2368
2369
|
|
|
@@ -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
|
|
|
@@ -2763,7 +2764,7 @@ All bindings work inside `<svg>` elements. Use `attr.*` for SVG attributes:
|
|
|
2763
2764
|
|
|
2764
2765
|
State objects can define `$connectedCallback`, `$disconnectedCallback`, `$renderedCallback`, and `$errorCallback` for initialization, cleanup, update, and binding-failure handling.
|
|
2765
2766
|
|
|
2766
|
-
> `$renderedCallback` receives updates of **applied bindings**, not every state update (use `$watch` for those). The 3.x name `$updatedCallback` throws `[wcs/declaration-alias] $updatedCallback was removed: write $renderedCallback.` (`#1601`).
|
|
2767
|
+
> `$renderedCallback` receives updates of **applied bindings**, not every state update (use `$watch` for those). The 3.x name `$updatedCallback` throws `[wcs/declaration-alias] $updatedCallback was removed: write $renderedCallback.` (`#1601`) where the `diagnostics` add-on is installed (`@wcstack/state`, `/auto`); without it the declaration is ignored.
|
|
2767
2768
|
|
|
2768
2769
|
```html
|
|
2769
2770
|
<wcs-state>
|
|
@@ -2845,16 +2846,16 @@ Only a batch that actually has bindings to apply is handed to the tag, so a writ
|
|
|
2845
2846
|
|
|
2846
2847
|
### Error messages and numbers
|
|
2847
2848
|
|
|
2848
|
-
Every message starts with `[@wcstack/state]`.
|
|
2849
|
+
Every message starts with `[@wcstack/state]`. Most messages have a number (`#501`), and a message about something `@wcstack/lint` and the VS Code extension also check has the same code (`[wcs/filter-unknown]`). The hundreds of a number name its code — 1xx `binding-syntax`, 2xx `template-syntax`, 3xx `binding-path-missing`, 4xx `binding-type-expectation`, 5xx `filter-unknown`, 6xx `filter-arity`, 7xx `getter-cycle`, 8xx `getter-depth-exceeded`, 9xx `index-arity`, 10xx `index-param-range`, 11xx `recursion-unsupported`, 12xx `token-misconfigured`, 13xx `token-undeclared`, 14xx `wildcard-rank`, 15xx `spread-no-bindable`, 16xx `declaration-alias`, 17xx `name-alias`; 1–99 carry no code. Numbers are only ever added, so a number keeps its meaning across versions.
|
|
2849
2850
|
|
|
2850
|
-
The sentence of a numbered message — with the nearest name (did-you-mean, by the same rule as the lint), how to fix it, and a pointer to the lint where the lint detects the case —
|
|
2851
|
+
The code and the sentence of a numbered message — with the nearest name (did-you-mean, by the same rule as the lint), how to fix it, and a pointer to the lint where the lint detects the case — come from the `diagnostics` add-on, which `@wcstack/state` and `/auto` include. On a `/core` page without it, a message is its number and the values it would show:
|
|
2851
2852
|
|
|
2852
2853
|
```
|
|
2853
2854
|
[@wcstack/state] [wcs/filter-unknown] filter not found: uc. "uc" was renamed "upper" in 3.2 and removed in 4.0 — write "upper". Validate statically: npx @wcstack/lint <file>.
|
|
2854
|
-
[@wcstack/state]
|
|
2855
|
+
[@wcstack/state] #501 "uc"
|
|
2855
2856
|
```
|
|
2856
2857
|
|
|
2857
|
-
The first line is what `@wcstack/state` and `/auto` print, the second what `/core` without `features/diagnostics` prints. Install `diagnostics` while developing on `/core
|
|
2858
|
+
The first line is what `@wcstack/state` and `/auto` print, the second what `/core` without `features/diagnostics` prints. Install `diagnostics` while developing on `/core`: it also detects the 3.x names 4.0 removed (#1, #1601, #1701), which a page without it ignores (a declaration under one does nothing, reading one gives `undefined`). The barriers a page without add-ons meets on purpose (`[wcs/feature-not-installed]`, a filter of `formats` without it) are full sentences in the core.
|
|
2858
2859
|
|
|
2859
2860
|
<details>
|
|
2860
2861
|
<summary>The numbered messages</summary>
|
|
@@ -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:".` |
|
|
@@ -3037,13 +3039,13 @@ The header (`#49`) names `mount=`, `bind-component=`, `state=` and `src=` when t
|
|
|
3037
3039
|
What fails initialization:
|
|
3038
3040
|
|
|
3039
3041
|
- A source that cannot be read ([State Initialization](#state-initialization)).
|
|
3040
|
-
- An invalid declaration: a removed name (`$scan`, `$streams`, `$updatedCallback`), a `$behavior` / `$features` / `$commandTokens` / `$eventTokens` / `$on` of the wrong shape, a declaration whose add-on is not installed (`[wcs/feature-not-installed]`).
|
|
3042
|
+
- An invalid declaration: a removed name (`$scan`, `$streams`, `$updatedCallback`; with the `diagnostics` add-on), a `$behavior` / `$features` / `$commandTokens` / `$eventTokens` / `$on` of the wrong shape, a declaration whose add-on is not installed (`[wcs/feature-not-installed]`).
|
|
3041
3043
|
- A markup error found while the page is bound: a syntax error, an unknown filter or a wrong argument count, a wildcard-rank error, `for:` with filters, `outerHTML:` in a template, a wc-bindable misconfiguration on an element whose class is already defined. The walk stops at the error: what comes before it in document order is bound, what comes after it is not. Run `npx @wcstack/lint` before you deploy.
|
|
3042
3044
|
- A second root `<wcs-state>` on a root that another one already binds (`#47`). There is one state tree per root; graft a subtree with `mount=`.
|
|
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
|
|