@wcstack/storage 2.1.1 → 2.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.
Files changed (3) hide show
  1. package/README.ja.md +45 -4
  2. package/README.md +45 -4
  3. package/package.json +3 -2
package/README.ja.md CHANGED
@@ -75,12 +75,53 @@ npm install @wcstack/storage
75
75
  - 任意で `loading`、`error` もバインド
76
76
  - `value` バインディングに `#init=element` を付けて保存値を初期同期の勝者にする — 理由は「5. load-before-bind」を参照
77
77
 
78
- ### 2. オブジェクトの永続化と `$trackDependency`
78
+ ### 2. フォームを 1 つのオブジェクトとして永続化する
79
79
 
80
- オブジェクトのサブプロパティ(`settings.theme` 等)を変更しても、親パス `settings` へのバインディングは発火しません。
81
- `@wcstack/state` の依存走査は**親→子方向**のみだからです。
80
+ いちばんありそうな形 —— 複数の入力欄を 1 つのキーにまとめて保存し、リロードで復元する。state に**アクセサペア**(オブジェクトを組み立てる getter と、受け取ってばらす setter)を置き、そこへ `value#init=element` をバインドします:
82
81
 
83
- この場合は `$trackDependency` で監視したいサブプロパティを明示し、`trigger` 経由で保存します:
82
+ ```html
83
+ <wcs-state>
84
+ <script type="module">
85
+ export default {
86
+ form: { name: "", email: "" },
87
+
88
+ // 保存経路。各フィールドを *パスで* 読むので、どれが変わっても getter が
89
+ // 再評価され、新しいオブジェクトが <wcs-storage> の value へ流れて保存される
90
+ // (write-through)。
91
+ get formSnapshot() {
92
+ return { name: this["form.name"], email: this["form.email"] };
93
+ },
94
+ // 復元経路。ロード時に永続化済みオブジェクトがここに届く(空キーなら null)。
95
+ set formSnapshot(v) {
96
+ if (v) this.form = { ...this.form, ...v };
97
+ },
98
+ };
99
+ </script>
100
+ </wcs-state>
101
+
102
+ <wcs-storage key="signup-form" type="local"
103
+ data-wcs="value#init=element: formSnapshot"></wcs-storage>
104
+
105
+ <input data-wcs="value: form.name" placeholder="名前">
106
+ <input data-wcs="value: form.email" type="email" placeholder="メール">
107
+ ```
108
+
109
+ **フロー:**
110
+
111
+ 1. 接続時に `<wcs-storage>` がキーをロード。`#init=element` によりロード済みオブジェクトが `formSnapshot` に書かれ → setter が `form` を埋め → 入力欄に復元値が出る
112
+ 2. 入力すると `form.name` が書かれ → `formSnapshot` が dirty に → バインディングが新しいオブジェクトを `value` に適用 → 保存
113
+ 3. 保存が発火する `value` イベントは同じオブジェクトを戻すだけ。ランタイムは書き込み確認として認識して止まり、setter が走るのはロード時の 1 回だけ
114
+
115
+ 間違えやすい点が 2 つ:
116
+
117
+ - フィールドは `this["form.name"]` で読み、`this.form.name` で読ま**ない**こと。後者は `form` にしか依存を張らず(`.name` は返ってきたオブジェクトへの素のプロパティアクセス)、フィールドを編集しても getter は再評価されません —— [依存追跡の境界](../state/README.ja.md#依存追跡の境界) を参照
118
+ - 空キーでは初回ロードで seed オブジェクトが 1 回書かれます。`null` が setter を通り、アクセサペアへの書き込みは常に getter を再評価するからです。永続化済みデータが上書きされることはありません —— 保存済みオブジェクトは復元され、同じ内容で再保存されます
119
+
120
+ このパターンは `packages/storage/__tests__/integration.accessorPairForm.test.ts` が実 `@wcstack/state` に対して固定しています。
121
+
122
+ #### 任意のタイミングで保存する(`manual` + `trigger`)
123
+
124
+ 変更のたびではなく自分で選んだ瞬間に保存したい場合は、`manual` を付けて `trigger:` を真偽値で駆動します。依存走査は**親→子方向**のみなので、`settings.theme` が変わっても `settings` へのバインディングは発火しません。`$trackDependency` で監視するフィールドを列挙し、`trigger` でオブジェクト全体をコミットします:
84
125
 
85
126
  ```html
86
127
  <wcs-state>
package/README.md CHANGED
@@ -74,12 +74,53 @@ This is the default mode:
74
74
  - Optionally bind `loading` and `error` as well
75
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
76
76
 
77
- ### 2. Persisting objects with `$trackDependency`
77
+ ### 2. Persisting a form as one object
78
78
 
79
- When sub-properties of an object (e.g. `settings.theme`) change, the parent path `settings` binding does **not** fire.
80
- This is because `@wcstack/state`'s dependency walk is **parent → child only**.
79
+ The most common shape: several inputs, one storage key, restored on reload. Give the state an **accessor pair** — a getter that assembles the object and a setter that takes it apart — and bind `value#init=element` to it:
81
80
 
82
- In this case, use `$trackDependency` to explicitly list the sub-properties to watch, and save via `trigger`:
81
+ ```html
82
+ <wcs-state>
83
+ <script type="module">
84
+ export default {
85
+ form: { name: "", email: "" },
86
+
87
+ // Save path. Reading each field *by path* makes the getter re-evaluate
88
+ // whenever any of them changes; the new object flows into <wcs-storage>'s
89
+ // value, which saves it (write-through).
90
+ get formSnapshot() {
91
+ return { name: this["form.name"], email: this["form.email"] };
92
+ },
93
+ // Restore path. The persisted object arrives here on load (null for an empty key).
94
+ set formSnapshot(v) {
95
+ if (v) this.form = { ...this.form, ...v };
96
+ },
97
+ };
98
+ </script>
99
+ </wcs-state>
100
+
101
+ <wcs-storage key="signup-form" type="local"
102
+ data-wcs="value#init=element: formSnapshot"></wcs-storage>
103
+
104
+ <input data-wcs="value: form.name" placeholder="Name">
105
+ <input data-wcs="value: form.email" type="email" placeholder="Email">
106
+ ```
107
+
108
+ **Flow:**
109
+
110
+ 1. On connection `<wcs-storage>` loads the key; `#init=element` writes the loaded object to `formSnapshot` → the setter fills `form` → the inputs show the restored values
111
+ 2. Typing writes `form.name` → `formSnapshot` is dirty → its binding applies the new object to `value` → saved
112
+ 3. The save's own `value` event carries the same object back; the runtime recognizes the write confirmation and stops — the setter runs once, on load
113
+
114
+ Two details that are easy to get wrong:
115
+
116
+ - Read fields as `this["form.name"]`, **not** `this.form.name`. The second form registers a dependency on `form` only (`.name` is a plain property access on the returned object), so editing a field never re-evaluates the getter — see [Dependency tracking boundaries](../state/README.md#dependency-tracking-boundaries).
117
+ - With an empty key the first load writes the seed object once: `null` goes through the setter, and a write to an accessor pair always re-evaluates the getter. Nothing persisted is ever overwritten — a stored object is restored, then re-saved unchanged.
118
+
119
+ This pattern is fixed by `packages/storage/__tests__/integration.accessorPairForm.test.ts` against the real `@wcstack/state`.
120
+
121
+ #### Saving on demand (`manual` + `trigger`)
122
+
123
+ To save at a moment of your choosing instead of on every change, add `manual` and drive `trigger:` from a boolean. The dependency walk is **parent → child only**, so a `settings` binding does not fire when `settings.theme` changes; `$trackDependency` lists the fields to watch and `trigger` commits the whole object:
83
124
 
84
125
  ```html
85
126
  <wcs-state>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wcstack/storage",
3
- "version": "2.1.1",
3
+ "version": "2.3.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",
@@ -57,13 +57,14 @@
57
57
  "@rollup/plugin-typescript": "^11.1.6",
58
58
  "@vitest/coverage-v8": "^4.0.15",
59
59
  "@vitest/ui": "^4.0.15",
60
+ "@wcstack/state": "file:../state",
60
61
  "eslint": "^9.39.1",
61
62
  "globals": "^16.5.0",
62
63
  "happy-dom": "^20.0.11",
63
64
  "rimraf": "^6.0.1",
64
65
  "rollup": "^4.22.4",
65
- "rollup-plugin-dts": "^6.1.1",
66
66
  "rollup-plugin-copy": "^3.5.0",
67
+ "rollup-plugin-dts": "^6.1.1",
67
68
  "tslib": "^2.8.1",
68
69
  "typescript": "^5.9.3",
69
70
  "typescript-eslint": "^8.49.0",