Repository navigation
feat(offline): 共通のストレージ世代判定・破棄ライフサイクルを追加 - #108
Conversation
| if (!state.requested && options.rebuild === 'after-reload') { | ||
| if (await this.#hasStorage()) return 'reload-required'; | ||
| await options.markerStore.set({ key: options.generationKey, value: state.generation }); | ||
| this.#revision = (await options.markerStore.get({ key: this.#revisionKey })).value; | ||
| return 'ready'; |
There was a problem hiding this comment.
🔴 DB がない場合に古いメディアファイルが残る
after-reload で対象 DB だけが消えていると、新世代を確定して additionalCleanup を飛ばします。残ったメディアファイルは次回以降も破棄されません。
Learn more
after-reload の空ストレージ判定は、登録済み SQLite データベースの存在だけを検査します。additionalCleanup が管理するファイルは検査しません。DB の削除後にファイル削除が失敗した場合や、DB を持たずファイルだけ残る場合でも、新世代のマーカーを書いて以後の削除をスキップします。clearOfflineLocalDatabases でのみ追加クリーンアップが実行されます。
Example: 対象 DB が消えた後も写真ファイルが残っている初回起動で、rebuild: 'after-reload' を使うと prepare() は ready を返し、写真を残したまま世代を書き換えます。
Recommended fix: DB がない場合も additionalCleanup が管理する残存データの有無を判定できるようにするか、世代不一致時には安全な追加クリーンアップを実行してから完了マーカーを書いてください。
Was this helpful? React with 👍 or 👎 to provide feedback.
| const state = await inspectOfflineStorageGeneration(this.options); | ||
| if (state.matches || state.requested) return false; |
There was a problem hiding this comment.
🟡 リセット要求の再試行で再起動が行われない
要求マーカーの保存後に再読み込みできなかった場合、再試行は false を返して再読み込みしません。そのランタイムはストレージを開けず、更新画面から復帰できません。
Learn more
この API は更新画面で準備完了を待ち、破棄要求を保存してからコールド再起動するためのものです。要求マーカーの書き込みと reload() の実行は別操作なので、再読み込みが失敗または中断されるとマーカーだけが残ります。再試行すると requested を検出して直ちに終了し、再読み込みできません。
Example: 1 回目に requested の保存は成功したが reload() が失敗した場合、2 回目は待機も再読み込みもせず false を返します。
Recommended fix: 既存の requested は破棄処理を重複実行する理由にはしない一方、呼び出し元のコールド再起動要求を妨げないようにしてください。複数の呼び出しが同時に走る場合の waitUntilReady と reload の順序も明示してください。
Was this helpful? React with 👍 or 👎 to provide feedback.
| const storage = { | ||
| keys: async () => Object.keys(localStorage), | ||
| remove: async (key) => { | ||
| localStorage.setItem('deletions', String(Number(localStorage.getItem('deletions') ?? 0) + 1)); | ||
| await new Promise((resolve) => setTimeout(resolve, 20)); | ||
| localStorage.removeItem(key); | ||
| }, |
オフラインストレージの世代判定と破棄・再構築をkitへ共通化
SeatKeepとWinecodeで重複している、ローカルストレージの世代判定、破棄要求の永続化、再起動後の再構築を
OfflineStorageLifecycleに集約します。アプリは世代・DB名・追加ファイルの削除・LiveUpdate準備待ちを設定します。破棄は明示的なopt-inで、レプリカと未送信Outboxが対象です。認証情報は保持します。通信障害やサーバープロトコル不一致を破棄条件にしません。既存の世代・マーカー形式を引き継ぎます。
WebではWeb Locksで破棄と読み書きを排他制御し、世代変更前のランタイムによる操作を拒否します。既に配布済みの旧タブはこの仕組みに参加できないため、導入時の再読み込みが必要です。Winecodeの起動経路は軽量なstorage-generationエントリーポイントで遅延読み込みを維持します。
検証: 最終コードのkitオフライン全体662件、ライフサイクル13件、実Chromiumの2タブ検証、lint、SQLite並行処理・実DBテスト、全ライブラリビルド、packed package consumer検証が成功。SeatKeep250件および最終対象10件、Winecode26件、両アプリのCI設定ビルド成功。ネイティブ実機のアップデート経由の検証は未実施です。
上長・第三者受け入れレビュー: それぞれ独立したエージェントがkit ebbc500、SeatKeep b31f73267、Winecode af2548e71をレビューし、双方承認・差し戻しなし。第三者はライフサイクル13件と実Chromiumの2タブテストも再実行して成功。公開後にアプリの依存を公開版kitへ更新し、再検証します。
CI・Devin指摘への追補 (d6811cd): Angular21/TypeScript5.9のWeb Locks戻り値型に対応。DBなしでも追加cleanupが設定されている場合はreadinessを待って残存mediaを削除してから世代を確定。kit APIはreload失敗後の再呼出しで再試行可能。WinecodeのUIに自動再試行機能を追加するものではありません。回帰2件追加後、オフライン全体664件・lint成功。両レビュアーが再レビューし承認、差し戻しなし。
ブラウザー試験の範囲: localStorage adapterと実Web Locksの2タブ競合を検証。Ionic StorageのIndexedDBドライバーを含む統合試験は未実施です。