# データベース設計書

## 概要

Decap CMS（旧 Netlify CMS）は、Gitリポジトリをコンテンツのバックエンドとして利用するヘッドレスCMSである。従来のRDBMS（PostgreSQL, MySQL等）やNoSQL（MongoDB等）データベースは使用しておらず、以下の3層でデータを永続化・管理している。

1. **Gitリポジトリ（プライマリストレージ）**: コンテンツ（エントリ）とメディアファイルの永続的な保存先。GitHub, GitLab, Bitbucket, Gitea, Azure DevOps等のGitホスティングサービスのAPIを介してアクセスする。
2. **localForage（IndexedDB）**: ブラウザ側でのファイルコンテンツキャッシュ、メタデータキャッシュ、ローカルファイルツリーキャッシュ、エントリのローカルバックアップの保存に使用する。
3. **localStorage**: UIの設定状態（ソート順、ビュースタイル、エディタのプレビュー表示設定、コードエディタのキーマップ・テーマ等）の永続化に使用する。

本設計書では、上記3層それぞれのデータストア構造を「テーブル」に見立てて記述する。また、アプリケーション実行時のメモリ内データ構造（Redux Store）についても、論理的なデータモデルとして記載する。

## テーブル一覧

### Gitリポジトリ（プライマリストレージ）

| テーブル名 | 対応エンティティ | 説明 |
| --- | --- | --- |
| Entry (Git File) | EntryValue / EntryObject | Gitリポジトリ内のファイルとして管理されるコンテンツエントリ |
| Media File (Git File) | MediaFile / ImplementationMediaFile | Gitリポジトリ内のメディアフォルダに格納される画像・ファイル等 |
| Unpublished Entry (Git Branch/PR) | UnpublishedEntry | Editorial Workflowにおいてブランチ/PRとして管理される未公開エントリ |

### localForage（IndexedDB）

| テーブル名 | 対応エンティティ | 説明 |
| --- | --- | --- |
| File Content Cache | - | Gitリポジトリから取得したファイルコンテンツのキャッシュ |
| File Metadata Cache | FileMetadata | ファイルの作成者・更新日時等のメタデータキャッシュ |
| Local Tree Cache | LocalTree | ブランチ毎のファイルツリーのローカルキャッシュ |
| Entry Backup | BackupEntry | 編集中エントリのローカルバックアップ |
| GitHub Metadata Cache | - | GitHubバックエンド固有のメタデータキャッシュ |

### localStorage

| テーブル名 | 対応エンティティ | 説明 |
| --- | --- | --- |
| Entries Sort Config | StorageSort | コレクション毎のエントリソート設定 |
| Entries View Style | - | エントリ一覧のビュースタイル（リスト/グリッド） |
| Auth Store | User | 認証済みユーザー情報の永続化 |
| Editor UI Settings | - | エディタのプレビュー表示、スクロール同期、i18n表示、分割ペイン位置 |
| Code Widget Settings | - | コードウィジェットのキーマップ・テーマ設定 |
| Markdown Widget Mode | - | マークダウンウィジェットの編集モード（リッチテキスト/RAW） |
| Netlify Site URL | - | Git Gatewayバックエンド用のサイトURL |
| Auth Token | - | 認証トークン情報 |

### Redux Store（メモリ内データモデル）

| テーブル名 | 対応エンティティ | 説明 |
| --- | --- | --- |
| auth | Auth | 認証状態（ユーザー情報、取得中フラグ、エラー） |
| config | CmsConfig | CMS設定情報（バックエンド設定、コレクション定義、メディア設定等） |
| collections | Collections | コレクション定義一覧 |
| entries | Entries | 公開済みエントリのエンティティとページング情報 |
| entryDraft | EntryDraft | 編集中のエントリドラフト状態 |
| editorialWorkflow | EditorialWorkflow | Editorial Workflowにおける未公開エントリ管理 |
| mediaLibrary | MediaLibrary | メディアライブラリの状態（ファイル一覧、表示URL等） |
| medias | Medias | アセットプロキシ（メディアファイルのインメモリ表現） |
| search | Search | 検索状態（検索クエリ、結果、ページング） |
| integrations | Integrations | 外部サービス連携の設定 |
| cursors | Cursors | ページネーション用カーソル |
| deploys | Deploys | デプロイプレビューの状態 |
| globalUI | GlobalUI | グローバルUI状態（ローディング、Open Authoring） |
| status | Status | APIおよび認証のステータス |
| notifications | NotificationsState | 通知メッセージの管理 |

## 各テーブル定義

### 1. Entry (Git File)

Gitリポジトリ内のファイルとして永続化されるコンテンツエントリ。Folder Collectionではフォルダ内のファイルとして、File Collectionでは指定パスのファイルとして管理される。ファイルフォーマットはYAML, TOML, JSON, Frontmatter等をサポートする。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| collection | string | NO | エントリが属するコレクション名 |
| slug | string | NO | エントリの一意識別子（ファイル名から拡張子を除いたもの） |
| path | string | NO | Gitリポジトリ内のファイルパス |
| raw | string | NO | ファイルの生テキストコンテンツ |
| data | object (any) | NO | パース済みのエントリデータ（フロントマターの内容等） |
| label | string | YES | エントリのラベル（File Collection用） |
| partial | boolean | NO | 部分的な読み込みかどうか（デフォルト: false） |
| isModification | boolean | YES | 既存エントリの変更かどうか |
| mediaFiles | MediaFile[] | NO | エントリに関連するメディアファイルの一覧 |
| author | string | YES | エントリの作成者（Gitのコミット情報由来） |
| updatedOn | string | YES | エントリの最終更新日時（Gitのコミット情報由来） |
| status | string | YES | 公開ステータス（Editorial Workflow使用時） |
| meta | object | YES | メタ情報（パス等） |
| i18n | object | YES | 多言語コンテンツのロケール別データ |
| newRecord | boolean | NO | 新規作成エントリかどうか |

### 2. Media File (Git File)

Gitリポジトリのメディアフォルダに格納される画像やファイル。グローバルメディアフォルダまたはコレクション/エントリ単位のメディアフォルダに保存される。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| name | string | NO | ファイル名 |
| id | string | NO | ファイルの一意識別子（Git SHA等） |
| size | number | YES | ファイルサイズ（バイト） |
| displayURL | DisplayURL | YES | 表示用URL（文字列またはid+pathのオブジェクト） |
| path | string | NO | Gitリポジトリ内のファイルパス |
| draft | boolean | YES | ドラフト（未保存）のメディアファイルかどうか |
| url | string | YES | メディアファイルのURL |
| file | File | YES | ブラウザのFileオブジェクト（アップロード時） |
| field | EntryField | YES | 関連するエントリフィールド定義 |
| key | string | YES | UIレンダリング用の一意キー（UUID） |

### 3. Unpublished Entry (Git Branch/PR)

Editorial Workflowが有効な場合に、ブランチ/プルリクエストとして管理される未公開エントリ。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| slug | string | NO | エントリのスラグ |
| collection | string | NO | コレクション名 |
| status | string | NO | ワークフローステータス（draft / pending_review / pending_publish） |
| diffs | UnpublishedEntryDiff[] | NO | ファイルの差分一覧 |
| updatedAt | string | NO | 最終更新日時 |
| pullRequestAuthor | string | YES | プルリクエスト作成者 |

### 4. File Content Cache (localForage)

Gitリポジトリから取得したファイルコンテンツをブラウザのIndexedDBにキャッシュする。SHA値をキーとしてコンテンツを保存し、同一ファイルの再取得を防ぐ。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key | string | NO | キャッシュキー（`gh.{SHA}` : テキスト、`gh.{SHA}.blob` : バイナリ） |
| value | string / Blob | NO | ファイルのコンテンツ（テキストまたはバイナリ） |

### 5. File Metadata Cache (localForage)

ファイルの作成者や更新日時等のメタデータをキャッシュする。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key | string | NO | キャッシュキー（`gh.{SHA}.meta`） |
| author | string | NO | ファイルの作成者 |
| updatedOn | string | NO | ファイルの更新日時 |

### 6. Local Tree Cache (localForage)

ブランチ毎のファイルツリーをローカルにキャッシュし、差分取得による効率的なエントリ読み込みを実現する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key | string | NO | キャッシュキー（`git.local.{branch}.{folder}.{extension}.{depth}`） |
| head | string | NO | キャッシュ時点のブランチのHEAD SHA |
| files | object[] | NO | ファイル一覧（各要素は id, name, path を持つ） |

### 7. Entry Backup (localForage)

編集中のエントリをローカルにバックアップし、ブラウザの予期せぬ終了時にデータを復元できるようにする。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key | string | NO | バックアップキー（`backup.{collection}.{slug}` または `backup`） |
| raw | string | NO | エントリの生テキストコンテンツ |
| path | string | NO | エントリのファイルパス |
| mediaFiles | MediaFile[] | NO | 関連するメディアファイル一覧 |
| i18n | object | YES | 多言語コンテンツのロケール別生テキスト |

### 8. GitHub Metadata Cache (localForage)

GitHubバックエンド固有のメタデータ（PRメタ情報等）をキャッシュする。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key | string | NO | キャッシュキー（`gh.meta.{key}`） |
| value | object | NO | メタデータの内容（PR情報等） |

### 9. Entries Sort Config (localStorage)

コレクション毎のエントリのソート設定をブラウザに永続化する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key | string (固定) | NO | `decap-cms.entries.sort` |
| value | JSON string | NO | コレクション毎のソートキーとソート方向（Ascending/Descending/None）のJSON |

### 10. Entries View Style (localStorage)

エントリ一覧の表示スタイルを永続化する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key | string (固定) | NO | `decap-cms.entries.viewStyle` |
| value | string | NO | ビュースタイル（VIEW_STYLE_LIST 等） |

### 11. Auth Store (localStorage)

認証済みユーザー情報をlocalStorageに永続化し、セッション復元に使用する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key | string (動的) | NO | バックエンド名に基づくストレージキー |
| value | JSON string | NO | ユーザー情報（トークン、名前、ログイン名等）のJSON |

### 12. Editor UI Settings (localStorage)

エディタ画面のUI設定をブラウザに永続化する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key (previewVisible) | string (固定) | NO | プレビュー表示の有効/無効 |
| key (scrollSyncEnabled) | string (固定) | NO | スクロール同期の有効/無効 |
| key (i18nVisible) | string (固定) | NO | i18n表示の有効/無効 |
| key (splitPanePosition) | string (固定) | NO | 分割ペインの位置（ピクセル値） |

### 13. Code Widget Settings (localStorage)

コードウィジェットのエディタ設定を永続化する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key (keyMap) | string (固定) | NO | キーマップ設定（default等） |
| key (theme) | string (固定) | NO | テーマ設定 |

### 14. Markdown Widget Mode (localStorage)

マークダウンウィジェットの編集モードを永続化する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| key | string (固定) | NO | 編集モードのストレージキー |
| value | string | NO | 編集モード（`rich_text` または `raw`） |

### 15. auth（Redux Store）

認証状態を管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| isFetching | boolean | NO | 認証処理中フラグ |
| user | User | YES | 認証済みユーザー情報（token, name, login, backendName等） |
| error | string | YES | 認証エラーメッセージ |

### 16. config（Redux Store）

CMS全体の設定情報を保持する。設定ファイル（config.yml等）から読み込まれる。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| backend | CmsBackend | NO | バックエンド設定（name, repo, branch, api_root等） |
| collections | CmsCollection[] | NO | コレクション定義の配列 |
| media_folder | string | YES | グローバルメディアフォルダパス |
| public_folder | string | YES | 公開メディアフォルダパス |
| publish_mode | string | YES | 公開モード（simple / editorial_workflow） |
| locale | string | YES | ロケール設定 |
| slug | CmsSlug | YES | スラグ生成設定（encoding, clean_accents等） |
| i18n | CmsI18nConfig | YES | 国際化設定（structure, locales, default_locale） |
| media_library | CmsMediaLibrary | YES | メディアライブラリ設定 |
| integrations | Integration[] | YES | 外部サービス連携設定 |
| site_url | string | YES | サイトURL |
| local_backend | boolean / CmsLocalBackend | YES | ローカルバックエンド設定 |
| isFetching | boolean | NO | 設定取得中フラグ |
| error | string | YES | 設定取得エラーメッセージ |

### 17. collections（Redux Store）

CMS設定から読み込まれたコレクション定義一覧を保持する（OrderedMapとして管理）。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| name | string | NO | コレクション名（一意識別子） |
| label | string | NO | 表示ラベル |
| type | string | NO | コレクションタイプ（folder_based_collection / file_based_collection） |
| folder | string | YES | フォルダコレクションのフォルダパス |
| files | CollectionFiles | YES | ファイルコレクションのファイル定義一覧 |
| fields | EntryFields | YES | フィールド定義一覧 |
| format | Format | YES | ファイルフォーマット（yml, json, frontmatter等） |
| extension | string | YES | ファイル拡張子 |
| create | boolean | YES | 新規エントリ作成の許可 |
| delete | boolean | YES | エントリ削除の許可 |
| slug | string | YES | スラグテンプレート |
| identifier_field | string | YES | 識別フィールド名 |
| summary | string | YES | サマリーテンプレート |
| sortable_fields | List | YES | ソート可能フィールド一覧 |
| view_filters | List | YES | ビューフィルタ定義一覧 |
| view_groups | List | YES | ビューグループ定義一覧 |
| media_folder | string | YES | コレクション単位のメディアフォルダ |
| public_folder | string | YES | コレクション単位の公開フォルダ |
| nested | Nested | YES | ネスト設定（depth等） |
| meta | Meta | YES | メタ設定（path等） |
| i18n | i18n | YES | コレクション単位のi18n設定 |
| preview_path | string | YES | プレビューパステンプレート |
| filter | FilterRule | YES | エントリフィルタルール |

### 18. entries（Redux Store）

公開済みエントリのエンティティとページング情報を管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| entities | Map<string, EntryMap> | NO | エントリエンティティ（キー: `{collection}.{slug}`） |
| pages | Map<string, object> | NO | コレクション毎のページング情報（page, ids, isFetching） |
| sort | Sort (Map) | NO | コレクション毎のソート設定 |
| filter | Filter (Map) | YES | コレクション毎のフィルタ設定 |
| group | Group (Map) | YES | コレクション毎のグループ設定 |
| viewStyle | string | NO | 表示スタイル |

### 19. entryDraft（Redux Store）

現在編集中のエントリドラフトの状態を管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| entry | Entry (EntryMap) | YES | 編集中のエントリデータ |
| fieldsMetaData | Map | NO | フィールドのメタデータ（リレーション解決結果等） |
| fieldsErrors | Map | NO | フィールドのバリデーションエラー |
| hasChanged | boolean | NO | 変更があったかどうか |
| key | string | NO | ドラフトの一意キー（UUID） |
| localBackup | Map | YES | ローカルバックアップから復元されたエントリ |

### 20. editorialWorkflow（Redux Store）

Editorial Workflowにおける未公開エントリの管理状態を保持する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| entities | Map<string, EntryMap> | NO | 未公開エントリエンティティ（キー: `{collection}.{slug}`） |
| pages | Map | NO | ページング情報（ids, isFetching等） |

### 21. mediaLibrary（Redux Store）

メディアライブラリの状態を管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| isVisible | boolean | NO | メディアライブラリの表示状態 |
| showMediaButton | boolean | NO | メディアボタンの表示フラグ |
| controlMedia | Map<string, string> | NO | コントロールに挿入されたメディアパス |
| displayURLs | Map<string, DisplayURLState> | NO | メディアファイルの表示URL状態 |
| externalLibrary | MediaLibraryInstance | YES | 外部メディアライブラリインスタンス |
| controlID | string | YES | 現在操作中のコントロールID |
| files | MediaFile[] | YES | メディアファイル一覧 |
| config | Map | NO | メディアライブラリ設定 |
| field | EntryField | YES | 関連するフィールド定義 |
| isLoading | boolean | NO | 読み込み中フラグ |
| isPersisting | boolean | NO | 保存中フラグ |
| isDeleting | boolean | NO | 削除中フラグ |
| page | number | YES | 現在のページ番号 |
| hasNextPage | boolean | NO | 次ページの有無 |

### 22. medias（Redux Store）

メディアファイルのアセットプロキシ（インメモリ表現）を管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| [path] | object | NO | パスをキーとするアセット情報 |
| [path].asset | AssetProxy | YES | アセットプロキシ（url, fileObj, path, field） |
| [path].isLoading | boolean | NO | 読み込み中フラグ |
| [path].error | Error | YES | 読み込みエラー |

### 23. search（Redux Store）

エントリ検索の状態を管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| isFetching | boolean | NO | 検索実行中フラグ |
| term | string | NO | 検索キーワード |
| collections | string[] | NO | 検索対象コレクション一覧 |
| page | number | NO | 現在のページ番号 |
| entryIds | object[] | NO | 検索結果のエントリID一覧（collection + slug） |
| queryHits | Record<string, EntryValue[]> | NO | クエリ毎のヒット結果 |
| error | Error | YES | 検索エラー |
| requests | QueryRequest[] | NO | 実行中のクエリリクエスト一覧 |

### 24. integrations（Redux Store）

外部サービス連携（Algolia等）の設定を管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| providers | Record<string, object> | NO | プロバイダ設定（プロバイダ名をキー） |
| hooks | Record<string, string/object> | NO | フック設定（コレクション/フック名をキー） |

### 25. cursors（Redux Store）

ページネーション用のカーソル情報を管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| cursorsByType | Map | NO | カーソルタイプ毎の管理 |
| cursorsByType.collectionEntries | Map<string, Cursor> | NO | コレクション名をキーとしたカーソル |

### 26. deploys（Redux Store）

デプロイプレビューの状態を管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| [collection.slug] | object | NO | コレクション+スラグをキーとするデプロイ情報 |
| [collection.slug].isFetching | boolean | NO | 取得中フラグ |
| [collection.slug].url | string | YES | プレビューURL |
| [collection.slug].status | string | YES | デプロイステータス |

### 27. globalUI（Redux Store）

グローバルなUI状態を管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| isFetching | boolean | NO | グローバルローディングフラグ |
| useOpenAuthoring | boolean | NO | Open Authoring機能の使用フラグ |

### 28. status（Redux Store）

バックエンドAPIおよび認証のステータスを管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| isFetching | boolean | NO | ステータス取得中フラグ |
| status.auth.status | boolean | NO | 認証ステータス（正常/異常） |
| status.api.status | boolean | NO | APIステータス（正常/異常） |
| status.api.statusPage | string | NO | ステータスページURL |
| error | Error | YES | ステータス取得エラー |

### 29. notifications（Redux Store）

通知メッセージを管理する。

| カラム名 | データ型 | NULL | 説明 |
| --- | --- | --- | --- |
| notifications | Notification[] | NO | 通知一覧 |
| notifications[].id | string | NO | 通知の一意識別子（UUID） |
| notifications[].message | string / NotificationMessage | NO | 通知メッセージ |
| notifications[].dismissAfter | number | YES | 自動消去までの時間（ミリ秒） |
| notifications[].type | TypeOptions | YES | 通知タイプ（success, error, info等） |

## 備考

### アーキテクチャ上の特徴

1. **Gitをデータベースとして利用**: Decap CMSの最大の特徴は、RDBMSを一切使わずGitリポジトリをコンテンツストアとして利用する点にある。全てのコンテンツ変更はGitコミットとして記録されるため、バージョン管理が自動的に行われる。

2. **ブラウザ側キャッシュの活用**: localForage（IndexedDB）を用いて、Gitリポジトリから取得したファイルコンテンツやメタデータをブラウザ側にキャッシュすることで、Git APIへのリクエスト数を削減し、パフォーマンスを向上させている。

3. **差分取得によるファイルツリー管理**: Local Tree Cacheを活用し、前回取得時からの差分のみをGit APIから取得する仕組みにより、大量のファイルを持つリポジトリでも効率的にエントリ一覧を取得できる。

4. **Editorial Workflow**: `publish_mode: editorial_workflow` を設定すると、エントリの変更がGitブランチとプルリクエストを通じて管理される。ステータスは `draft` → `pending_review` → `pending_publish` の遷移を経て公開される。

5. **マルチバックエンド対応**: GitHub, GitLab, Bitbucket, Gitea, Azure DevOps, Git Gatewayの各バックエンドに対応し、共通のImplementationインターフェースを通じてアクセスされる。

6. **Immutable.jsによるRedux状態管理**: アプリケーション状態の大部分（entries, collections, editorialWorkflow等）はImmutable.jsのデータ構造で管理されている。一部のReducer（auth, config, search, medias等）はimmer（produce）を使用したプレーンオブジェクトベースの状態管理に移行されている。

### localForageのキーパターン

| パターン | 用途 | 保存先 |
| --- | --- | --- |
| `gh.{SHA}` | テキストファイルコンテンツキャッシュ | IndexedDB |
| `gh.{SHA}.blob` | バイナリファイルコンテンツキャッシュ | IndexedDB |
| `gh.{SHA}.meta` | ファイルメタデータキャッシュ | IndexedDB |
| `gh.meta.{key}` | GitHub固有のメタデータキャッシュ | IndexedDB |
| `git.local.{branch}.{folder}.{extension}.{depth}` | ローカルファイルツリーキャッシュ | IndexedDB |
| `backup.{collection}.{slug}` | エントリバックアップ（コレクション+スラグ指定） | IndexedDB |
| `backup` | エントリバックアップ（汎用） | IndexedDB |

### localStorageのキーパターン

| パターン | 用途 |
| --- | --- |
| `decap-cms.entries.sort` | エントリソート設定 |
| `decap-cms.entries.viewStyle` | エントリ表示スタイル |
| `decap-cms-auth` | 認証トークン情報 |
| `netlifySiteURL` | Git Gatewayサイト設定 |
