@wcstack/storage 1.17.0 → 1.19.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.
Files changed (3) hide show
  1. package/README.ja.md +55 -2
  2. package/README.md +55 -2
  3. package/package.json +1 -1
package/README.ja.md CHANGED
@@ -52,7 +52,12 @@ npm install @wcstack/storage
52
52
 
53
53
  <wcs-state>
54
54
  <script type="module">
55
- export default { username: "" };
55
+ export default {
56
+ // 意図的に undefined — "" や null で初期化すると双方向バインディング
57
+ // 経由で書き戻され、リロードのたびに保存値を上書きしてしまう
58
+ // (下記「5. load-before-bind」参照)
59
+ username: undefined,
60
+ };
56
61
  </script>
57
62
  </wcs-state>
58
63
 
@@ -67,6 +72,7 @@ npm install @wcstack/storage
67
72
  - `key` を設定すると接続時に自動読み込み
68
73
  - `value` にバインドすると双方向永続化
69
74
  - 任意で `loading`、`error` もバインド
75
+ - バインドする state スロットは `undefined` で開始する — 完全な idiom は「5. load-before-bind」を参照
70
76
 
71
77
  ### 2. オブジェクトの永続化と `$trackDependency`
72
78
 
@@ -137,7 +143,10 @@ localStorage の変更は、別のタブからの更新も自動的に検知さ
137
143
  ```html
138
144
  <wcs-state>
139
145
  <script type="module">
140
- export default { sharedCounter: 0 };
146
+ export default {
147
+ // 意図的に undefined — 下記「5. load-before-bind」参照
148
+ sharedCounter: undefined,
149
+ };
141
150
  </script>
142
151
  </wcs-state>
143
152
 
@@ -151,6 +160,49 @@ localStorage の変更は、別のタブからの更新も自動的に検知さ
151
160
 
152
161
  > **注意**: `storage` イベントは同一オリジンの他のタブでの変更時にのみ発火します。sessionStorage はタブ間で共有されないため、クロスタブ同期は localStorage でのみ動作します。
153
162
 
163
+ ### 5. load-before-bind: 永続スロットの idiom
164
+
165
+ `<wcs-storage>` は自身の `connectedCallback` で永続値をロードして通知します。スクリプトのロード状況によっては、これは `<wcs-state>` のバインディング確立**前**に起こりえます — 帰結は 2 つ:
166
+
167
+ 1. **上書き消去(clobber)**: バインドした state スロットが `""` / `0` / `null` / `[]` で始まると、初期の state→element 適用がその値を `value` に書き込み、write-through 保存が**リロードのたびに永続データを上書き**します。
168
+ 2. **ロードの取り逃し**: ロード完了を知らせる value イベントが誰も聴いていない間に発火し、state スロットが初期値のまま残ることがあります。
169
+
170
+ 両方を塞ぐ idiom:
171
+
172
+ ```html
173
+ <wcs-state>
174
+ <script type="module">
175
+ export default {
176
+ // 1. undefined =「無意見」: 初期適用がスキップされ、
177
+ // 永続値が上書きされることはない
178
+ todos: undefined,
179
+ // 読み出しは正規化 getter 経由
180
+ get list() {
181
+ return Array.isArray(this.todos) ? this.todos : [];
182
+ },
183
+ // 2. <wcs-storage> がロード済みの値を一度だけ pull する
184
+ $connectedCallback() {
185
+ (async () => {
186
+ await customElements.whenDefined("wcs-storage");
187
+ const el = document.querySelector("wcs-storage");
188
+ if (!el) return;
189
+ await el.connectedCallbackPromise;
190
+ if (!Array.isArray(this.todos) && Array.isArray(el.value)) {
191
+ this.todos = el.value;
192
+ }
193
+ })();
194
+ },
195
+ };
196
+ </script>
197
+ </wcs-state>
198
+
199
+ <wcs-storage key="todos" type="local" data-wcs="value: todos"></wcs-storage>
200
+ ```
201
+
202
+ - 原則: **`value` に双方向バインドする state スロットは `undefined` で開始する** — `""` / `0` / `null` / `[]` にしない。
203
+ - `$connectedCallback` の pull が必要なのは、永続値を初回描画で表示したい場合だけです。ユーザー操作後にしか書かないスロットなら `undefined` だけで十分です。
204
+ - 動作する実例: `examples/state-cross-tab-todo`、`examples/state-color-palette`。
205
+
154
206
  ## ステートサーフェス vs コマンドサーフェス
155
207
 
156
208
  `<wcs-storage>` は 2 種類のプロパティを公開します。
@@ -417,6 +469,7 @@ interface WcsStorageValues<T = unknown> extends WcsStorageCoreValues<T> {
417
469
  - `value` が状態パスにバインドされ、UI に反映
418
470
  - ユーザーが UI を操作すると状態が変わり、自動的にストレージに書き戻し
419
471
  - リロードしても状態が復元される
472
+ - バインドするスロットは `undefined` で開始する(クイックスタート 5)ため、リロードが保存値を上書きしない
420
473
 
421
474
  永続化が通常の状態更新と同じように見えるようになります。
422
475
 
package/README.md CHANGED
@@ -51,7 +51,12 @@ Primitive values (strings, numbers, booleans) work with just a `value` binding f
51
51
 
52
52
  <wcs-state>
53
53
  <script type="module">
54
- export default { username: "" };
54
+ export default {
55
+ // undefined on purpose — a "" / null initial would be written back
56
+ // through the two-way binding and overwrite the saved value on
57
+ // reload (see "5. Load-before-bind" below)
58
+ username: undefined,
59
+ };
55
60
  </script>
56
61
  </wcs-state>
57
62
 
@@ -66,6 +71,7 @@ This is the default mode:
66
71
  - Set a `key` to auto-load on connection
67
72
  - Bind to `value` for two-way persistence
68
73
  - Optionally bind `loading` and `error` as well
74
+ - Start the bound state slot as `undefined` — see [5. Load-before-bind](#5-load-before-bind-the-persistent-slot-idiom) for the full idiom
69
75
 
70
76
  ### 2. Persisting objects with `$trackDependency`
71
77
 
@@ -136,7 +142,10 @@ localStorage changes are automatically detected from other tabs:
136
142
  ```html
137
143
  <wcs-state>
138
144
  <script type="module">
139
- export default { sharedCounter: 0 };
145
+ export default {
146
+ // undefined on purpose — see "5. Load-before-bind" below
147
+ sharedCounter: undefined,
148
+ };
140
149
  </script>
141
150
  </wcs-state>
142
151
 
@@ -150,6 +159,49 @@ localStorage changes are automatically detected from other tabs:
150
159
 
151
160
  > **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.
152
161
 
162
+ ### 5. Load-before-bind: the persistent-slot idiom
163
+
164
+ `<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:
165
+
166
+ 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**.
167
+ 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.
168
+
169
+ The idiom that closes both:
170
+
171
+ ```html
172
+ <wcs-state>
173
+ <script type="module">
174
+ export default {
175
+ // 1. undefined = "no opinion": the initial apply is skipped,
176
+ // so the persisted value is never clobbered
177
+ todos: undefined,
178
+ // reads go through a normalizing getter
179
+ get list() {
180
+ return Array.isArray(this.todos) ? this.todos : [];
181
+ },
182
+ // 2. pull the value <wcs-storage> already loaded, once
183
+ $connectedCallback() {
184
+ (async () => {
185
+ await customElements.whenDefined("wcs-storage");
186
+ const el = document.querySelector("wcs-storage");
187
+ if (!el) return;
188
+ await el.connectedCallbackPromise;
189
+ if (!Array.isArray(this.todos) && Array.isArray(el.value)) {
190
+ this.todos = el.value;
191
+ }
192
+ })();
193
+ },
194
+ };
195
+ </script>
196
+ </wcs-state>
197
+
198
+ <wcs-storage key="todos" type="local" data-wcs="value: todos"></wcs-storage>
199
+ ```
200
+
201
+ - Rule of thumb: **a state slot bound two-way to `value` must start as `undefined`** — never `""` / `0` / `null` / `[]`.
202
+ - 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.
203
+ - Working examples: `examples/state-cross-tab-todo`, `examples/state-color-palette`.
204
+
153
205
  ## State Surface vs Command Surface
154
206
 
155
207
  `<wcs-storage>` exposes two kinds of properties.
@@ -416,6 +468,7 @@ interface WcsStorageValues<T = unknown> extends WcsStorageCoreValues<T> {
416
468
  - `value` is bound to a state path, reflected in the UI
417
469
  - User interactions update state, which auto-saves back to storage
418
470
  - State survives page reloads
471
+ - The bound slot starts as `undefined` (Quick Start 5), so a reload never overwrites what was saved
419
472
 
420
473
  Persistence looks just like any other state update.
421
474
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wcstack/storage",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
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",