@wcstack/storage 1.22.5 → 1.22.6

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.
Files changed (3) hide show
  1. package/README.ja.md +20 -32
  2. package/README.md +20 -32
  3. package/package.json +1 -1
package/README.ja.md CHANGED
@@ -55,15 +55,14 @@ npm install @wcstack/storage
55
55
  <wcs-state>
56
56
  <script type="module">
57
57
  export default {
58
- // 意図的に undefined"" や null で初期化すると双方向バインディング
59
- // 経由で書き戻され、リロードのたびに保存値を上書きしてしまう
60
- // (下記「5. load-before-bind」参照)
61
- username: undefined,
58
+ // 空のキーは null をロードする 都合のよい初期値ではなく
59
+ // 要素の実初期値をシードする(下記「5. load-before-bind」参照)
60
+ username: null,
62
61
  };
63
62
  </script>
64
63
  </wcs-state>
65
64
 
66
- <wcs-storage key="username" data-wcs="value: username"></wcs-storage>
65
+ <wcs-storage key="username" data-wcs="value#init=element: username"></wcs-storage>
67
66
 
68
67
  <input data-wcs="value: username" placeholder="ユーザー名">
69
68
  <p>保存済み: <span data-wcs="textContent: username"></span></p>
@@ -74,7 +73,7 @@ npm install @wcstack/storage
74
73
  - `key` を設定すると接続時に自動読み込み
75
74
  - `value` にバインドすると双方向永続化
76
75
  - 任意で `loading`、`error` もバインド
77
- - バインドする state スロットは `undefined` で開始する完全な idiom は「5. load-before-bind」を参照
76
+ - `value` バインディングに `#init=element` を付けて保存値を初期同期の勝者にする理由は「5. load-before-bind」を参照
78
77
 
79
78
  ### 2. オブジェクトの永続化と `$trackDependency`
80
79
 
@@ -146,14 +145,14 @@ localStorage の変更は、別のタブからの更新も自動的に検知さ
146
145
  <wcs-state>
147
146
  <script type="module">
148
147
  export default {
149
- // 意図的に undefined — 下記「5. load-before-bind」参照
150
- sharedCounter: undefined,
148
+ // 下記「5. load-before-bind」参照
149
+ sharedCounter: null,
151
150
  };
152
151
  </script>
153
152
  </wcs-state>
154
153
 
155
154
  <wcs-storage key="shared-counter"
156
- data-wcs="value: sharedCounter">
155
+ data-wcs="value#init=element: sharedCounter">
157
156
  </wcs-storage>
158
157
 
159
158
  <!-- 他のタブで localStorage を変更すると、この値も自動更新される -->
@@ -162,47 +161,36 @@ localStorage の変更は、別のタブからの更新も自動的に検知さ
162
161
 
163
162
  > **注意**: `storage` イベントは同一オリジンの他のタブでの変更時にのみ発火します。sessionStorage はタブ間で共有されないため、クロスタブ同期は localStorage でのみ動作します。
164
163
 
165
- ### 5. load-before-bind: 永続スロットの idiom
164
+ ### 5. load-before-bind: `#init=element`
166
165
 
167
166
  `<wcs-storage>` は自身の `connectedCallback` で永続値をロードして通知します。スクリプトのロード状況によっては、これは `<wcs-state>` のバインディング確立**前**に起こりえます — 帰結は 2 つ:
168
167
 
169
- 1. **上書き消去(clobber)**: バインドした state スロットが `""` / `0` / `null` / `[]` で始まると、初期の state→element 適用がその値を `value` に書き込み、write-through 保存が**リロードのたびに永続データを上書き**します。
170
- 2. **ロードの取り逃し**: ロード完了を知らせる value イベントが誰も聴いていない間に発火し、state スロットが初期値のまま残ることがあります。
168
+ 1. **上書き消去(clobber)**: `value` はこの要素で唯一の*双方向*メンバなので、既定のバインディング authority `state` です。初期の state→element 適用が state スロットのシード値を `value` に書き込み、write-through 保存が**リロードのたびに永続データを上書き**します。
169
+ 2. **ロードの取り逃し**: ロード完了を知らせる value イベントが誰も聴いていない間に発火し、state スロットがシード値のまま残ることがあります。
171
170
 
172
- 両方を塞ぐ idiom:
171
+ 両方を塞ぐのは修飾子 1 つです。`#init=element` は初期同期の authority を**要素側**にします:
173
172
 
174
173
  ```html
175
174
  <wcs-state>
176
175
  <script type="module">
177
176
  export default {
178
- // 1. undefined =「無意見」: 初期適用がスキップされ、
179
- // 永続値が上書きされることはない
180
- todos: undefined,
181
- // 読み出しは正規化 getter 経由
177
+ // 空のキーは null をロードするので、それが正直なシード値。
178
+ // 都合のよい [] ではなく正規化 getter 経由で読む。
179
+ todos: null,
182
180
  get list() {
183
181
  return Array.isArray(this.todos) ? this.todos : [];
184
182
  },
185
- // 2. <wcs-storage> がロード済みの値を一度だけ pull する
186
- $connectedCallback() {
187
- (async () => {
188
- await customElements.whenDefined("wcs-storage");
189
- const el = document.querySelector("wcs-storage");
190
- if (!el) return;
191
- await el.connectedCallbackPromise;
192
- if (!Array.isArray(this.todos) && Array.isArray(el.value)) {
193
- this.todos = el.value;
194
- }
195
- })();
196
- },
197
183
  };
198
184
  </script>
199
185
  </wcs-state>
200
186
 
201
- <wcs-storage key="todos" type="local" data-wcs="value: todos"></wcs-storage>
187
+ <wcs-storage key="todos" type="local" data-wcs="value#init=element: todos"></wcs-storage>
202
188
  ```
203
189
 
204
- - 原則: **`value` に双方向バインドする state スロットは `undefined` で開始する** — `""` / `0` / `null` / `[]` にしない。
205
- - `$connectedCallback` pull が必要なのは、永続値を初回描画で表示したい場合だけです。ユーザー操作後にしか書かないスロットなら `undefined` だけで十分です。
190
+ - `#init=element` は**初期の state→element 書き込みを行わず**(clobber しない)、**要素の現在の `value` state スロットへ pull します**(取り逃さない)。
191
+ - authority が支配するのは*初期同期のみ*です。以後の `todos` 代入は通常どおり state→element に流れるので、自動保存は生きたままです。
192
+ - シードは要素の実初期値(空キーなら `null`)に合わせ、読み出しは派生 getter で null ガードしてください。`[]` や `""` のような都合のよいシードは、どのみち初期 pull で置き換えられます。
193
+ - `enableDirectionalInitialSync`(v1.21.0 以降は既定 ON)が前提です。明示的に無効化した場合 `#init=` は throw するので、その構成では `undefined` シード + `$connectedCallback` での一度きり pull に倒してください。
206
194
  - 動作する実例: `examples/state-cross-tab-todo`、`examples/state-color-palette`。
207
195
 
208
196
  ## ステートサーフェス vs コマンドサーフェス
package/README.md CHANGED
@@ -54,15 +54,14 @@ Primitive values (strings, numbers, booleans) work with just a `value` binding f
54
54
  <wcs-state>
55
55
  <script type="module">
56
56
  export default {
57
- // undefined on purpose a "" / null initial would be written back
58
- // through the two-way binding and overwrite the saved value on
59
- // reload (see "5. Load-before-bind" below)
60
- username: undefined,
57
+ // null is what an empty key loads as seed the element's real initial
58
+ // value, not a convenient one (see "5. Load-before-bind" below)
59
+ username: null,
61
60
  };
62
61
  </script>
63
62
  </wcs-state>
64
63
 
65
- <wcs-storage key="username" data-wcs="value: username"></wcs-storage>
64
+ <wcs-storage key="username" data-wcs="value#init=element: username"></wcs-storage>
66
65
 
67
66
  <input data-wcs="value: username" placeholder="Username">
68
67
  <p>Saved: <span data-wcs="textContent: username"></span></p>
@@ -73,7 +72,7 @@ This is the default mode:
73
72
  - Set a `key` to auto-load on connection
74
73
  - Bind to `value` for two-way persistence
75
74
  - Optionally bind `loading` and `error` as well
76
- - Start the bound state slot as `undefined` — see [5. Load-before-bind](#5-load-before-bind-the-persistent-slot-idiom) for the full idiom
75
+ - Add `#init=element` to the `value` binding so the stored value wins the initial sync — see [5. Load-before-bind](#5-load-before-bind-initelement) for why
77
76
 
78
77
  ### 2. Persisting objects with `$trackDependency`
79
78
 
@@ -145,14 +144,14 @@ localStorage changes are automatically detected from other tabs:
145
144
  <wcs-state>
146
145
  <script type="module">
147
146
  export default {
148
- // undefined on purpose — see "5. Load-before-bind" below
149
- sharedCounter: undefined,
147
+ // see "5. Load-before-bind" below
148
+ sharedCounter: null,
150
149
  };
151
150
  </script>
152
151
  </wcs-state>
153
152
 
154
153
  <wcs-storage key="shared-counter"
155
- data-wcs="value: sharedCounter">
154
+ data-wcs="value#init=element: sharedCounter">
156
155
  </wcs-storage>
157
156
 
158
157
  <!-- Changes from other tabs update this value automatically -->
@@ -161,47 +160,36 @@ localStorage changes are automatically detected from other tabs:
161
160
 
162
161
  > **Note**: The `storage` event only fires for changes made in other tabs of the same origin. Since sessionStorage is not shared across tabs, cross-tab sync only works with localStorage.
163
162
 
164
- ### 5. Load-before-bind: the persistent-slot idiom
163
+ ### 5. Load-before-bind: `#init=element`
165
164
 
166
165
  `<wcs-storage>` loads and announces the persisted value in its own `connectedCallback`. Depending on script loading, that can happen **before** `<wcs-state>` finishes attaching its bindings — with two consequences:
167
166
 
168
- 1. **Clobber**: if the bound state slot starts as `""` / `0` / `null` / `[]`, the initial state→element apply writes that value into `value`, and the write-through save **overwrites the persisted data on every reload**.
169
- 2. **Missed load**: the value event announcing the loaded data fired before anyone was listening, so the state slot can stay at its initial value.
167
+ 1. **Clobber**: `value` is the one *bidirectional* member on this element, so its default binding authority is `state`. The initial state→element apply writes the state slot's seed into `value`, and the write-through save **overwrites the persisted data on every reload**.
168
+ 2. **Missed load**: the value event announcing the loaded data fired before anyone was listening, so the state slot can stay at its seed.
170
169
 
171
- The idiom that closes both:
170
+ One modifier closes both. `#init=element` makes the **element** the authority for the initial sync:
172
171
 
173
172
  ```html
174
173
  <wcs-state>
175
174
  <script type="module">
176
175
  export default {
177
- // 1. undefined = "no opinion": the initial apply is skipped,
178
- // so the persisted value is never clobbered
179
- todos: undefined,
180
- // reads go through a normalizing getter
176
+ // An empty key loads as null, so that is the honest seed. Reads go
177
+ // through a normalizing getter instead of a convenient [] seed.
178
+ todos: null,
181
179
  get list() {
182
180
  return Array.isArray(this.todos) ? this.todos : [];
183
181
  },
184
- // 2. pull the value <wcs-storage> already loaded, once
185
- $connectedCallback() {
186
- (async () => {
187
- await customElements.whenDefined("wcs-storage");
188
- const el = document.querySelector("wcs-storage");
189
- if (!el) return;
190
- await el.connectedCallbackPromise;
191
- if (!Array.isArray(this.todos) && Array.isArray(el.value)) {
192
- this.todos = el.value;
193
- }
194
- })();
195
- },
196
182
  };
197
183
  </script>
198
184
  </wcs-state>
199
185
 
200
- <wcs-storage key="todos" type="local" data-wcs="value: todos"></wcs-storage>
186
+ <wcs-storage key="todos" type="local" data-wcs="value#init=element: todos"></wcs-storage>
201
187
  ```
202
188
 
203
- - Rule of thumb: **a state slot bound two-way to `value` must start as `undefined`** never `""` / `0` / `null` / `[]`.
204
- - The `$connectedCallback` pull is only needed when the persisted value must render on first paint. If the slot is only ever written after user interaction, `undefined` alone is enough.
189
+ - `#init=element` **skips the initial state→element write** (no clobber) and **pulls the element's current `value` into the state slot** (no missed load).
190
+ - Authority governs the *initial sync only*: every later `todos` assignment still flows state→element, so auto-save keeps working.
191
+ - Seed the slot with the element's real initial value (`null` for an empty key) and null-guard reads through a derived getter. A convenient `[]` / `""` seed does not survive the initial pull anyway.
192
+ - Needs `enableDirectionalInitialSync`, on by default since v1.21.0. If you explicitly disable it, `#init=` throws; seed `undefined` and pull once in `$connectedCallback` instead.
205
193
  - Working examples: `examples/state-cross-tab-todo`, `examples/state-color-palette`.
206
194
 
207
195
  ## State Surface vs Command Surface
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wcstack/storage",
3
- "version": "1.22.5",
3
+ "version": "1.22.6",
4
4
  "description": "Declarative persistent storage component for Web Components. Framework-agnostic localStorage/sessionStorage via wc-bindable-protocol.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.esm.js",