capacitor-offline-first について
Capacitor アプリのオフラインファースト設計を支援する開発スキルです。SQLite によるデータ保存、outbox を使った同期、競合処理、プラットフォーム固有の注意点を扱います。
主な機能
- 01SQLite によるローカルデータ保存
- 02トランザクション対応の同期 outbox
- 03競合と削除の方針
- 04Capacitor のプラットフォーム別トラブル対応
活用シーン
- 01オフライン用レコード保存の設計
- 02同期時の書き込み重複の防止
- 03iOS でのデータ消失の調査
- 04ネイティブプラグインの利用維持
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ cap-go/ capgo-skills --skill capacitor-offline-firstインストール後、Claude Code で/capacitor-offline-firstを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.jsonreferences/fast-sql-setup.mdsync-engine.md詳細
オフラインファーストの Capacitor アプリ
使用する場面
次の場合に使用します:
- アプリがオフライン中も読み書きでき、後で同期する必要がある
- ユーザーデータ用のローカルストレージ(Preferences、SQLite、IndexedDB)を選択する
- 同期中に書き込みが重複、消失、または上書きされる
- ストレージ不足が発生した後、iOS でキャッシュデータが消える
- サービスワーカーを追加したところ、ネイティブプラグインが動作しなくなった(
Plugin not implemented)
次の場合は使用しないでください:
@capacitor-community/sqlite/Ionic セキュアストレージの置き換え:sqlite-to-fast-sql- 新しい Web コードをデバイスに配信(OTA):
capgo-live-updates - 暗号化キーの保存、SSL ピンニング:
capacitor-security - クエリ速度/ブリッジのオーバーヘッド調整:
capacitor-performance
判断事項(コードを書く前に決めてください)
| 質問 | デフォルトの回答 |
|---|---|
| UI はどこから読み取りますか? | ローカル DB のみです。ネットワークからの書き込みは DB に行い、UI は DB を監視します。 |
| レコードのストレージ | @capgo/capacitor-fast-sql 経由の SQLite |
| 小さな設定/トークンのストレージ | @capacitor/preferences(シークレットには使用しません。capacitor-security を参照してください) |
| localStorage/IndexedDB | 一時データのみ:iOS はストレージに負荷がかかると WebView のストレージを削除することがあります |
| アプリシェルのオフライン対応 | すでにオフライン対応です:Capacitor は Web アセットをバイナリにバンドルします(または Capgo のライブアップデートバンドルを使用します)。サービスワーカーは不要です。 |
| ネイティブビルドでのサービスワーカー | 避けてください。Android の SW は Capacitor プラグインの挿入を妨げます。iOS の SW には WKAppBoundDomains が必要で、これも ios.limitsNavigationsToAppBoundDomains を設定しない限り挿入を妨げます。SW は PWA ビルドでのみ使用してください。 |
| ID | クライアントで生成する UUID(crypto.randomUUID())。オフラインでの作成にサーバーとの往復が不要になります |
| 競合ポリシー | ユーザー/プロダクトオーナーに確認します:レコード単位の最終書き込み優先、フィールド単位のマージ、またはユーザーによる確認を伴うサーバー優先 |
| 削除 | サーバーが確認するまでソフト削除(トゥームストーン)を使用します |
ワークフロー
- 調査:現在のストレージ(
localStorage、@capacitor/preferences、その他の SQLite プラグイン)、API の仕様(updated_since/バージョンフィールド/冪等性キーに対応しているか)、認証の更新フロー、既存のサービスワーカー登録を確認します。 - 計画を報告し、広範囲に及ぶ変更を行う前に、スキーマ、送信待ちキュー、競合ポリシーをユーザーに伝えます。大規模なアプリでは、エンティティごとに TODO を列挙します。
- インストール:
npm install @capgo/capacitor-fast-sql @capacitor/network && npx cap syncを実行し、references/fast-sql-setup.md に記載されたプラットフォーム設定を行います(必須:iOS の ATS ローカルネットワーク、Android の localhost クリアテキスト通信)。 - スキーマと送信待ちキュー:references/sync-engine.md を参照してください。
- 同期のトリガー:アプリ起動時、
networkStatusChangeで接続時、App のresume時、各ローカル書き込みの後(デバウンス)、必要に応じてバックグラウンドタスク。 - UI:送信待ち件数/最終同期時刻を表示し、ネットワークを理由に書き込みをブロックしないでください。
- 検証(以下を参照)。
関連するトピックのリファレンスのみを読み込んでください。
最小構成
import { FastSQL } from '@capgo/capacitor-fast-sql';
import { Network } from '@capacitor/network';
import { App } from '@capacitor/app';
const db = await FastSQL.connect({ database: 'app' });
await db.execute(`CREATE TABLE IF NOT EXISTS outbox (
id TEXT PRIMARY KEY, entity TEXT NOT NULL, op TEXT NOT NULL,
payload TEXT NOT NULL, created_at INTEGER NOT NULL, attempts INTEGER NOT NULL DEFAULT 0
)`);
export async function saveTodo(todo: Todo) {
await db.transaction(async (tx) => {
await tx.run('INSERT OR REPLACE INTO todos (id, text, done, updated_at, dirty) VALUES (?, ?, ?, ?, 1)',
[todo.id, todo.text, todo.done ? 1 : 0, Date.now()]);
await tx.run('INSERT INTO outbox (id, entity, op, payload, created_at) VALUES (?, ?, ?, ?, ?)',
[crypto.randomUUID(), 'todo', 'upsert', JSON.stringify(todo), Date.now()]);
});
void sync(); // fire and forget; sync() is single-flight
}
Network.addListener('networkStatusChange', (s) => { if (s.connected) void sync(); });
App.addListener('resume', () => void sync());
データ行と送信待ちキューのエントリーは、クラッシュによって片方だけが残ることのないよう、1 つのトランザクションで書き込みます。
注意点
Network.getStatus().connected === trueでも API に到達できるとは限りません(キャプティブポータル、VPN、サーバーダウンなど)。fetch の失敗はオフラインとして扱い、connectedは再試行のヒントとしてのみ使用します。networkStatusChangeは連続して複数回発生することがあります。sync()は単一実行にしてください。- すべての変更リクエストに冪等性キー(送信待ちキューの行 ID)を付けてください。タイムアウト後に再試行すると、付けていない場合はレコードが重複します。
- エンティティごとに順番どおり送信待ちキューを処理してください。削除が作成より先に処理されてはなりません。
- プルのカーソルには、デバイスの時計ではなく、レスポンスに含まれるサーバーのタイムスタンプ/バージョンを使用してください。
- オフライン中に認証トークンが期限切れになります:送信待ちキューを処理する前に更新し、401 の場合もエントリーを破棄せず保持してください。
- SQLite の行に大きな Blob を保存しないでください。
@capacitor/filesystemでファイルを保存し、DB にはパスを保持してください。 - iOS のバックグラウンド実行は状況に左右されます。バックグラウンドタスクは役立ちますが、同期タイミングは保証できません。
resume時のフォアグラウンド同期が確実な方法です。
検証
- デバイスで機内モードをオンにし、レコードを作成、編集、削除します。アプリを強制終了して再起動しても、データが保持されていることを確認します。
- ネットワークを再びオンにします。アウトボックスが 0 件になり、サーバーにすべての変更が一度だけ反映される(重複がない)ことを確認します。
- 競合テスト:2 台のデバイスをオフラインにして同じレコードを編集し、両方を再接続します。結果が選択したポリシーに一致することを確認します。
- 不安定なネットワーク:Android エミュレーターのネットワークスロットリング/iOS Network Link Conditioner(設定 -> デベロッパ)を使用します。再試行の間隔が徐々に長くなり、データが失われないことを確認します。
- ネイティブの確認:
grep -rn "serviceWorker.register" src/ | grep -v "isNativePlatform" # should be empty or web-guarded
/usr/libexec/PlistBuddy -c "Print :NSAppTransportSecurity:NSAllowsLocalNetworking" ios/App/App/Info.plist
grep -n "networkSecurityConfig\|usesCleartextTraffic" android/app/src/main/AndroidManifest.xml
- DB を確認します。デバッグ画面で
SELECT COUNT(*) FROM outboxを表示するか、ファイルを取得します(adb exec-out run-as com.example.app ls databases/)。
エラー処理
| エラー/症状 | 原因 | 解決方法 |
|---|---|---|
"CapgoCapacitorFastSql" plugin is not implemented on android / Plugin not implemented | サービスワーカーまたは WKAppBoundDomains によるインジェクションのブロック、あるいは npx cap sync の未実行 | ネイティブビルドから SW を削除する/ios.limitsNavigationsToAppBoundDomains を設定する。npx cap sync を実行する |
iOS:The resource could not be loaded because the App Transport Security policy requires the use of a secure connection | Fast SQL の localhost 通信が ATS によってブロックされている | NSAllowsLocalNetworking を追加する |
Android:CLEARTEXT communication to localhost not permitted by network security policy | localhost へのクリアテキスト通信がブロックされている | localhost の例外を network_security_config.xml に追加する |
pull 中の SQLITE_CONSTRAINT: UNIQUE constraint failed | サーバーの行 ID がローカルにすでに存在する | INSERT ... ON CONFLICT(id) DO UPDATE を使用する |
database is locked | 並列書き込み/長時間のトランザクション | 書き込みを直列化する。トランザクションを短く保つ。同期を単一実行にする |
| iOS のストレージ圧迫後にデータが消失する | localStorage/IndexedDB に保存されている | SQLite に移行する |
| 再接続後にサーバーレコードが重複する | 冪等性キーなしで POST が再試行されている | outbox の ID を Idempotency-Key として送信する。サーバー側で重複を排除する |
リソース
- Fast SQL プラグイン:https://capgo.app/docs/plugins/fast-sql/
- Capacitor ストレージガイド:https://capacitorjs.com/docs/guides/storage
- ネットワーク API:https://capacitorjs.com/docs/apis/network
- バックグラウンドタスクプラグイン:https://capgo.app/docs/plugins/background-task/
- iOS のトラブルシューティング(WKAppBoundDomains):https://capacitorjs.com/docs/ios/troubleshooting
コメント
Markdown 対応