# 機能設計書 7-エントリ読み込み

## 概要

本ドキュメントは、Decap CMSのエントリ読み込み機能の設計を記載する。単一エントリおよびエントリ一覧の読み込みを行い、カーソルベースのページネーションに対応する。

### 本機能の処理概要

本機能は、Gitリポジトリに保存されたコンテンツエントリをバックエンドから読み込む。単一エントリの読み込み（`loadEntry`）とコレクション全体のエントリ一覧読み込み（`loadEntries`）の2つの主要操作を提供し、ページネーション（`traverseCollectionCursor`）によるインクリメンタルな読み込みもサポートする。

**業務上の目的・背景**：CMSのコンテンツ管理において、エントリの閲覧は最も基本的な操作である。コレクション一覧画面でのエントリリスト表示や、エントリ編集画面でのデータ取得に使用される。大量のエントリを持つコレクションではカーソルベースのページネーションにより、パフォーマンスを維持しながらデータを取得する。

**機能の利用シーン**：コレクション一覧画面でのエントリリスト表示、エントリ編集画面でのデータ読み込み、ネストコレクションのツリー構築、i18n対応コレクションの全エントリ取得。

**主要な処理内容**：
1. `loadEntry`: 単一エントリの読み込み。バックエンドの`getEntry()`を呼び出し、結果からドラフトを作成
2. `loadEntries`: コレクションのエントリ一覧読み込み。ソート設定、デフォルトソート、ページネーション対応
3. `traverseCollectionCursor`: カーソルベースのページネーション操作（次ページ読み込み等）
4. `getAllEntries`: 全エントリの取得（ソート・フィルタ・グルーピング用）

**関連システム・外部連携**：各Gitバックエンド（GitHub, GitLab, Bitbucket等）のAPI、統合検索（Algolia等）。

**権限による制御**：認証済みユーザーのみがエントリを読み込める。Open Authoringモードではフォークリポジトリからの読み込みとなる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | コレクション一覧画面 | 主画面 | 選択されたコレクションのエントリ一覧をバックエンドから読み込み。カーソルベースのページネーションに対応 |
| 5 | コレクションフィルター画面 | 主画面 | フィルター条件に基づくエントリの読み込みと表示 |
| 6 | エントリ編集画面 | 主画面 | 既存エントリのバックエンドからの読み込み。loadEntryアクションによるデータ取得 |

## 機能種別

データ読み込み（Read）/ ページネーション

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| collection | Collection (ImmutableMap) | Yes | 対象コレクション | - |
| slug | string | Yes（loadEntry） | エントリのスラッグ | - |
| page | number | No（loadEntries） | ページ番号（デフォルト: 0） | - |
| action | string | Yes（traverseCursor） | ページネーションアクション（'next', 'append_next'等） | カーソルがアクションをサポートしているか |

### 入力データソース

- Reduxストア（config, entries, integrations, cursors）
- バックエンドAPI（listEntries, getEntry, listAllEntries）
- 統合プロバイダー（Algolia等）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| entry | EntryValue | 単一エントリデータ（data, slug, path, mediaFiles等） |
| entries | EntryValue[] | エントリ一覧 |
| cursor | Cursor | ページネーションカーソル |
| pagination | number | ページ番号 |

### 出力先

- Reduxストア（entries状態スライス、cursors状態スライス、entryDraft状態スライス）
- 通知システム（エラー時のトーストメッセージ）

## 処理フロー

### 処理シーケンス

```
[loadEntry]
1. waitForMediaLibraryToLoad()でメディアライブラリの準備を待機
2. ENTRY_REQUESTをディスパッチ
3. backend.getEntry(state, collection, slug)でエントリ取得
4. ENTRY_SUCCESSをディスパッチ
5. createDraftFromEntry(entry)でドラフト作成

[loadEntries]
1. collection.isFetchingチェック（重複読み込み防止）
2. ユーザー設定済みソートフィールドの確認
   └─ 設定済み: sortByField()にデリゲート
3. デフォルトソートフィールドの確認
   └─ 設定済み: sortByField()にデリゲート
4. ENTRIES_REQUESTをディスパッチ
5. ネストコレクション/i18nの場合: listAllEntries()で全件取得
   └─ 通常: listEntries(collection, page)でページ単位取得
6. カーソル作成（統合の場合は旧ページネーションAPI、それ以外は標準カーソル）
7. ENTRIES_SUCCESSをディスパッチ

[traverseCollectionCursor]
1. isFetchingチェック
2. appendActionsマッピング（append_next -> {action: 'next', append: true}）
3. カーソルが旧ページネーションAPIの場合: loadEntries(collection, nextPage)にデリゲート
4. ENTRIES_REQUESTをディスパッチ
5. backend.traverseCursor(cursor, action)で次ページ取得
6. ENTRIES_SUCCESSをディスパッチ（appendフラグ付き）
```

### フローチャート

```mermaid
flowchart TD
    A[エントリ読み込み開始] --> B{読み込み種別}
    B -->|単一エントリ| C[loadEntry]
    B -->|一覧| D[loadEntries]
    B -->|ページネーション| E[traverseCollectionCursor]
    C --> F[ENTRY_REQUEST dispatch]
    F --> G[backend.getEntry]
    G --> H{成功?}
    H -->|Yes| I[ENTRY_SUCCESS dispatch]
    I --> J[createDraftFromEntry]
    H -->|No| K[エラー通知 + ENTRY_FAILURE]
    D --> L{ソート設定あり?}
    L -->|Yes| M[sortByField にデリゲート]
    L -->|No| N{デフォルトソートあり?}
    N -->|Yes| M
    N -->|No| O[ENTRIES_REQUEST dispatch]
    O --> P{ネスト or i18n?}
    P -->|Yes| Q[listAllEntries]
    P -->|No| R[listEntries]
    Q --> S[ENTRIES_SUCCESS dispatch]
    R --> S
    E --> T{旧ページネーション?}
    T -->|Yes| D
    T -->|No| U[backend.traverseCursor]
    U --> S
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 重複読み込み防止 | collection.isFetchingがtrueの場合は読み込みをスキップ | loadEntries, traverseCollectionCursor |
| BR-02 | ソート優先 | ユーザー設定ソート > デフォルトソート > ページネーション読み込みの順で処理を決定 | loadEntries |
| BR-03 | ネスト/i18n全件取得 | ネストコレクションまたはi18n対応コレクションはページネーションせず全件取得 | loadEntries |
| BR-04 | 旧ページネーションAPI | 統合（Algolia等）使用時は整数ベースのページネーション | 統合プロバイダー使用時 |
| BR-05 | append_nextアクション | 'append_next'カーソルアクションは既存エントリに追加する | カーソルページネーション時 |
| BR-06 | 旧APIの逆順表示 | 旧ページネーションAPIのエントリはreverse()で逆順にする | usingOldPaginationAPI時 |

### 計算ロジック

特になし。

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象ストア | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 単一読み込み開始 | entries | UPDATE | ENTRY_REQUEST: コレクション名+スラッグで読み込み開始 |
| 単一読み込み成功 | entries | UPDATE | ENTRY_SUCCESS: エントリデータを格納 |
| 単一読み込み失敗 | entries | UPDATE | ENTRY_FAILURE: エラー情報を格納 |
| 一覧読み込み開始 | entries | UPDATE | ENTRIES_REQUEST: コレクション名で読み込み開始 |
| 一覧読み込み成功 | entries | UPDATE | ENTRIES_SUCCESS: エントリ一覧+カーソルを格納 |
| 一覧読み込み失敗 | entries | UPDATE | ENTRIES_FAILURE: エラー情報を格納 |
| ドラフト作成 | entryDraft | UPDATE | DRAFT_CREATE_FROM_ENTRY: エントリからドラフトを作成 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ENTRY_FAILURE | エントリ取得エラー | backend.getEntry()がエラーを返す | トースト通知（'ui.toast.onFailToLoadEntries'、8000ms） |
| ENTRIES_FAILURE | 一覧取得エラー | listEntries/listAllEntriesがエラーを返す | トースト通知（'ui.toast.onFailToLoadEntries'、8000ms） |
| Error | カーソルアクション未サポート | カーソルが要求されたアクションをサポートしない | 例外をスロー |

### リトライ仕様

エントリ読み込みのリトライ機構は実装されていない。ページリロードまたは画面遷移で再読み込みされる。

## トランザクション仕様

クライアントサイド処理のためトランザクション管理は不要。

## パフォーマンス要件

- ネストコレクション/i18nコレクションは全件取得が必要なため、エントリ数が多い場合にパフォーマンスへの影響がある
- カーソルベースのページネーションにより、通常コレクションでは必要なページのみ取得
- メディアライブラリの読み込み完了を待機してからエントリを取得（loadEntry）

## セキュリティ考慮事項

- エントリの読み込みは認証済みバックエンドAPIを介して行われる
- 統合プロバイダー（Algolia等）使用時はバックエンドのgetTokenでトークンを取得

## 備考

- CursorはImmutableベースのページネーション状態管理オブジェクト
- appendActionsマップにより、'append_next'が内部的に{action: 'next', append: true}に変換される
- loadEntryの成功後に自動的にcreateDraftFromEntryが呼び出され、編集用ドラフトが準備される

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Entry.ts | `packages/decap-cms-core/src/valueObjects/Entry.ts` | EntryValue型の定義（data, slug, path, mediaFiles） |
| 1-2 | types/redux.ts | `packages/decap-cms-core/src/types/redux.ts` | Collection, EntryMap, State等の型定義 |

**読解のコツ**: Immutable.jsのMapとListが多用されている。`collection.get('name')`のようなアクセスパターンに慣れる必要がある。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | entries.ts (actions) | `packages/decap-cms-core/src/actions/entries.ts` | loadEntry, loadEntries, traverseCollectionCursor |

**主要処理フロー**:
1. **46-87行目**: アクションタイプ定数の定義
2. **93-159行目**: 基本アクションクリエーター（entryLoading, entryLoaded, entriesLoading, entriesLoaded等）
3. **530-553行目**: loadEntry - 単一エントリ読み込み
4. **555-558行目**: tryLoadEntry - バックエンドからのエントリ取得
5. **575-658行目**: loadEntries - エントリ一覧読み込み
6. **660-710行目**: traverseCollectionCursor - ページネーション操作
7. **161-169行目**: getAllEntries - 全エントリ取得

#### Step 3: カーソル管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | entries.ts (actions) | `packages/decap-cms-core/src/actions/entries.ts` | appendActions, addAppendActionsToCursor |

**主要処理フロー**:
- **561-563行目**: appendActionsの定義（'append_next' -> {action: 'next', append: true}）
- **565-573行目**: addAppendActionsToCursor - カーソルにappendアクションを追加

### プログラム呼び出し階層図

```
loadEntry(collection, slug)
    |
    +-- waitForMediaLibraryToLoad()
    +-- entryLoading()  [dispatch]
    +-- tryLoadEntry()
    |       +-- currentBackend(config)
    |       +-- backend.getEntry(state, collection, slug)
    +-- entryLoaded()  [dispatch]
    +-- createDraftFromEntry()  [dispatch]

loadEntries(collection, page)
    |
    +-- selectEntriesSortFields()
    |       +-- [ソートあり] sortByField()  [dispatch]
    +-- selectDefaultSortField()
    |       +-- [デフォルトソートあり] sortByField()  [dispatch]
    +-- entriesLoading()  [dispatch]
    +-- [ネスト/i18n] provider.listAllEntries()
    +-- [通常] provider.listEntries()
    +-- addAppendActionsToCursor()
    +-- entriesLoaded()  [dispatch]

traverseCollectionCursor(collection, action)
    |
    +-- selectCollectionEntriesCursor()
    +-- [旧API] loadEntries()
    +-- entriesLoading()  [dispatch]
    +-- backend.traverseCursor()
    +-- entriesLoaded()  [dispatch]
```

### データフロー図

```
[入力]                          [処理]                              [出力]

collection + slug --------> loadEntry() --------> Reduxストア(entries)
                            backend.getEntry()     Reduxストア(entryDraft)

collection + page --------> loadEntries() -------> Reduxストア(entries.pages)
                            backend.listEntries()   Reduxストア(cursors)

cursor + action ----------> traverseCursor() ----> Reduxストア(entries.pages)
                            backend.traverseCursor() Reduxストア(cursors)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| entries.ts (actions) | `packages/decap-cms-core/src/actions/entries.ts` | ソース | エントリ読み込みアクション（loadEntry, loadEntries, traverseCollectionCursor） |
| entries.ts (reducer) | `packages/decap-cms-core/src/reducers/entries.ts` | ソース | エントリ状態のリデューサー |
| cursors.ts (reducer) | `packages/decap-cms-core/src/reducers/cursors.ts` | ソース | カーソル状態のリデューサー |
| entryDraft.ts (reducer) | `packages/decap-cms-core/src/reducers/entryDraft.ts` | ソース | エントリドラフト状態のリデューサー |
| backend.ts | `packages/decap-cms-core/src/backend.ts` | ソース | バックエンド抽象化レイヤー |
| Entry.ts | `packages/decap-cms-core/src/valueObjects/Entry.ts` | ソース | EntryValue型とcreateEntry関数 |
| collections.ts (reducer) | `packages/decap-cms-core/src/reducers/collections.ts` | ソース | selectFields, selectDefaultSortField |
| i18n.ts | `packages/decap-cms-core/src/lib/i18n.ts` | ソース | hasI18n関数 |
