# ストレージと再起動

> Calendarpit がデータを保存する場所、SQLite とインメモリモードの選び方、再起動やリセットの後に残るデータについて説明します。

Calendarpit は、デフォルトでワークスペースの状態をローカルの SQLite ファイルに保存します。使い捨てのテストセッションでは、インメモリに保存することもできます。

## ストレージモードの選び方

Homebrew や単体実行ファイルでは、デフォルトで起動したディレクトリ内の `.calendarpit/calendarpit.sqlite` に保存します。プロジェクトのディレクトリから起動すると、プロジェクトごとにデータを保持できます。

保存先や `:memory:` は `--database <path>` または `CALENDARPIT_DATABASE` で指定します。CLI フラグは環境変数より優先されます。必要な環境と公開状況は[インストール](https://calendarpit.dev/ja/installation.md)で確認してください。

```sh
# Temporary E2E session; no database files are written
calendarpit serve --database :memory: --port 0

# Choose a persistent workspace
calendarpit serve --database ./.calendarpit/manual.sqlite
```

### モード

- **SQLite ファイル（デフォルト）**: 状態を変更するリクエストが終わるたびに、変更を保存します。Webhook の配信が終わったときにも保存します。相対パスは作業ディレクトリを基準に解決されます。
- **インメモリ（`:memory:`）**: データベースファイルもロックファイルも書き込みません。プロセスが終了すると、すべての状態が破棄されます。
- **Docker**: イメージでは `CALENDARPIT_DATABASE=/data/calendarpit.sqlite` が設定されています。
  - コンテナを削除して作り直してもデータを残すには、`-v calendarpit-data:/data` で名前付きボリュームをマウントします。
  - 一時的なセッションには `-e CALENDARPIT_DATABASE=:memory:` を渡します。
  - [Compose の例](https://calendarpit.dev/ja/installation.md)も名前付きボリュームを使います。`docker compose down` ではデータが残り、`docker compose down -v` では削除されます。

### 使用中のモードを確認する

使用中のストレージモードとパスは、次の 3 か所で確認できます。

- **起動ログ**: CLI は `Data   <absolute-path>`（または `Data   :memory:`）を出力します。サーバーのエントリが読み込まれた時点で `Calendarpit data: <path or :memory:>` も出力します。
- **管理画面**: サイドバーのフッターに `SQLite` または `In-memory` が表示されます。**Settings → Test data** には、モードの説明とパス（または `:memory:`）が表示されます。
- **管理 API**: `GET /__calendarpit/health` は `storage: "sqlite" | "memory"` を返します。`GET /__calendarpit/state` は `storage: { mode, path? }` を返します。

## SQLite の再起動後に残るデータ

既存の SQLite ファイルを開き直すと、保存済みのデータが復元されます。短時間しか使わないフローの状態は破棄されます。

`SIGINT` または `SIGTERM` を受け取ると、Calendarpit は次の順に処理します。

1. 処理中の Webhook 配信を中断します。
2. 状態を保存します。
3. データベースのロックを解放します。

再起動後の扱いは、状態の種類ごとに次のとおりです。

| 状態                                                        | SQLite の再起動後の扱い                                            |
| ----------------------------------------------------------- | ------------------------------------------------------------------ |
| ユーザー、カレンダー、イベント、OAuth クライアント          | 復元されます。イベント ID、タイムスタンプ、etag もそのまま残ります |
| カレンダーリストの設定                                      | 復元されます。リセットまたは Fixture の読み込みで消去されます      |
| リセット基準（起動時 Fixture）                              | 最初の初期データが保持されます                                     |
| OAuth 接続（grant）、アクセストークン、リフレッシュトークン | 復元されます                                                       |
| watch チャネル                                              | 下記の条件を満たす場合だけ復元されます                             |
| リクエスト履歴、配信ログ、通知                              | それぞれ最新の 200 件が復元されます                                |
| キュー内または処理中の Webhook 配信                         | `cancelled` になります。自動で再送されることはありません           |
| 同期トークン、カレンダーの削除済みレコード                  | 復元されます。リセットまたは Fixture の読み込みで消去されます      |
| 保留中の同意、認可コード、ページトークン                    | 破棄されます。フローまたは一覧の取得をやり直してください           |

一部の行の補足です。

- **カレンダーリストの設定**には、ユーザーごとの上書き・色・非表示/選択状態・リマインダー・削除したエントリが含まれます。
- **リセット基準**: `--config` または `CALENDARPIT_CONFIG` のファイルの内容は無視されます。
- **トークンと接続**:
  - アクセストークンは元の有効期限のままです。
  - リフレッシュトークンは期限切れになりません。
  - 取り消した接続は取り消されたままです。
- **watch チャネル**は、次のすべてを満たす場合に復元されます。復元されたチャネルはメッセージ番号も引き継ぎます。
  - 有効期限内である。
  - 接続が有効である。
  - コールバック先のホストが引き続き許可されている。
- **ページトークン**は `calendarList` と `events` のカーソルです。

再起動後は、アプリ側でカレンダーの状態を再取得してください。停止によって中断された[watch 通知](https://calendarpit.dev/ja/watch.md)の差分を取り込めます。

## 初期データ、Fixture、リセット

起動時[Fixture](https://calendarpit.dev/ja/fixtures.md)がデータを投入するのは、**新しいデータベースのときだけ**です。起動時 Fixture は、`--config`、`CALENDARPIT_CONFIG`、または組み込みのデフォルトから読み込まれます。

- **既存の SQLite ファイル**: 保存済みのデータと、最初のリセット基準を開き直します。
  - `--config` または `CALENDARPIT_CONFIG` を設定していても、Fixture の内容は無視され、既存のデータベースは上書きされません。
  - ただし、指定したファイルが存在し、有効な JSON である必要があります。
- **インメモリモード**: 起動するたびに新しいデータベースになります。そのため、起動時 Fixture は毎回適用され、省略した ID も毎回生成し直されます。
- **リセット（`POST /__calendarpit/reset`）**: 再起動をはさんでいても、起動時のリセット基準に戻します。
  - 接続・トークン・認可コード・同意・watch チャネル・カレンダーリストの設定・[同期トークン](https://calendarpit.dev/ja/sync.md)・ページトークン・カレンダーの削除済みレコード・リクエスト履歴・通知・配信を消去します。
  - すぐに保存します。
  - SQLite ファイルを削除したり、Fixture のファイルを変更したりはしません。
- **Fixture の読み込み（`POST /__calendarpit/fixtures`）**: 現在のデータを置き換え、実行時の状態を消去します。**リセット基準は変わりません。** その後にリセットしても、最初の初期データに戻ります。詳しくは[テスト制御 API](https://calendarpit.dev/ja/test-control.md)を参照してください。

## ロック、ファイル、並列実行

### 1 つのファイルに 1 プロセス

1 つの SQLite ファイルを同時に使えるのは、1 つの Calendarpit プロセスだけです。

- Calendarpit は実行中、`<db>.lock` に排他ロックをかけます。
- 同じファイルを使う 2 つ目のプロセスは、起動時に `Database is already in use: <path>. Use a separate path or :memory:.` というエラーで失敗します。CLI ではこの前に `Calendarpit: ` が付きます。
- ロックはプロセスの終了時に OS が解放します。クラッシュした場合も同じです。

テストを並列に実行する場合は、ワーカーごとに `--database :memory:` を使うか、別々の SQLite ファイルを指定してください。

### 補助ファイルとパーミッション

SQLite は WAL モードで動作します。Calendarpit は実行中、データベースと同じ場所に次の補助ファイルを作成します。

- `<db>-wal`
- `<db>-shm`
- `<db>.lock`
- `<db>.lock-journal`

データベース、`-wal`、`-shm`、`.lock` の各ファイルはモード `0600` になります。Calendarpit がデータベース用のディレクトリを作成した場合、そのディレクトリはモード `0700` になります。

実行中は補助ファイルをデータベースと同じ場所に置いたままにしてください。データベースファイルをコピーするときは、先にサーバーを停止してください。

### データベースを Git に含めない

> [!WARNING]
>
> データベースにはローカルのテスト用認証情報とクライアントシークレットが入っています。Git には含めないでください。
>

このリポジトリでは、次のものが Git で無視されます。

- `.calendarpit/`
- `*.sqlite`、`*.sqlite-wal`、`*.sqlite-shm`、`*.sqlite.lock` のパターン
- `<db>.lock-journal`（`.calendarpit/` の中にある場合だけ）
