@wcstack/state 3.2.0 → 3.3.0
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 +106 -76
- package/README.md +107 -77
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +45 -8
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/manifest.d.ts +28 -5
- package/dist/manifest.esm.js +253 -246
- package/dist/parser.d.ts +16 -2
- package/dist/parser.esm.js +223 -50
- package/dist/split/chunks/binder.js +1 -1
- package/dist/split/chunks/binder.js.map +1 -1
- package/dist/split/chunks/checkDependency.js +1 -1
- package/dist/split/chunks/declarationAliases.js +1 -1
- package/dist/split/chunks/declarationAliases.js.map +1 -1
- package/dist/split/chunks/declarationHooks.js +2 -0
- package/dist/split/chunks/declarationHooks.js.map +1 -0
- package/dist/split/chunks/featureBridge.js +2 -0
- package/dist/split/chunks/featureBridge.js.map +1 -0
- package/dist/split/chunks/filterRegistry.js +1 -1
- package/dist/split/chunks/filterRegistry.js.map +1 -1
- package/dist/split/chunks/getAllPropertyDescriptors.js +1 -1
- package/dist/split/chunks/getAllPropertyDescriptors.js.map +1 -1
- package/dist/split/chunks/lifecycleHooks.js +1 -1
- package/dist/split/chunks/pathDiagnostics.js +1 -1
- package/dist/split/chunks/pathDiagnostics.js.map +1 -1
- package/dist/split/chunks/registerComponents.js +1 -1
- package/dist/split/chunks/registerComponents.js.map +1 -1
- package/dist/split/chunks/setAll.js +1 -1
- package/dist/split/chunks/setAll.js.map +1 -1
- package/dist/split/chunks/version.js +1 -1
- package/dist/split/core.d.ts +10 -2
- package/dist/split/core.js +1 -1
- package/dist/split/core.js.map +1 -1
- package/dist/split/features/devtools.js +1 -1
- package/dist/split/features/devtools.js.map +1 -1
- package/dist/split/features/diagnostics.js +1 -1
- package/dist/split/features/diagnostics.js.map +1 -1
- package/dist/split/features/formats.js +1 -1
- package/dist/split/features/formats.js.map +1 -1
- package/dist/split/features/recursion.js +1 -1
- package/dist/split/features/recursion.js.map +1 -1
- package/dist/split/features/scopes.js +1 -1
- package/dist/split/features/scopes.js.map +1 -1
- package/dist/split/features/ssr.js +1 -1
- package/dist/split/features/ssr.js.map +1 -1
- package/dist/split/features/temporal.js +1 -1
- package/dist/split/features/temporal.js.map +1 -1
- package/dist/wcs-manifest.json +36 -21
- package/package.json +1 -1
- package/dist/split/chunks/mount.js +0 -2
- package/dist/split/chunks/mount.js.map +0 -1
- package/dist/split/chunks/watchRuntime.js +0 -2
- package/dist/split/chunks/watchRuntime.js.map +0 -1
package/README.ja.md
CHANGED
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
|
|
89
89
|
- UIを作り直しても、状態のロジックに触れる必要がありません — ただしロジックが描画内容にぶら下がっていない範囲で。live binding は 3 つある[評価のきっかけ](#評価のきっかけdemand-root)の 1 つなので、表示専用のつもりの要素がページ唯一の購読になりえます。
|
|
90
90
|
- 状態のデータ構造をリファクタリングしても、パス文字列の更新だけで済みます。
|
|
91
|
-
- HTMLを読めば、すべてのバインディングを把握できます。HTMLに現れない依存(`$watch`・`$
|
|
91
|
+
- HTMLを読めば、すべてのバインディングを把握できます。HTMLに現れない依存(`$watch`・`$stream`・`$scan`)は、すべて state という 1 箇所に宣言されています。
|
|
92
92
|
|
|
93
93
|
このパスによる契約は、REST APIのURLと同じ発想です — 両者が合意するシンプルな文字列だけが存在し、そこに共有するコードはありません。これはJavaScriptの上に独自のテンプレート言語を発明するのではなく、HTML本来の宣言的な性質をフルに活かした結果として生まれた設計です。
|
|
94
94
|
|
|
@@ -132,12 +132,12 @@
|
|
|
132
132
|
| **集計と一括書き込み** | `$getAll` / `$setAll` / `$resolve` が `items.*.price` を横断して読み書きする。配列は作り直さない | [Proxy API](#proxy-api) |
|
|
133
133
|
| **再帰パス** | `$recursion` が木の形の繰り返し地点を宣言し、1 本の `**` getter が全深さを覆う | [再帰パス](#再帰パスrecursion) |
|
|
134
134
|
| **リアクティビティ** | ES Proxy がアドレス単位で読み取りを追跡・キャッシュし、依存順に無効化し、DOM 書き込みをマイクロタスクでまとめる | [状態の更新](#状態の更新) · [依存追跡の境界](#依存追跡の境界) |
|
|
135
|
-
| **getter が動く条件** | getter は遅延評価。評価需要は live binding・`$watch`・`$
|
|
135
|
+
| **getter が動く条件** | getter は遅延評価。評価需要は live binding・`$watch`・`$stream` の `args` の 3 箇所からしか生まれない | [評価のきっかけ](#評価のきっかけdemand-root) |
|
|
136
136
|
| **モジュール化** | `mount=` がモジュールを 1 本のツリーに接ぎ木し、`state: path` が部分木をコンポーネントにマウントする。個別対応付けは単一キーを繋ぎ、マウントされたコンポーネントの getter はマウント点で公開される | [ボリューム](#追加の状態をマウントするmount) · [丸ごとマウント](#丸ごとマウントstate-path) |
|
|
137
137
|
| **コンポーネント** | 排他的な 2 方式 — JavaScript クラス+`bind-component` か、HTML だけの DCC か | [機構の選び方](#コンポーネント機構の選び方) |
|
|
138
138
|
| **他要素との配線** | wc-bindable プロトコル、spread(`...: obj`)、`#init=` / `#sync=` の authority、プロパティ→属性ミラー | [バインディング authority](#バインディング-authority-init--sync) · [Spread](#spread-バインディング) · [Inputs](#inputs-と属性ミラー) |
|
|
139
139
|
| **トークン** | command token が state から要素のメソッドを呼び、event token が要素のイベントを state へ戻す | [Command token](#command-tokenメソッドバインディング) · [Event token](#event-tokenイベントバインディング) |
|
|
140
|
-
| **時間** | `$
|
|
140
|
+
| **時間** | `$stream` が非同期ソースを fold し、`$watch` が headless に反応し、`$scan` が両者を越えて残る累積値を持つ | [時間を扱う機構の選び方](#時間を扱う機構の選び方) |
|
|
141
141
|
| **初期化とライフサイクル** | state の供給は 6 通り。`$connectedCallback` 〜 `$stateReadyCallback`、`bootstrapState()` / `createState()` | [状態の初期化](#状態の初期化) · [ライフサイクルフック](#ライフサイクルフック) · [API リファレンス](#api-リファレンス) |
|
|
142
142
|
| **診断** | 解決しないパス・添字の本数・階数・getter の循環を報告する。失敗はそのバインディング 1 本に閉じ、値も DOM も巻き戻さない | [診断と失敗の扱い](#診断と失敗の扱い) |
|
|
143
143
|
| **配布** | ランタイム依存ゼロ、ビルド不要、ESM、CDN の `/auto` 1 タグ。`unsafe-eval` 不要で Trusted Types 対応 | [インストール](#インストール) · [docs/csp.ja.md](../../docs/csp.ja.md) |
|
|
@@ -186,15 +186,15 @@
|
|
|
186
186
|
<script type="importmap">
|
|
187
187
|
{
|
|
188
188
|
"imports": {
|
|
189
|
-
"@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@3.
|
|
190
|
-
"@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@3.
|
|
191
|
-
"@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@3.
|
|
189
|
+
"@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@3.3.0/dist/split/core.js",
|
|
190
|
+
"@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@3.3.0/dist/split/features/temporal.js",
|
|
191
|
+
"@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@3.3.0/dist/split/features/scopes.js"
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
</script>
|
|
195
195
|
<script type="module">
|
|
196
196
|
import { bootstrapState, installFeatures } from '@wcstack/state/core';
|
|
197
|
-
import temporal from '@wcstack/state/features/temporal'; // $watch / $scan / $
|
|
197
|
+
import temporal from '@wcstack/state/features/temporal'; // $watch / $scan / $stream
|
|
198
198
|
import scopes from '@wcstack/state/features/scopes'; // bind-component・mount=・DCC
|
|
199
199
|
|
|
200
200
|
installFeatures([temporal, scopes]);
|
|
@@ -202,6 +202,11 @@
|
|
|
202
202
|
</script>
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
+
`installFeatures([...])` は上の例のとおり `bootstrapState()` の**前**に呼びます。機能は自分のタグ
|
|
206
|
+
(`<wcs-ssr>`)を登録することがあり、タグを定義するのは bootstrap だからです。逆順でもエラーには
|
|
207
|
+
なりません — 後から install した機能は、すでに bootstrap 済みのレジストリへ自分のタグを定義して
|
|
208
|
+
追いつきます — が、宣言どおりの順で呼べば、最初の要素が upgrade する前にすべてのタグが揃います。
|
|
209
|
+
|
|
205
210
|
分割の形は、パッケージのファイルそのものから読みます — 上の例のように jsDelivr の素の `/npm/` パスで
|
|
206
211
|
版を固定するか(素のパスは `exports` を読まないので、`dist/split/` 以下のファイル名を書く)、バンドラを
|
|
207
212
|
通します。**`esm.run` からは読まないでください**。`+esm` はエントリごとにサーバー側で再バンドルし、
|
|
@@ -213,20 +218,23 @@
|
|
|
213
218
|
| エントリ | 足されるもの |
|
|
214
219
|
|---|---|
|
|
215
220
|
| `@wcstack/state/core` | バインディングの本体: `data-wcs`・`for` / `if`・パス getter・フィルタ・イベント・`$command` / `$on`・`bootstrapState`・`installFeatures` |
|
|
216
|
-
| `@wcstack/state/features/temporal` | `$watch`・`$scan`・`$
|
|
221
|
+
| `@wcstack/state/features/temporal` | `$watch`・`$scan`・`$stream` |
|
|
217
222
|
| `@wcstack/state/features/scopes` | `bind-component`・`mount=` のボリューム・オーバーレイの公開 getter・DCC(`data-wc-definition`) |
|
|
218
223
|
| `@wcstack/state/features/recursion` | `$recursion` と `**` パス |
|
|
219
224
|
| `@wcstack/state/features/ssr` | `enable-ssr`: サーバー描画とハイドレーション |
|
|
220
|
-
| `@wcstack/state/features/formats` | 書式フィルタ群(`
|
|
225
|
+
| `@wcstack/state/features/formats` | 書式フィルタ群(`upper`・`date`・`round`・`truncate` …)。core が答えるのは `if` / `else` が要る `not` だけ |
|
|
221
226
|
| `@wcstack/state/features/devtools` | DevTools Hook Protocol への source 登録 |
|
|
222
227
|
| `@wcstack/state/features/diagnostics` | 開発時の警告: 束縛・`$watch`・`$scan` のパスが state 上で解決できないとき、did-you-mean 付きで知らせる。入れなければ静か(本番向け)。throw するエラーの文言はどちらでも変わらない |
|
|
223
228
|
| `@wcstack/state/define` | `defineState` と型だけ — ランタイムは 0 |
|
|
224
229
|
|
|
225
230
|
機能が入っていない宣言は黙って無視されません。state の定義時に
|
|
226
231
|
`[wcs/feature-not-installed] … install it with installFeatures([...]) from "@wcstack/state/features/…"`
|
|
227
|
-
で落ち、実装の無いフィルタは束縛計画の段で `[wcs/filter-unknown]`
|
|
232
|
+
で落ち、実装の無いフィルタは束縛計画の段で `[wcs/filter-unknown]` で落ちます。`$watch` を宣言した
|
|
233
|
+
ボリューム(`<wcs-state mount=…>`)も接ぎ木の時点で同じ `[wcs/feature-not-installed]` で落ちます —
|
|
234
|
+
ボリュームの state は要素自身の宣言の門を通らないためです。install は冪等で、
|
|
228
235
|
どのエントリも core のチャンクを 1 つだけ共有します(機能側がエンジンの 2 つ目のコピーを抱える
|
|
229
|
-
|
|
236
|
+
ことも、**別の機能**のコピーを抱えることもありません — 機能をまたぐ呼び出しは、持ち主の機能が
|
|
237
|
+
install 時に埋める受け口を通ります)。
|
|
230
238
|
|
|
231
239
|
## 基本的な使い方
|
|
232
240
|
|
|
@@ -333,7 +341,7 @@
|
|
|
333
341
|
<div data-wcs="textContent: cart.total"></div>
|
|
334
342
|
```
|
|
335
343
|
|
|
336
|
-
ボリュームは getter・`$watch`・`$listKeys`・`$
|
|
344
|
+
ボリュームは getter・`$watch`・`$listKeys`・`$renderedCallback`・`$connectedCallback`/`$disconnectedCallback` を宣言できます(すべてマウントパス相対)。`$errorCallback` はルート専用です(バインディングの失敗はツリーの所有者へ 1 回だけ報告されます)。読み込み順は自由です(ルートより先に接続されたボリュームは、ルートの登録時に接ぎ木されます)。ルートの `<wcs-state>` が初期化に失敗した場合、その時点で待機していたボリュームは永久に待たずに自分の報告を出して決着します。その報告が終点です —— 孤児として報告されたボリュームは後から自分で接ぎ木し直さないので、あとから修正版のルートを接続しても復帰しません。接ぎ木しないまま決着したボリューム(孤児・ロード失敗・接ぎ木失敗)はマウントの枠を返します。ロード中やルート待ちのあいだに外れたボリュームも枠を返します。そうしたボリュームは、同じ root へ付け直したときはその場で、それ以外は接ぎ木の直前に枠を取り直し、枠が空いていれば従来どおり(外れたままでも)接ぎ木します。外れている間に別のボリュームが枠を取っていた場合は、それを報告して接ぎ木しません。ボリュームの `$connectedCallback` が同期で投げた場合も非同期の失敗と同じく報告に留まり、接ぎ木は済んだものとして扱います。ページを読み直さずに復旧するには、壊れたルートと孤児のボリュームを取り除いて新しい要素を追加してください。接ぎ木済みのボリュームは、外してもデータがツリーに残るので枠を握ったままです。マウントパスは静的パスのみです(`*`・`$`・`#`・`@` は不可)。初期化後に `mount` 属性を変更することはできません — 変更は console 警告付きで無視されます。要素を取り除き、望むパスで新しい要素を追加してください。
|
|
337
345
|
|
|
338
346
|
**ルートのパスをボリュームへ注入する(3.1)。** ボリュームのコードが自分の外のパスを読むには、ボリューム要素の `data-wcs` にコンポーネントの部分マウントと同じ形で書きます:
|
|
339
347
|
|
|
@@ -341,7 +349,7 @@
|
|
|
341
349
|
<wcs-state mount="cart" src="./cart.js" data-wcs="state.taxRate: settings.taxRate"></wcs-state>
|
|
342
350
|
```
|
|
343
351
|
|
|
344
|
-
`cart.js` の中(getter・メソッド・`$watch`・`$listKeys`・ライフサイクル)では `this.taxRate` がルートの `settings.taxRate` を読み書きし、依存も張られるので、`settings.taxRate` が変われば `get total()` も再評価されます。`$watch: { taxRate() {…} }` は `settings.taxRate` の変化で発火し、`$
|
|
352
|
+
`cart.js` の中(getter・メソッド・`$watch`・`$listKeys`・ライフサイクル)では `this.taxRate` がルートの `settings.taxRate` を読み書きし、依存も張られるので、`settings.taxRate` が変われば `get total()` も再評価されます。`$watch: { taxRate() {…} }` は `settings.taxRate` の変化で発火し、`$renderedCallback` はその更新を内側の名前 `taxRate` で受けます。注入はボリュームのコードの中の名前です — ページは今までどおり `settings.taxRate` を読み、`cart.taxRate` はツリーにありません。
|
|
345
353
|
|
|
346
354
|
- **1 キーずつ**: 左辺は `state.<キー>` の 1 段です(`state: …` でボリューム全体を動かすことはできません — `mount` を変えてください)。
|
|
347
355
|
- **注入が自前のキーに勝つ**: ボリュームが同名のデータキー(`taxRate: 0`)を宣言していても、その値は接ぎ木されません。同名の getter・setter・メソッドはどちらが `this.taxRate` か曖昧なので、そのボリュームは接ぎ木せずにエラーを報告します。
|
|
@@ -354,8 +362,8 @@
|
|
|
354
362
|
|---|---|---|---|
|
|
355
363
|
| データキー・getter・setter・メソッド | 動く | 動く(`p` 相対) | 動く — 自前のキーはホストがマップしない限り私有、getter は公開される |
|
|
356
364
|
| `$connectedCallback` / `$disconnectedCallback` | 動く | 動く(`p` 相対) | 動く |
|
|
357
|
-
| `$watch`・`$listKeys`・`$
|
|
358
|
-
| `$
|
|
365
|
+
| `$watch`・`$listKeys`・`$renderedCallback` | 動く | 動く(`p` 相対) | 動かない — `wcs/mount-dollar-declaration` を 1 回警告 |
|
|
366
|
+
| `$stream`・`$scan`・`$recursion`・`**` の getter | 動く | 接ぎ木の前にエラーで拒否 | 動かない — 警告 |
|
|
359
367
|
| `$commandTokens`・`$eventTokens`・`$on` | 動く | 動かない — 警告 | 動かない — 警告 |
|
|
360
368
|
| `$errorCallback` | 動く | 動かない — 警告(3.0 より前は無言) | 動かない — 警告(3.0 より前は無言) |
|
|
361
369
|
| 初期化の失敗 | 1 回報告し、`connectedCallbackPromise` を reject | 接ぎ木せずに決着して枠を返す。`connectedCallbackPromise` は resolve(`scopes` 機能が無いときは reject) | 1 回報告し、コンポーネントの `connectedCallbackPromise` を reject |
|
|
@@ -652,7 +660,9 @@ export default {
|
|
|
652
660
|
<wcs-fetch data-wcs="...: usersFetch; status: alternateStatus"></wcs-fetch>
|
|
653
661
|
```
|
|
654
662
|
|
|
655
|
-
**`undefined` は「無意見」** — 展開された state パスが `undefined` に解決される場合(slot オブジェクトでその input を初期化していない場合など)、プロパティ書き込みは**スキップ**され、要素側の既定値がそのまま生きます。実際に使うパスだけ初期化すれば十分で、`<wcs-fetch>` が `method` / `manual` / `body` を宣言していても `usersFetch: { value: null, loading: false }` だけで動きます。明示的にクリアしたい場合は `null` を代入してください(`null` は常に書き込まれます)。**表示の表面は別です(3.0):** `textContent` / `innerText` / `innerHTML`・mustache のテキスト・`attr.*`・`style.*` には生かすべき要素側の既定値が無いので、`undefined` も `null` も「値が無い」—
|
|
663
|
+
**`undefined` は「無意見」** — 展開された state パスが `undefined` に解決される場合(slot オブジェクトでその input を初期化していない場合など)、プロパティ書き込みは**スキップ**され、要素側の既定値がそのまま生きます。実際に使うパスだけ初期化すれば十分で、`<wcs-fetch>` が `method` / `manual` / `body` を宣言していても `usersFetch: { value: null, loading: false }` だけで動きます。明示的にクリアしたい場合は `null` を代入してください(`null` は常に書き込まれます)。**表示の表面は別です(3.0):** `textContent` / `innerText` / `innerHTML`・mustache のテキスト・`attr.*`・`style.*`・`class.*` には生かすべき要素側の既定値が無いので、`undefined` も `null` も「値が無い」— テキストは空になり、属性やスタイルは削除され、クラスは外れます。3.0 より前は `textContent:` も `undefined` をスキップしていたため使い回したリストの行に前の行の文字が残り、属性には文字列 `"undefined"` / `"null"` が入っていました。`class.*` だけは範囲が狭く、「値が無い」扱いになるのは `undefined` と `null` だけです — **それ以外の非 boolean は今も throw します**。クラスの束縛は真偽の切り替えなので、truthy な文字列は意図というより書き間違いである方がずっと多いためです。「truthy として扱う」つもりなら `class.on: flag|truthy` と書いてください。このスキップは spread に限らずすべてのプロパティバインディングに適用され、`config.debug` 時はスキップごとに `console.debug` でログが出ます。
|
|
664
|
+
|
|
665
|
+
**フィルタを挟んでも契約は保たれます。** 空値の判定はフィルタ**後**の値に対して行われるため、`String(value)` で結果を組み立てるフィルタは値が無いときに `"undefined"` という**文字**を作ってしまい、`attr.title: x|trim` が `title="undefined"` を書いていました(フィルタの無い `attr.title: x` は属性を消します)。書式の族(`upper` / `lower` / `capitalize` / `trim` / `slice` / `substr` / `padStart` / `padEnd` / `repeat` / `reverse` / `truncate` / `unit`)は `undefined` / `null` を素通しするようになり、適用側の「テキストは空・属性は削除」がそのまま効きます。次の 3 群は**意図的に素通ししません**: 空値そのものが入力として意味を持つフィルタ(`defaults` / `coalesce` / `nullIfEmpty` / `boolean` / `truthy` / `falsy` / `not` / `eq` / `ne`)、変換が仕事のフィルタ(`int` / `float` / `number` / `string` — `undefined|string` は今も `"undefined"`)、値の型を検査するフィルタ(数値・日付・配列の族。`toFixed` / `date` / `join` など)。最後の群は throw しますが、それは束縛 1 本に閉じ込められ `$errorCallback` に報告されます。値が無いことが正当にありうるなら前に `coalesce` を置いてください(`price|coalesce(0)|toFixed(2)`)。
|
|
656
666
|
|
|
657
667
|
**制約事項**:
|
|
658
668
|
|
|
@@ -726,7 +736,7 @@ this.items = await (await fetch("/api/items")).json();
|
|
|
726
736
|
|---|---|---|
|
|
727
737
|
| `.name` | `users.*.name` | 現在の要素のプロパティ |
|
|
728
738
|
| `.` | `users.*` | 現在の要素そのもの |
|
|
729
|
-
| `.name\|
|
|
739
|
+
| `.name\|upper` | `users.*.name\|upper` | フィルタは保持される |
|
|
730
740
|
|
|
731
741
|
プリミティブ配列では、`.` が要素の値を直接参照します:
|
|
732
742
|
|
|
@@ -1059,9 +1069,9 @@ get total() { return this.price * exchangeRate; } // モジュール変
|
|
|
1059
1069
|
|
|
1060
1070
|
| API | 用途 |
|
|
1061
1071
|
|---|---|
|
|
1062
|
-
| `this.$
|
|
1072
|
+
| `this.$dependOn(path)` | 依存を明示的に追加し、そのパスの変更でこの getter を dirty にする |
|
|
1063
1073
|
| `this.$postUpdate(path)` | 追跡外の入力が変わったことを getter の外から通知する |
|
|
1064
|
-
| `this.$
|
|
1074
|
+
| `this.$untracked(fn)` | 依存として登録せずにパスを読む(上の対称) |
|
|
1065
1075
|
| `this.$eq(path, key)` / `$eqPath(path, keyPath)` / `$eqIndex(path)` | 鍵付き購読: 「`path` はこの行の鍵に等しいか」をパターン依存なしで答える([鍵付き選択](#鍵付き選択eq--eqpath--eqindex)) |
|
|
1066
1076
|
|
|
1067
1077
|
```javascript
|
|
@@ -1085,9 +1095,9 @@ getter の例外は握り潰されません。評価された場所(バイン
|
|
|
1085
1095
|
| **setter の中の読み取りは追跡しない。** setter は命令的な代入であって派生ではないので、その中で読んだものは何の依存にもならない | 何を書くかを `this.a` を読んで決める setter は、`a` が変わっても再実行されない。再実行されるのは getter だけ |
|
|
1086
1096
|
| **同値ガードはプリミティブにだけ効く。** 現在値と `Object.is` で等しいプリミティブの書き込みはキューに入る前に落とされる。オブジェクト・配列の書き込みは同じ参照でも必ず通る | 同じ文字列を再代入しても何も起きない。同じオブジェクトを再代入するとバインディングと `$watch` が再発火する(`config.sameValueGuard`。`semantics: "event"` のプロパティはどちらにせよ対象外) |
|
|
1087
1097
|
|
|
1088
|
-
規則 1 は静的解析で捕まえられる唯一の規則です。getter が `this.form.name` を読んでいて、ドキュメントのどこかで `form.name` を書いている(`value:` バインド・spread・`this["form.name"] = …`)と、`wcs-validate` と VS Code 拡張が `wcs/getter-untracked-read` を報告します。ルートを丸ごと置換するだけの設計(router の params・`$
|
|
1098
|
+
規則 1 は静的解析で捕まえられる唯一の規則です。getter が `this.form.name` を読んでいて、ドキュメントのどこかで `form.name` を書いている(`value:` バインド・spread・`this["form.name"] = …`)と、`wcs-validate` と VS Code 拡張が `wcs/getter-untracked-read` を報告します。ルートを丸ごと置換するだけの設計(router の params・`$stream` の fold)には出ません。
|
|
1089
1099
|
|
|
1090
|
-
`$
|
|
1100
|
+
`$untracked(fn)` は setter の規則を getter に意図的に適用するもので、`fn` の中の読み取りは追跡されません。`$dependOn(path)` は最初の規則に対する逃げ道です。
|
|
1091
1101
|
|
|
1092
1102
|
### 鍵付き選択(`$eq` / `$eqPath` / `$eqIndex`)
|
|
1093
1103
|
|
|
@@ -1120,6 +1130,7 @@ export default {
|
|
|
1120
1130
|
- **`path` より上のオブジェクトへの書き込み** — `$eq("sel.id", …)` に対する `this.sel = { id: 2 }`: 同じく 2 行。旧いオブジェクトの下と新しいオブジェクトの下で `path` が持つ値を鍵にします。
|
|
1121
1131
|
- **`path` が getter か getter の配下**(`get current()` に対する `$eq("current.id", …)`): 値が書き込みなしに変わるので、依存を張る普通の読み取りに戻ります。選択は正しく保たれますが、変化のたびに全行が再評価されます(鍵付きの形を使わないのと同じ)。2 行で済ませるには、`path` を書き込まれる state(`selectedId`)に向けてください。`@wcstack/devtools` の State ペインは、path ごとの購読の数を **Keyed selection** に出し、この形に落ちた path には `tracked` バッジを付けます(3.0)。
|
|
1122
1132
|
- **型変換はしません:** `"2"` は id `2` に一致しません。`<input>` や `<select>` は文字列を書くので、入口で変換する(`value|number: selectedId`)か、id を文字列で持ってください。
|
|
1133
|
+
- **スコープの中では `path` は相対**です — パスを取る他の `$` API と同じです。マウントされたコンポーネント(`bind-component`)とボリューム(`<wcs-state mount="cart">`)では、`$eq("selectedId", …)`・`$eqPath` の 2 つのパス・`$eqIndex`・`$dependOn` はどれもそのスコープ(`cart.selectedId`)に解決され、ルートには向きません。`$untracked` はコールバックを取るので翻訳の対象外です。
|
|
1123
1134
|
|
|
1124
1135
|
### ループインデックス変数(`$1`, `$2`, ...)
|
|
1125
1136
|
|
|
@@ -1151,7 +1162,7 @@ export default {
|
|
|
1151
1162
|
|
|
1152
1163
|
```html
|
|
1153
1164
|
<template data-wcs="for: items">
|
|
1154
|
-
<td>{{ $1|
|
|
1165
|
+
<td>{{ $1|add(1) }}</td> <!-- 1始まりの行番号 -->
|
|
1155
1166
|
</template>
|
|
1156
1167
|
```
|
|
1157
1168
|
|
|
@@ -1337,7 +1348,7 @@ k=2 nodes.*.children.*.children.*
|
|
|
1337
1348
|
| `$getAll(path, [])`(**明示**) | **全深さの合併** —— 深さ優先・行きがけ・添字昇順 |
|
|
1338
1349
|
| `$getAll(path, [i, …])` | 拒否。接頭辞ではどの深さの話か言えない(`wcs/recursion-getall-form`) |
|
|
1339
1350
|
| `$setAll(path, [], value)` | 全深さへのブロードキャスト(合併と同じ走査・同じ順序) |
|
|
1340
|
-
| `$resolve` / `$postUpdate` / `$
|
|
1351
|
+
| `$resolve` / `$postUpdate` / `$dependOn` / `$watch` のキー / `$listKeys` のキー / markup の `data-wcs` / 直接代入 | 拒否(`wcs/recursion-unsupported`) |
|
|
1341
1352
|
|
|
1342
1353
|
束縛形は束縛先の深さを必要とするので、**再帰 getter の中**でしか解決できません(アンカー配下の普通の行 getter や、その行に紐づくイベントハンドラも同じく実体の `ListIndex` を持つので使えます)。トップレベルで `this["nodes.**.value"]` を読むと `wcs/recursion-context` になります —— どのノードのつもりだったかを黙って推測することはありません。深さは行の添字と同じく**最も内側の評価フレームだけ**から読みます。再帰 getter が呼ぶ普通の getter(`get "nodes.**.x"() { return this.helper }` と `get helper() { return this["nodes.**.value"] }`)は自分の行を持たないので、これも `wcs/recursion-context` になります —— `**` は再帰 getter の側で読み、値を渡してください。合併形は深さを要求しないので、トップレベルの getter でも普通の行 getter でもメソッドでも読めます。
|
|
1343
1354
|
|
|
@@ -1406,8 +1417,8 @@ this.$setAll("nodes.**.selected", [], false); // 全深さの全ノード
|
|
|
1406
1417
|
| 添字の省略 | 書き込み API は文脈を取らないので束縛する深さが無い —— `[]` を渡す |
|
|
1407
1418
|
| mapper 関数 | `(current, ...indexes)` の添字の本数が深さごとに変わる |
|
|
1408
1419
|
| `{ spread: true }` | 1 次元配列を木へ配るには作者が走査順を知っている必要があり、契約として使えない |
|
|
1409
|
-
| `nodes.**` / `nodes.**.children` / `nodes.**.children.*` / `nodes.**.children.length` —— 反復サブパスが多段(`branch.children.*`)なら、子リストへ至る途中の `nodes.**.branch`
|
|
1410
|
-
| `nodes.**.total`、およびその値の内側を指すパス | 再帰 getter に setter は無い。導出元を書く(`wcs/recursion-readonly`) |
|
|
1420
|
+
| `nodes.**` / `nodes.**.children` / `nodes.**.children.*` / `nodes.**.children.length` —— 反復サブパスが多段(`branch.children.*`)なら、子リストへ至る途中の `nodes.**.branch` も。添字綴りも同じ形に畳まれるので `nodes.**.children.0` は子ノード | 構造そのものへの書き込み(`length` への代入はリストを切り詰める)は、その書き込み自身が確定済みの子アドレスを壊す(`wcs/recursion-structural-write`) |
|
|
1421
|
+
| `nodes.**.total`、およびその値の内側を指すパス —— 添字綴りも同じ形に畳まれるので `nodes.**.children.0.total` も getter | 再帰 getter に setter は無い。導出元を書く(`wcs/recursion-readonly`) |
|
|
1411
1422
|
|
|
1412
1423
|
読み取り専用の規則は `**` の綴りに依存しません。再帰 getter の具体的な展開形 —— `nodes.*.total` / `nodes.*.children.*.total` / … —— への書き込みも、固定本数の `$setAll` でも値付きの `$resolve(path, indexes, value)` でも直接代入でも、またその深さが実体化済みかどうかに関わらず、書き込みの入口で拒否します。この検査が無かったときは、未実体化の展開形が「無いキー」に見えてノードのオブジェクトに書き込まれ、代入値が getter のキャッシュ結果として固定されていました。
|
|
1413
1424
|
|
|
@@ -1463,7 +1474,7 @@ customElements.define("tree-node", class extends HTMLElement {
|
|
|
1463
1474
|
- 再帰 setter、接尾辞が構造そのものを名指す `**` getter(`get "nodes.**.children"()`)、`**` getter の展開形と同名の具体 getter、そして代入による `**` 経由の書き込み(`this["nodes.**.x"] = v`、`++` も含む)
|
|
1464
1475
|
- 再帰 `$setAll` の mapper・`{ spread: true }`・添字省略・非空の接頭辞・配列でない `indexes`。書き込み API には深さを束縛する評価文脈が無いので、`[]` は必須です
|
|
1465
1476
|
- 再帰 `$getAll` の非空の接頭辞・配列でない `indexes`。**添字省略は正当です** —— 再帰 getter の中では束縛形で、評価中の深さを読みます
|
|
1466
|
-
- `data-wcs` / `$watch` や `$listKeys` のキー / `$resolve` / `$postUpdate` / `$
|
|
1477
|
+
- `data-wcs` / `$watch` や `$listKeys` のキー / `$resolve` / `$postUpdate` / `$dependOn` への `**`
|
|
1467
1478
|
- ボリューム(`mount=`)やマウントされたコンポーネント(`bind-component`)の `$recursion` と `**` getter —— ルートの state に置きます
|
|
1468
1479
|
- 再帰 `<template>`、`$depth` 変数、公開の `maxDepth` オプション(3 つとも存在しません)
|
|
1469
1480
|
|
|
@@ -1509,7 +1520,7 @@ export default {
|
|
|
1509
1520
|
|---|---|---|
|
|
1510
1521
|
| `eq(value)` | 等しい | `count\|eq(0)` → `true/false` |
|
|
1511
1522
|
| `ne(value)` | 等しくない | `count\|ne(0)` |
|
|
1512
|
-
| `not` |
|
|
1523
|
+
| `not` | 真偽性を反転 — `0` / `""` / `null` / `undefined` はすべて `true`(3.x。以前は非 boolean で throw し、`else:` がどちらの枝も描かなかった) | `isActive\|not` |
|
|
1513
1524
|
| `lt(n)` | より小さい | `count\|lt(10)` |
|
|
1514
1525
|
| `le(n)` | 以下 | `count\|le(10)` |
|
|
1515
1526
|
| `gt(n)` | より大きい | `count\|gt(0)` |
|
|
@@ -1548,12 +1559,12 @@ export default {
|
|
|
1548
1559
|
| `capitalize`(`cap`) | 先頭大文字 | `name\|capitalize` |
|
|
1549
1560
|
| `trim` | 空白除去 | `text\|trim` |
|
|
1550
1561
|
| `slice(n)` | 文字列スライス | `text\|slice(5)` |
|
|
1551
|
-
| `substr(start, length)` |
|
|
1562
|
+
| `substr(start, length)` | 部分文字列(引数は 2 つとも必須) | `text\|substr(0,10)` |
|
|
1552
1563
|
| `padStart(n, char?)`(`pad`) | 先頭を埋める(既定 `0`) | `id\|padStart(5,0)` → `"00001"` |
|
|
1553
1564
|
| `padEnd(n, char?)` | 末尾を埋める(既定は空白。3.2) | `code\|padEnd(8)` |
|
|
1554
1565
|
| `repeat(n)`(`rep`) | 繰り返し | `text\|repeat(3)` |
|
|
1555
1566
|
| `reverse`(`rev`) | 反転 | `text\|reverse` |
|
|
1556
|
-
| `truncate(n, suffix?)` |
|
|
1567
|
+
| `truncate(n, suffix?)` | 切り詰めて省略記号を付加(既定は `…` — U+2026 の 1 文字) | `title\|truncate(20)` → `"…"` 付き |
|
|
1557
1568
|
| `join(sep?)` | 配列を連結(既定 `", "`) | `tags\|join` / `tags\|join(/)` |
|
|
1558
1569
|
|
|
1559
1570
|
### 型変換
|
|
@@ -1571,11 +1582,13 @@ export default {
|
|
|
1571
1582
|
|
|
1572
1583
|
| フィルタ | 説明 | 例 |
|
|
1573
1584
|
|---|---|---|
|
|
1574
|
-
| `date(loc?)` | 日付フォーマット | `
|
|
1575
|
-
| `time(loc?)` | 時刻フォーマット | `
|
|
1576
|
-
| `datetime(loc?)` | 日付 + 時刻 | `
|
|
1577
|
-
| `ymd(sep?)` | YYYY-MM-DD | `
|
|
1578
|
-
| `hms(sep?)` | HH:MM:SS | `
|
|
1585
|
+
| `date(loc?)` | 日付フォーマット | `createdAt\|date` / `createdAt\|date(ja-JP)` |
|
|
1586
|
+
| `time(loc?)` | 時刻フォーマット | `createdAt\|time` |
|
|
1587
|
+
| `datetime(loc?)` | 日付 + 時刻 | `createdAt\|datetime(en-US)` |
|
|
1588
|
+
| `ymd(sep?)` | YYYY-MM-DD(既定は `-`) | `createdAt\|ymd` / `createdAt\|ymd(/)` |
|
|
1589
|
+
| `hms(sep?)` | HH:MM:SS(既定は `:`) | `createdAt\|hms` / `createdAt\|hms(-)` |
|
|
1590
|
+
|
|
1591
|
+
**5 つとも `Date` を取ります**(タイムスタンプではありません)。他の型は「requires a date value」で拒否されます。JSON から読み込んだ state は数値か ISO 文字列を持つので、束縛ではなく読み出し側で変換してください: `get createdAt() { return new Date(this.createdAtMs); }`。`loc?` の既定は `config.locale` で、**適用のたびに**読み直されるため、束縛を組み立てた後に確定したロケールも反映されます(明示引数 `date(ja-JP)` は束縛式の一部なので組み立て時に固定されます)。
|
|
1579
1592
|
|
|
1580
1593
|
### 真偽値 / デフォルト
|
|
1581
1594
|
|
|
@@ -1596,7 +1609,15 @@ export default {
|
|
|
1596
1609
|
|
|
1597
1610
|
フィルタは束縛計画の段で解決されます。未知の名前は `[wcs/filter-unknown]`(did-you-mean 付き)で、3.0 からは受け付ける個数の外の引数も `[wcs/filter-arity]`(`join(a,b)`: "accepts at most 1 argument(s) (2 given)")で落ちます — lint と同じコード・同じ範囲です。引数は構造のままキャッシュされるので、`join('a,b')` と `join(a)` を取り違えません。
|
|
1598
1611
|
|
|
1599
|
-
**引数のリテラルは型を持ちます(3.0)。** 引用符の無い `true` / `false` / `null` / 数値はその値、引用符付きの引数は文字列です。比較フィルタは真偽値と `null` の比較にこれを使います: `done|eq(true)` は `true` に一致し(3.0 より前は文字列 `"true"` と比べていて一致しなかった)、`eq('true')` は一致せず、`eq(null)` は `null` に一致します。数値と文字列の比べ方は変わりません — 数値の値は数と、文字列の値は原文と比べるので、フォームの値 `"1"` は今も `eq(1)`
|
|
1612
|
+
**引数のリテラルは型を持ちます(3.0)。** 引用符の無い `true` / `false` / `null` / 数値はその値、引用符付きの引数は文字列です。比較フィルタは真偽値と `null` の比較にこれを使います: `done|eq(true)` は `true` に一致し(3.0 より前は文字列 `"true"` と比べていて一致しなかった)、`eq('true')` は一致せず、`eq(null)` は `null` に一致します。数値と文字列の比べ方は変わりません — 数値の値は数と、文字列の値は原文と比べるので、フォームの値 `"1"` は今も `eq(1)` に一致します。**数値**の値と型付きの `true` / `false` / `null` は単に「一致しない」です: `selectedId|eq(null)` は id が `null` の間は `true`、数値になれば `false` になります(3.2 まではこの組み合わせが**適用のたびに** `requires a number as option` で throw していたため、null と数値の両方を取りうるパスは値が数値になった瞬間にその束縛が壊れていました)。型の食い違いとして報告されるのは、引用符の無い非数値(数値の値に対する `eq(abc)`)だけです — `eq` は値の型を選ばないので、`status|eq(active)` は値が分かるまで判定できません。`defaults(v)` は型付きの値を返します: `defaults(0)` は `0`、`defaults('0')` は `"0"`、`defaults(null)` は `null`。
|
|
1613
|
+
|
|
1614
|
+
**引用符の規則。** 引用符(`'` / `"`)は「ここはリテラル」という宣言で、引数の文法は意図的に小さく保たれています:
|
|
1615
|
+
|
|
1616
|
+
- 引用符の中では `,` `;` `|` `:` はただの文字です。`join(', ')`・`join(': ')`・`join(';')` はそのまま通ります。
|
|
1617
|
+
- 空白のトリムは**引用符の外側だけ**です: `padStart(5, ' ')` は空白で埋め、`toFixed( 2 )` は `toFixed(2)` と同じです。
|
|
1618
|
+
- **エスケープ文字はありません。** 同じ種類の引用符をリテラルの中に入れることはできません — `'it\'s'` ではなく `"it's"` と書いてください(前者は未終端の引用符として `[wcs/binding-syntax]` で落ちます)。
|
|
1619
|
+
- 隣り合う並びは 1 つの引数に連結されます: `'a' 'b'` は 1 個の文字列 `a b`、`1'2'` は文字列 `12` です(引数のどこかに引用符があれば、その引数は文字列になります)。
|
|
1620
|
+
- 未終端の引用符は受理しません。末尾の空引数は落とされ(`filter()` は引数 0 個)、先頭・中間の空引数は位置を保ちます(`defaults(,)` は空文字 1 個を渡します)。
|
|
1600
1621
|
|
|
1601
1622
|
## Web Component バインディング
|
|
1602
1623
|
|
|
@@ -1719,6 +1740,11 @@ customElements.define("user-card", UserCard);
|
|
|
1719
1740
|
> 宣言しているコンポーネント(`state = { message: "" }` + `state.message: ...`)もホストの値を読み、
|
|
1720
1741
|
> 既定値は使われません(2.x では R1 によって自前のキーが私有になり、ホストの値を隠していました —
|
|
1721
1742
|
> `wcs/mount-own-key-shadow` の警告付き)。マップしていない自前のキーは今も R1 で私有です。
|
|
1743
|
+
> これは**1 段のエントリ**の話です: より深いエントリ(`state.a.b: outer.b`)は `a` の私有性に
|
|
1744
|
+
> 触らないので、コンポーネントが `a` を自分で宣言していれば `a` とその配下は私有のままで、
|
|
1745
|
+
> エントリは届きません。名前がコンポーネントの getter / setter / メソッドと一致するエントリも同じで、
|
|
1746
|
+
> コンポーネント自身の面が勝ちます。いずれの形も `wcs/mount-own-key-shadow` で 1 回報告されます —
|
|
1747
|
+
> どちらかの名前を変えてください。
|
|
1722
1748
|
>
|
|
1723
1749
|
> **マウントの `#ro` を尊重します(3.0):** `state#ro: user` や `state.title#ro: doc.title` では、
|
|
1724
1750
|
> コンポーネントはそのエントリを読めても書けません — `element.state.title = …`・メソッド内の
|
|
@@ -1785,7 +1811,7 @@ customElements.define("my-component", MyComponent);
|
|
|
1785
1811
|
- `bind-component` 付きの `<wcs-state>` はコンポーネント要素の**直下**(トップレベル)に配置すること
|
|
1786
1812
|
- 親要素は**カスタム要素**(ハイフンを含むタグ名)であること
|
|
1787
1813
|
- Light DOM コンポーネントはホストからの配線が必須(plain 形は v2 で廃止)
|
|
1788
|
-
- **マウントされた**スコープは宣言面を実行しない。`$watch`・`$
|
|
1814
|
+
- **マウントされた**スコープは宣言面を実行しない。`$watch`・`$stream`・`$scan` は一度だけ警告を出して無視され、`$recursion` / `**` getter は拒否される。ルート state に宣言すること(ボリューム `<wcs-state mount>` は `$watch` を持てるが、`$scan` と `$recursion` はルート専用)。配線されていない Shadow DOM の子は独立したツリーを持つため、いずれも宣言できる
|
|
1789
1815
|
|
|
1790
1816
|
### ループ内でのコンポーネント使用
|
|
1791
1817
|
|
|
@@ -1838,7 +1864,7 @@ this.shadowRoot.innerHTML = `
|
|
|
1838
1864
|
|
|
1839
1865
|
コンポーネントの中にさらにコンポーネントを置いて、**スコープを重ねる**こともできます。途中のコンポーネントが配列を素通しするだけで自分では `for` を回さなくても、正本スコープ起点の行フィールド書き込みは最下層の行まで届きます。
|
|
1840
1866
|
|
|
1841
|
-
コンポーネントの作者が自分の置かれる深さを意識する必要はありません。`$1` / イベントハンドラのインデックス / `$
|
|
1867
|
+
コンポーネントの作者が自分の置かれる深さを意識する必要はありません。`$1` / イベントハンドラのインデックス / `$renderedCallback` / `$getAll` はいずれも**自分のスコープ内の位置**を報告します。
|
|
1842
1868
|
|
|
1843
1869
|
## Command Token(メソッドバインディング)
|
|
1844
1870
|
|
|
@@ -1952,9 +1978,12 @@ interface CommandToken {
|
|
|
1952
1978
|
|
|
1953
1979
|
`emit` は各購読者の戻り値の配列を(subscribe 順で)返します。`Promise` を返すメソッドは `Promise.all(token.emit(...))` でラップしてすべてを待ち受けてください。
|
|
1954
1980
|
|
|
1981
|
+
**1 つの購読者が throw しても、残りへの配信は止まりません。** token はファンアウトの口で購読者どうしは互いを知らないため、`emit` は全員を呼びます。投げた購読者は `console.error`(token 名つき)で報告され、結果配列のその位置は `undefined` になり、残りの購読者には届きます。例外は emitter へは**届きません** — state のイベントハンドラと同じ規則で、発火経路はハンドラの完了を待たず、DOM イベント起点では投げ返す呼び出し元が存在しないためです。呼び出し側に知らせたいときは throw ではなく戻り値(または reject した `Promise`)で返してください。`Promise` を返す購読者の reject はここでは捕捉しません。そのための `Promise.all(token.emit(...))` です。
|
|
1982
|
+
|
|
1955
1983
|
### 購読のライフサイクル
|
|
1956
1984
|
|
|
1957
|
-
- 購読者は要素を `WeakRef`
|
|
1985
|
+
- 購読者は要素を `WeakRef` で保持するので、token の購読者セット**それ自体**は要素を生かし続けない
|
|
1986
|
+
- **注意(現在の実装):** 購読者のクロージャは `binding` も捕捉しており、`binding.node` はその同じ要素 — 上の `WeakRef` を打ち消す強参照です。実際には、取り外された要素は binding が解放されるまで残ります。つまり `WeakRef` が効くのは「binding は消えたが token に購読者が残っている」場合だけです。行や要素が消えた時点で購読者を解放するのが本筋の修正ですが、binding 機構に要素ライフサイクルのフックが無く、その追加とセットで入れるべき変更なので 3.x には入れていません。**今日頼れる仕組みは下の lazy purge**だと考えてください
|
|
1958
1987
|
- `emit` 時、WeakRef が回収済みか要素が接続されていない(`isConnected === false`)場合、購読は自動的に破棄される(lazy purge)
|
|
1959
1988
|
- 所有する `<wcs-state>` が disconnect されても token レジストリは保持されるので、ルート `<wcs-state>` を付け直した後(ホストを DOM 上で移動したときなど)も購読に命令が届く。切断中は state を作れないので、`$command` 経由の emit は起きない
|
|
1960
1989
|
|
|
@@ -1980,7 +2009,7 @@ command token は state コードから emit する必要はありません。DO
|
|
|
1980
2009
|
|
|
1981
2010
|
これは純粋な配線です。イベント端点を command token 端点に接続するだけで、間にロジックは入りません。`emit` の引数はハンドラ呼び出しとまったく同じく透過されます —— まず DOM の `Event`、続いて内包するリストインデックス —— なので購読者は `(event, ...listIndexes)` を受け取ります。購読者の中で必要なものをイベントから取り出してください(`event.target.value`、`event.detail` など)。
|
|
1982
2011
|
|
|
1983
|
-
- 右辺は `$command.<name>` であり、`<name>` は `$commandTokens` で宣言されていること。`CommandToken` に解決されないパス(例: typo
|
|
2012
|
+
- 右辺は `$command.<name>` であり、`<name>` は `$commandTokens` で宣言されていること。`CommandToken` に解決されないパス(例: typo)はイベント時に失敗する。DOM のイベントには投げ返せないので、伝播ではなくイベントごとに 1 回 `console.error` で報告される(reject した state ハンドラと同じ着地)。
|
|
1984
2013
|
- 修飾子はそのまま機能する: `onclick#prevent: $command.someToken` は emit の前に `preventDefault()` を呼ぶ(`#stop` も同様)。
|
|
1985
2014
|
- これは state が emit するのと同じ token を emit するので、`command.<method>: $command.someToken` で配線された要素の購読者は、誰がトリガを引いたかに関わらず受け取る。
|
|
1986
2015
|
|
|
@@ -2142,22 +2171,22 @@ event token は command token と同じ `Token` pub/sub プリミティブを共
|
|
|
2142
2171
|
| 宣言 | 宣言するもの | 値を持つか | 発火 | 主な用途 |
|
|
2143
2172
|
|---|---|---|---|---|
|
|
2144
2173
|
| [パス getter](#パス-getter算出プロパティ) | その値が現在の state から見て**何であるか** | 持たない(アドレス単位で再計算・キャッシュ) | 評価需要が読んだときに遅延評価 | 小計、分類、集計 |
|
|
2145
|
-
| [`$
|
|
2174
|
+
| [`$stream`](#streamstream) | 非同期の供給元と、**1 回の実行の中で** fold される値 | 持つ(出力は runtime の所有) | chunk ごと。`args` が変われば `initial` に戻して restart | フィード、ソケット、継続的な観測 |
|
|
2146
2175
|
| [`$watch`](#watchwatch) | 変更への反応 | 持たない | 変化したアドレスごとにバッチ 1 回、scan の書き込みの後 | 副作用、「条件が成立したとき」 |
|
|
2147
2176
|
| [`$scan`](#scanscan) | 時間をまたぐ累積値と、それを戻す条件 | 持つ(出力は runtime の所有) | 着地ごと(`from`)またはイベントごと(`on`) | ページ蓄積、履歴、件数 |
|
|
2148
2177
|
|
|
2149
2178
|
混乱のほとんどは、次の 2 点で解けます。
|
|
2150
2179
|
|
|
2151
|
-
- **`$
|
|
2152
|
-
- **`$
|
|
2180
|
+
- **`$renderedCallback` はこの表に入りません。** 適用されたバインディングを報告するものなので、そこに処理をぶら下げると描画内容に暗黙に依存します。[評価のきっかけ](#評価のきっかけdemand-root)を参照してください。
|
|
2181
|
+
- **`$stream` の fold は restart のたびに戻り、`$scan` は戻りません。** restart を越えて値を保ちたいとき、あるいは状態ではなく出来事を数えたいときは `$scan` の領分です。
|
|
2153
2182
|
|
|
2154
|
-
## Stream(`$
|
|
2183
|
+
## Stream(`$stream`)
|
|
2155
2184
|
|
|
2156
2185
|
> **3.2 から宣言キーの正式名は `$stream` です**(要件 B12 — `$watch` / `$scan` と単数で揃えた)。旧名 `$streams` は 3.x の間は同じに動き、4.0 で外れます。両方を宣言すると `[wcs/declaration-alias]` で落ちます。`$streamStatus` / `$streamError` の名前空間は変わりません。3.1 以前では `$streams` と書いてください。
|
|
2157
2186
|
|
|
2158
|
-
command token / event token が運ぶのは離散的なやり取りです。**`$
|
|
2187
|
+
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 されます。
|
|
2159
2188
|
|
|
2160
|
-
`$
|
|
2189
|
+
`$renderedCallback` は引き続き binding 駆動です。stream 宣言だけでは headless な購読にならず、その value/status/error の live DOM binding が実際に適用されたときだけ callback の path に現れます。描画せずに stream の値へ反応したい場合は、そのパスに [`$watch`](#watchwatch) を宣言してください。観測契約は [stream リファレンス](docs/streams.md) を参照してください。
|
|
2161
2190
|
|
|
2162
2191
|
```html
|
|
2163
2192
|
<wcs-state>
|
|
@@ -2165,7 +2194,7 @@ command token / event token が運ぶのは離散的なやり取りです。**`$
|
|
|
2165
2194
|
export default {
|
|
2166
2195
|
prompt: "",
|
|
2167
2196
|
|
|
2168
|
-
$
|
|
2197
|
+
$stream: {
|
|
2169
2198
|
// フル形: LLM トークンストリームを累積
|
|
2170
2199
|
tokens: {
|
|
2171
2200
|
args: (state) => state.prompt, // 依存はここでのみ捕捉される
|
|
@@ -2204,7 +2233,7 @@ error 時、プロパティは直前の fold 結果を保持し、エラーは `
|
|
|
2204
2233
|
**イベント API の橋渡し** —— 実際の source の多くはコールバック型(`EventSource`・`WebSocket`・DOM イベント)で、async iterable ではありません。標準の `ReadableStream` で包んでください: `start` で enqueue し、`cancel` でリソースを解放します。`AbortSignal` には一切触れません —— restart / 破棄時は runtime が reader を cancel し、parked read を強制解放して `cancel()` まで届けます:
|
|
2205
2234
|
|
|
2206
2235
|
```js
|
|
2207
|
-
$
|
|
2236
|
+
$stream: {
|
|
2208
2237
|
metrics: {
|
|
2209
2238
|
args: (state) => ({ host: state.host }),
|
|
2210
2239
|
source: ({ host }) => {
|
|
@@ -2230,6 +2259,7 @@ $streams: {
|
|
|
2230
2259
|
- **有界 fold** —— 需要は producer に逆流しません(backpressure は明示的に放棄)。無限 / 長寿命ストリームでは latest・count・last-N(`(acc, chunk) => [...acc.slice(-99), chunk]`)・ウィンドウ集計など有界な fold を使うこと。生の全チャンク累積は有限ストリーム限定。
|
|
2231
2260
|
- **`args` は同期** —— Promise を返すとエラー。`args` 内での wildcard 読みも拒否されます。
|
|
2232
2261
|
- **自己依存・相互サイクルの禁止** —— `args` が自 stream の値や status を読むとエラーになります。2 つの stream の相互サイクル(A の `args` が B の値を読み、B の `args` が A の値を読む)は検出されず無限 restart になるため組まないこと。一方向のチェイン(A の値を B の `args` が読む)は正当です。
|
|
2262
|
+
- **プロパティは runtime の所有物** —— エントリ名が state の getter / setter / メソッドと衝突すると、`$scan` の出力と同じく宣言時にエラーになります(検査が無いと、そのメソッドは起動時の `initial` リセットで無言に上書きされます)。
|
|
2233
2263
|
- **SSR では起動しない** —— サーバーでは宣言のパースとプロパティの実体化(`initial`)のみ行い、source は実行されません。クライアント側は通常どおり起動します。
|
|
2234
2264
|
|
|
2235
2265
|
完全な契約 —— ライフサイクルと所有権・restart セマンティクス・flush 粒度・スコープ外リスト —— は [docs/streams.ja.md](docs/streams.ja.md) を参照してください。
|
|
@@ -2244,9 +2274,9 @@ $streams: {
|
|
|
2244
2274
|
|---|---|---|
|
|
2245
2275
|
| **live DOM バインディング** | `data-wcs` / mustache / コメントバインディング | **する**(その要素が消えると需要も消える) |
|
|
2246
2276
|
| **`$watch` の宣言** | state 側 | しない(headless) |
|
|
2247
|
-
| **`$
|
|
2277
|
+
| **`$stream` の `args`** | state 側 | しない(起動・restart のたびに評価される) |
|
|
2248
2278
|
|
|
2249
|
-
**`$
|
|
2279
|
+
**`$renderedCallback` は根ではありません。** それは「バインディングが適用された結果」の報告であり、需要を作りません。
|
|
2250
2280
|
|
|
2251
2281
|
### 描画がプログラムの意味論を変えうる
|
|
2252
2282
|
|
|
@@ -2258,27 +2288,27 @@ $streams: {
|
|
|
2258
2288
|
```
|
|
2259
2289
|
|
|
2260
2290
|
```javascript
|
|
2261
|
-
// $
|
|
2291
|
+
// $renderedCallback は binding 駆動 —— 上の <b> を消すと paths に現れなくなり、
|
|
2262
2292
|
// フィードの commit が黙って止まる
|
|
2263
|
-
$
|
|
2293
|
+
$renderedCallback(paths) {
|
|
2264
2294
|
if (!paths.includes("$streamStatus.pageResult")) return;
|
|
2265
2295
|
this.items = this.items.concat(this.pageResult.items);
|
|
2266
2296
|
}
|
|
2267
2297
|
```
|
|
2268
2298
|
|
|
2269
|
-
**規則:** 描画に依存させたくないロジックは、`$watch`・`$scan`・`$
|
|
2299
|
+
**規則:** 描画に依存させたくないロジックは、`$watch`・`$scan`・`$stream` の `args` のどれかに根を置いてください。`$renderedCallback` は「描かれたものに追随する」用途に限ります。
|
|
2270
2300
|
|
|
2271
|
-
上の例はいまは `$scan` で feed を積み(sentinel の再武装は `$watch`)、`<b>` は表示専用に戻っています。この形(`$
|
|
2301
|
+
上の例はいまは `$scan` で feed を積み(sentinel の再武装は `$watch`)、`<b>` は表示専用に戻っています。この形(`$renderedCallback` が、どのバインディングにも現れないパスを判定に使っている)は **`wcs/updated-callback-unbound`** として静的に検出されます。
|
|
2272
2302
|
|
|
2273
2303
|
### 残る制約
|
|
2274
2304
|
|
|
2275
|
-
需要の根が 3 か所に分かれること自体は変わりません。**ある getter が評価されるかを知るには、その 3 か所(ページの全バインディング・全 `$watch`・全 `$
|
|
2305
|
+
需要の根が 3 か所に分かれること自体は変わりません。**ある getter が評価されるかを知るには、その 3 か所(ページの全バインディング・全 `$watch`・全 `$stream.args`)を見る必要があり、getter の定義だけを読んでも分かりません。** lint と DevTools の配線カバレッジはこの照合を機械にやらせるためのものです。
|
|
2276
2306
|
|
|
2277
2307
|
なお `$watch` に宣言したスカラー getter は **eager** になります(接続時に 1 回、以後は依存に触れたバッチごとに評価)。ワイルドカード行の getter は eager 化しません(初回評価がリスト全体を舐めるため)。
|
|
2278
2308
|
|
|
2279
2309
|
## Watch(`$watch`)
|
|
2280
2310
|
|
|
2281
|
-
`$
|
|
2311
|
+
`$renderedCallback` は **binding 駆動** です。その更新で live DOM binding が実際に適用された path だけを報告するため、**描画していない値の変化は見えません**。**`$watch`** はその headless 版で、ページ上でそのパスがバインドされているかどうかに関わらず、state の変化で発火します(**ワイルドカードの行パスだけは例外**で、headless に成立させるには `$listKeys` が要ります。後述)。
|
|
2282
2312
|
|
|
2283
2313
|
```html
|
|
2284
2314
|
<wcs-state>
|
|
@@ -2323,11 +2353,11 @@ $updatedCallback(paths) {
|
|
|
2323
2353
|
|
|
2324
2354
|
| 層 | 順序 | 制御 |
|
|
2325
2355
|
|---|---|---|
|
|
2326
|
-
| 機構間 | `$
|
|
2356
|
+
| 機構間 | `$renderedCallback` → `$scan` → `$watch` → `$stream` restart | 固定 |
|
|
2327
2357
|
| ハンドラ間 | `$watch` の宣言順 | **宣言を並べ替える** |
|
|
2328
2358
|
| 同一パスの行間 | `indexes` 昇順 | 固定 |
|
|
2329
2359
|
|
|
2330
|
-
**機構間の層を動かす唯一のもの**が、`state` 参加者を受け付ける `<wcs-view-transition>` です。バインディング適用 —— したがって `$
|
|
2360
|
+
**機構間の層を動かす唯一のもの**が、`state` 参加者を受け付ける `<wcs-view-transition>` です。バインディング適用 —— したがって `$renderedCallback` —— がフレームで着地する一方、`$scan`・`$watch`・`$stream` restart は state アドレスを消費し DOM を見ないので、drain がキューされた microtask に留まります。タグがある間の順序は `$scan` → `$watch` → `$stream` restart → `$renderedCallback` です。この層を並べ替えるものはページ上でこれ 1 つだけです。[docs/timing-and-firing-contract.ja.md](https://github.com/wcstack/wcstack/blob/main/docs/timing-and-firing-contract.ja.md) §4.3 を参照してください。
|
|
2331
2361
|
|
|
2332
2362
|
主なルール:
|
|
2333
2363
|
|
|
@@ -2336,14 +2366,14 @@ $updatedCallback(paths) {
|
|
|
2336
2366
|
- **行は drain の時点のリストに従います** —— 同じ job で行を書いてから取り除いた・置き換えた・リストを短くした行は発火せず、位置だけが移った行も発火しません。1 つの位置が発火するのは多くても 1 回です。入れ子のリストを置き換えると、新しい配列の行がすべて発火します。
|
|
2337
2367
|
- **行単位の差分を見たいなら `$listKeys`** —— 未宣言のまま配列全体を代入すると、行 watch は**全行**について `prev === undefined` で発火します(どの行もパス書き込みを通っていないため)。`$listKeys` を宣言すればキー突合が per-field 書き込みに分解するので、変化した行だけが発火し `prev` もスカラで取れます。
|
|
2338
2368
|
- **headless な行 watch には `$listKeys` が必要** —— `$watch` が単独では headless にならない唯一の箇所です。`items` から `items.*.price` への展開はリストの `for` バインディングが駆動しており、watch を宣言してもそのパスをリストとしては登録しません(意図的)。したがって `for` バインドも `$listKeys` も無い状態で配列を代入すると、行 watch は**一度も**発火しません。`$listKeys` を宣言する(キー突合がフィールドごとにパス書き込みするので展開を経由しない)か、リストを描画してください。スカラーパスは `user.name` のようなネストしたものも含め、この条件なしに headless で発火します。
|
|
2339
|
-
- **ハンドラの例外は隔離されます** —— throw はコンソールに報告され、残りの watch(と stream の restart)は続行します。loud fail する `$connectedCallback` / `$
|
|
2369
|
+
- **ハンドラの例外は隔離されます** —— throw はコンソールに報告され、残りの watch(と stream の restart)は続行します。loud fail する `$connectedCallback` / `$renderedCallback` とは異なる扱いです。
|
|
2340
2370
|
- **書き込みの連鎖には上限があります** —— ハンドラの書き込みは新しいバッチを作るため、相互に書き合う watch は無限ループになり得ます。32 段で打ち切り、コンソールに報告します(値と DOM は巻き戻しません)。
|
|
2341
|
-
- **マウントされた `bind-component` スコープでは実行されません** —— マウントされたコンポーネントは宣言面を実行せず、`$watch` の宣言があると 1 回だけ console.warn でルート state(またはボリューム —— `<wcs-state mount>` は `$watch` / `$listKeys` / `$
|
|
2371
|
+
- **マウントされた `bind-component` スコープでは実行されません** —— マウントされたコンポーネントは宣言面を実行せず、`$watch` の宣言があると 1 回だけ console.warn でルート state(またはボリューム —— `<wcs-state mount>` は `$watch` / `$listKeys` / `$renderedCallback` を持てます)へ誘導します(`$stream` も同様)。plain な(配線なし Shadow の)子は独立ツリーを持つので宣言できます。
|
|
2342
2372
|
- **SSR では実行されません** —— ハンドラの副作用がサーバーとクライアントで二重に走るためです。
|
|
2343
2373
|
|
|
2344
2374
|
## Scan(`$scan`)
|
|
2345
2375
|
|
|
2346
|
-
`$
|
|
2376
|
+
`$stream` が畳むのは 1 回の run の**内側**で、restart のたびに値は `initial` へ戻ります。`$watch` は値を所有しません。**`$scan`** はその両方を跨いで残る値 —— 時間軸方向の累積 —— を、持ち主・発火単位・reset 条件つきで宣言します。
|
|
2347
2377
|
|
|
2348
2378
|
```html
|
|
2349
2379
|
<wcs-state>
|
|
@@ -2352,7 +2382,7 @@ $updatedCallback(paths) {
|
|
|
2352
2382
|
page: 1,
|
|
2353
2383
|
host: "a",
|
|
2354
2384
|
$eventTokens: ["message"],
|
|
2355
|
-
$
|
|
2385
|
+
$stream: {
|
|
2356
2386
|
pageResult: { args: (s) => s.page, source: loadPage },
|
|
2357
2387
|
},
|
|
2358
2388
|
$scan: {
|
|
@@ -2388,7 +2418,7 @@ $updatedCallback(paths) {
|
|
|
2388
2418
|
| `fold` | 必須。`from` は `(acc, cur, prev, ...indexes) => next`、`on` は `(acc, event, ...indexes) => next`。同期で、`this` 無しで呼ばれ、新しい値を返す。`acc` そのものを返すと書き込まない。 |
|
|
2389
2419
|
| `resetOn` | 任意。素の state パスの配列。どれかが書かれたら出力を `initial` に戻す。`from` の scan はそのバッチの fold を行わず、`on` の scan は書き込みより後に来たイベントを `initial` から畳む。`from` の配下は raise、`from` の祖先は可(親の差し替えで作り直す)。オブジェクトのパスはそのオブジェクト自身が書かれたときだけ reset し、子への書き込みでは reset しない(葉のパスを並べるか nonce を使う)。 |
|
|
2390
2420
|
|
|
2391
|
-
**出力はランタイムが所有します**(`$
|
|
2421
|
+
**出力はランタイムが所有します**(`$stream` の値と同じ)。state にそのプロパティが無ければ `initial` で実体化され(plain なデータは複製するので、出力の plain な部分の子パスへ書いても宣言の `initial` は変わりません。クラスのインスタンスや凍結された値など plain でない値は宣言と共有したままです)、他のパスと同じようにバインドできます。stream の restart・切断と再接続・同じオブジェクトの再セットを跨いで残り、新しい宣言での再セットでは作り直されます。出力名が getter・setter・メソッド・`$stream` のエントリと衝突すると raise します。
|
|
2392
2422
|
|
|
2393
2423
|
2 つの source の発火:
|
|
2394
2424
|
|
|
@@ -2403,8 +2433,8 @@ $updatedCallback(paths) {
|
|
|
2403
2433
|
- **getter を畳まない。** getter は入力が変わるたびに再評価されるので、畳むと出来事ではなく再評価の回数を数えます。`from` や `resetOn` に getter を書く(`from` に `$recursion` の `**` getter の展開形 `nodes.*.total` を書くのも同じ)と、宣言時に raise します(`wcs/scan-source-computed`)。
|
|
2404
2434
|
- **1 回の fold は着地ごとで、ページごとではない。** `done` 後の再試行や、ページの再接続は同じページをもう一度着地させます。問題になるなら fold に冪等キーを持たせてください(上の `pages`)。
|
|
2405
2435
|
- **stream の `args` を自分の scan 出力から導出しない。** `feed` から導出した getter(`feed` を畳む別の scan の出力から導出したものを含む)を `pageResult` の `args` が読むと、stream が自分の結果で restart し続けるので、ランタイムは `wcs/scan-feedback-loop` を raise します。カーソルはイベントから進めてください。stream の restart と同じバッチに着地した chunk は abort される run のものなので畳みません。
|
|
2406
|
-
- **要素の出来事は `on` で受ける。** `from` はそのパスへの書き込みをすべて見ます。バインドした要素の初期同期や、親オブジェクトの丸ごと書き(`prev` は `undefined`)も 1 回として畳みます。`prev` は `$watch` と同じ台帳なので、`$scan` / `$watch` のリスナーの中の書き込み(`$watch` ハンドラや、`from` にした別の scan の出力)でも `undefined` です。台帳はそのリスナーの終わりに消えるので、同じ drain でその後に走る `$
|
|
2407
|
-
- **fold は有界に。** 無限の source は有界な値(直近 N 件・件数)に畳んでください(`$
|
|
2436
|
+
- **要素の出来事は `on` で受ける。** `from` はそのパスへの書き込みをすべて見ます。バインドした要素の初期同期や、親オブジェクトの丸ごと書き(`prev` は `undefined`)も 1 回として畳みます。`prev` は `$watch` と同じ台帳なので、`$scan` / `$watch` のリスナーの中の書き込み(`$watch` ハンドラや、`from` にした別の scan の出力)でも `undefined` です。台帳はそのリスナーの終わりに消えるので、同じ drain でその後に走る `$stream` の restart の書き込みは `prev` を持ちます。
|
|
2437
|
+
- **fold は有界に。** 無限の source は有界な値(直近 N 件・件数)に畳んでください(`$stream` と同じ)。
|
|
2408
2438
|
- **例外は隔離される。** throw・Promise の戻り値・読めない値はコンソールと DevTools に報告され、書き込みません(ワイルドカードの `from` で読めない行はその行だけを飛ばし、行の着地はリストの位置 1 つにつき 1 回に絞ります)。他の scan・watch・stream の restart は続行します。
|
|
2409
2439
|
- **`$watch` は scan の書き込みの後に走る。** 同じ drain の `$watch` ハンドラは畳んだ後の出力を読み、ハンドラが出力へ書いた値はそのまま残ります。出力の着地が drain される前に `from` の source がもう一度書かれる(その drain の `$watch` ハンドラが書くなど)と、両方が同じバッチに載ります。このとき出力を見る `$watch` は `prev` に着地した値を受け、`cur` に 1 段先の値を見て、次のバッチで同じ値でもう一度発火することがあるので、同じ値の重複に耐える形にしてください。ユーザー操作で累積を消すなら、`resetOn` に nonce を読ませてください。
|
|
2410
2440
|
- **ルートのみ。** ボリューム(`mount=`)は `$scan` を拒否し、マウントされた `bind-component` スコープは 1 回の warn で無視します。SSR では `from` は畳みません(出力の実体化は行います)。
|
|
@@ -2546,7 +2576,7 @@ export default {
|
|
|
2546
2576
|
- 非空文字列でないエントリ
|
|
2547
2577
|
- `$` 始まりのエントリ(内部プロパティはコンポーネントの prototype に公開されません)
|
|
2548
2578
|
- 重複したエントリ — これは従来サイレントに壊れていました。重複名があると `wcBindable` 宣言全体が読み取り不能になり、その要素が黙って双方向バインド不可になります
|
|
2549
|
-
- 状態に存在しないエントリ(自身とプロトタイプチェーンの両方を探索します。`$
|
|
2579
|
+
- 状態に存在しないエントリ(自身とプロトタイプチェーンの両方を探索します。`$stream` の名前は値プロパティがインスタンスごとに実体化されるため「存在する」と見なされます)
|
|
2550
2580
|
- `$bindables` にメソッドを書いた場合、または `$commands` に値プロパティを書いた場合
|
|
2551
2581
|
|
|
2552
2582
|
### DCC のメソッドを起動する
|
|
@@ -2606,7 +2636,7 @@ export default {
|
|
|
2606
2636
|
| `$commands` | 起動可能メソッドの宣言 |
|
|
2607
2637
|
| `$connectedCallback` | ライフサイクルフック(各インスタンスで実行) |
|
|
2608
2638
|
| `$disconnectedCallback` | クリーンアップフック |
|
|
2609
|
-
| `$
|
|
2639
|
+
| `$renderedCallback` | 状態変更後に呼ばれる |
|
|
2610
2640
|
|
|
2611
2641
|
## SVG サポート
|
|
2612
2642
|
|
|
@@ -2622,7 +2652,7 @@ export default {
|
|
|
2622
2652
|
|
|
2623
2653
|
## ライフサイクルフック
|
|
2624
2654
|
|
|
2625
|
-
状態オブジェクトに `$connectedCallback` / `$disconnectedCallback` / `$
|
|
2655
|
+
状態オブジェクトに `$connectedCallback` / `$disconnectedCallback` / `$renderedCallback` / `$errorCallback` を定義すると、初期化・クリーンアップ・更新時・バインディング失敗時のフックとして利用できます。
|
|
2626
2656
|
|
|
2627
2657
|
> **3.2 から `$updatedCallback` の正式名は `$renderedCallback` です**(要件 B12)。このフックが受けるのは**適用されたバインディング**の更新で、state の更新全体ではない(それには `$watch`)ことを名前で言うためです。旧名は 3.x の間は同じに動き、4.0 で外れます。両方を宣言すると `[wcs/declaration-alias]` で落ちます。3.1 以前では `$updatedCallback` と書いてください。
|
|
2628
2658
|
|
|
@@ -2653,15 +2683,15 @@ export default {
|
|
|
2653
2683
|
|---|---|---|
|
|
2654
2684
|
| `$connectedCallback` | 初回接続時は状態初期化後、再接続時は毎回呼び出し | 可(await される) |
|
|
2655
2685
|
| `$disconnectedCallback` | 要素が DOM から削除された時 | 不可(同期のみ) |
|
|
2656
|
-
| `$
|
|
2657
|
-
| `$errorCallback(error, info)` | バインディングの適用に失敗した drain の後 — 失敗した本数ぶん、`$
|
|
2686
|
+
| `$renderedCallback(paths, indexesListByPath)` | live binding に更新が適用された後に呼び出し | 可(await されない) |
|
|
2687
|
+
| `$errorCallback(error, info)` | バインディングの適用に失敗した drain の後 — 失敗した本数ぶん、`$renderedCallback` の後に呼び出し | 可(await されない) |
|
|
2658
2688
|
|
|
2659
2689
|
`$disconnectedCallback` を除くすべてのフックで `async` を使用できます。リアクティブ Proxy はすべてのプロパティへの代入を変更として検知します。そのため、標準の `async/await` による処理とプロパティへの直接代入だけで非同期ロジックが完結します。ローディングフラグの切り替え、取得したデータの格納、エラーメッセージの更新といった処理もすべて単なるプロパティ代入で行えるため、非同期状態を管理するための複雑な抽象化機能は必要ありません。
|
|
2660
2690
|
|
|
2661
2691
|
- フック内の `this` は読み書き可能な状態プロキシです。
|
|
2662
2692
|
- `$connectedCallback` は要素が接続される**たびに**呼ばれます(一度削除された後の再接続も含みます)。再確立が必要なセットアップ処理に適しています。
|
|
2663
2693
|
- `$disconnectedCallback` は同期的に呼び出されます。タイマーのクリア、イベントリスナーの削除、リソースの解放といったクリーンアップ処理に使用してください。
|
|
2664
|
-
- `$
|
|
2694
|
+
- `$renderedCallback(paths, indexesListByPath)` は、その drain で live binding が適用された path の一覧を受け取ります。binding のない state 書き込みでは呼ばれず、`paths` にも現れません。ワイルドカードをもつパスが更新された場合は、`indexesListByPath` から対象のインデックス情報も取得可能です。マウントされたコンポーネントのマーカーパス(`#m…`)は `paths` に現れません — コンポーネントの私有キーは私有のままです(DevTools の overlays 表示で見えます)。`async` を使用できますが、戻り値は await されません。
|
|
2665
2695
|
- `$errorCallback(error, info)` はバインディングの**ページ内エラー境界**です。バインディングの適用が throw したとき(パス getter やフィルタが throw した、構造ディレクティブが失敗した)、その失敗は隔離され(同じバッチの残りは適用され、値も DOM も巻き戻されません)、このフックが無ければ `console.error` で報告されます。フックを宣言すると報告はそこへ届きます: `error` は throw された値、`info` はバインディングを識別する `{ path, bindingType, node }`(`path` は `data-wcs` に書いた形のまま。ワイルドカードもそのまま)。`this` は書き込み可能な state proxy なので、メッセージを state に書いて普通に描画するのが基本形です:
|
|
2666
2696
|
|
|
2667
2697
|
```js
|
|
@@ -2674,7 +2704,7 @@ export default {
|
|
|
2674
2704
|
};
|
|
2675
2705
|
```
|
|
2676
2706
|
|
|
2677
|
-
フックはバッチの後(`$
|
|
2707
|
+
フックはバッチの後(`$renderedCallback` の後)に走り、await されず、フック内で throw しても console に報告されるだけで drain は壊れません。DevTools にはフックの有無に関わらず全失敗が `state:binding-apply-error` として届きます。ルート専用で、ボリューム(`<wcs-state mount>`)に宣言しても無視されます。`$watch` ハンドラの失敗(別途隔離・報告)や、`$connectedCallback` / `$renderedCallback` が投げた例外(loud に失敗する)は対象外です。
|
|
2678
2708
|
- Web Component を使用している場合は、コンポーネント側に `async $stateReadyCallback(stateProp)` を定義おくことで、`bind-component` でバインドした状態が利用可能になった瞬間にフックとして呼び出されます。
|
|
2679
2709
|
|
|
2680
2710
|
## 遷移アニメーション
|
|
@@ -2697,8 +2727,8 @@ li {
|
|
|
2697
2727
|
|
|
2698
2728
|
そのタグが `state` 参加者を受け付けている間、知っておくべき帰結が 2 つある。
|
|
2699
2729
|
|
|
2700
|
-
- drain は microtask ではなくフレームで着地する。state に書いてから `await Promise.resolve()` で DOM を読むコードは遷移を待つ必要がある。`$
|
|
2701
|
-
- `$scan`・`$watch`・`$
|
|
2730
|
+
- drain は microtask ではなくフレームで着地する。state に書いてから `await Promise.resolve()` で DOM を読むコードは遷移を待つ必要がある。`$renderedCallback` はバインディング適用の直後という*位置*こそ変わらないが、その適用ごと 1 フレーム後ろへずれる。
|
|
2731
|
+
- `$scan`・`$watch`・`$stream` restart は元の microtask に留まるため、`$renderedCallback` の**前**に走るようになる。
|
|
2702
2732
|
|
|
2703
2733
|
適用すべきバインディングが実際にあるバッチだけがタグへ渡されるので、headless なパスへの書き込みが遷移を起こすことはない。タグが無ければ drain は従来どおり。[docs/timing-and-firing-contract.ja.md](https://github.com/wcstack/wcstack/blob/main/docs/timing-and-firing-contract.ja.md) §4.3 参照。
|
|
2704
2734
|
|
|
@@ -2742,7 +2772,7 @@ dropped. Validate statically: npx @wcstack/lint <file>.
|
|
|
2742
2772
|
| `wcs/getter-cycle` | パス getter どうしが循環参照していないか。実行時は「アドレススタックが既に積んでいるアドレスへ戻る」ことで判定する | 循環を断つ |
|
|
2743
2773
|
| `wcs/getter-depth-exceeded` | getter の評価が 1 パスで評価できる深さ(128 段)を超え、かつ同じアドレスを 2 度通っていない = データが単に深い | 集計の段数を減らすか、木を平らにする |
|
|
2744
2774
|
| `wcs/index-param-range` | `$N` は実在するワイルドカード段を指すこと(`$1`〜`$128`・先頭ゼロ不可) | 実在する段を使う |
|
|
2745
|
-
| `wcs/recursion-unsupported` | `**` を解釈しない場所へ `**` が渡った —— markup・`$watch` / `$listKeys` のキー・`$resolve` / `$postUpdate` / `$
|
|
2775
|
+
| `wcs/recursion-unsupported` | `**` を解釈しない場所へ `**` が渡った —— markup・`$watch` / `$listKeys` のキー・`$resolve` / `$postUpdate` / `$dependOn`・代入、あるいは state が `$recursion` を宣言していない | 具体パスを使うか、アンカーを宣言する |
|
|
2746
2776
|
| `wcs/recursion-declaration-invalid` | `$recursion` 宣言か `**` getter のキーが、このバージョンが受け付けない形 —— 要素を指さない・途中に添字セグメントを持つ(`"nodes.0.items.*"`)アンカー / 反復サブパス、複数アンカー、getter でない・setter を持つ `**` キー、`get "nodes.**"`、構造を名指す getter、同じ具体パスへ展開する 2 本の getter、展開形と同名の具体 getter。lint が先に出し、実行時は宣言を読んだ時点で throw する | 文面のとおり宣言を直す |
|
|
2747
2777
|
| `wcs/recursion-anchor` | 宣言済みのアンカーと合致しない `**` パス(このバージョンは state ごとに単一の自己再帰アンカー)、または `**` の後ろが整形されていない —— 空セグメント(`nodes.**.` / `nodes.**..x`)や `**` 直後の素の `*`(`nodes.**.*`) | 宣言どおりに綴り、その後ろに実在するパスを書く |
|
|
2748
2778
|
| `wcs/recursion-context` | **束縛**形の `**` を、束縛先の深さが無い場所で読んだ —— トップレベル、またはアンカー外の getter | 再帰 getter か行 getter の中から読むか、`[]` で全深さを合併する |
|
|
@@ -2768,7 +2798,7 @@ this.$getAll("matrix.*.*", [row]);
|
|
|
2768
2798
|
|
|
2769
2799
|
### バインディング 1 本の失敗は 1 本に閉じ込められます
|
|
2770
2800
|
|
|
2771
|
-
バインディングの適用が throw しても、そのバッチの残り・`$
|
|
2801
|
+
バインディングの適用が throw しても、そのバッチの残り・`$renderedCallback`・`$watch`・`$stream` の restart はすべて続行します。失敗は握り潰されず、`console.error` と DevTools(`state:binding-apply-error`)に出ます。
|
|
2772
2802
|
|
|
2773
2803
|
```
|
|
2774
2804
|
[@wcstack/state] binding "text: items.*.label" failed to apply; the rest of this batch continues.
|
|
@@ -3062,7 +3092,7 @@ bootstrapState();
|
|
|
3062
3092
|
| `setterPaths` | setter として定義されたパスの Set |
|
|
3063
3093
|
| `createState(mutability, callback)` | 状態プロキシを作成(`"readonly"` または `"writable"`) |
|
|
3064
3094
|
| `createStateAsync(mutability, callback)` | `createState` の非同期版 |
|
|
3065
|
-
| `setInitialState(state)` | プログラムから状態を設定。初期化前は初期 state を渡します。初期化済みの要素では state 全体を入れ替え、確立済みのバインドを戻る前に新しい state で適用し直します(切断中の要素は再接続したときに適用し直します)。新しい state に無いパスのバインドは、古い表示のまま残さず適用の失敗として報告します。再セットは書き込みではないので、`$watch` のハンドラも `$
|
|
3095
|
+
| `setInitialState(state)` | プログラムから状態を設定。初期化前は初期 state を渡します。初期化済みの要素では state 全体を入れ替え、確立済みのバインドを戻る前に新しい state で適用し直します(切断中の要素は再接続したときに適用し直します)。新しい state に無いパスのバインドは、古い表示のまま残さず適用の失敗として報告します。再セットは書き込みではないので、`$watch` のハンドラも `$renderedCallback` も呼びません。リストは配列の同一性で突き合わせるので、長さが変わったリストは新しい配列で渡してください(push や splice でその場で長さを変えた同じ配列インスタンスでの再セットは非対応です)。読み込み済みのボリューム(`<wcs-state mount="…">` —— データはルートの木へ複製済みなので、ルートのマウントパスの下へ書いてください)、ボリュームやマウント済みコンポーネントのあるツリー、初期化に失敗した要素では throw します —— 失敗した要素は再武装できないので、取り除いて作り直してください |
|
|
3066
3096
|
| `nextVersion()` | バージョン番号をインクリメントして返す |
|
|
3067
3097
|
|
|
3068
3098
|
## アーキテクチャ
|