# 機能設計書 40-Resume Data Cache

## 概要

本ドキュメントは、Next.jsにおけるResume Data Cache機能の設計を記述する。レンダリング再開時のデータキャッシュ機構であり、PPR（Partial Prerendering）のプリレンダリング段階で収集されたデータを保存・復元し、レンダリング再開時に再利用するための仕組みを提供する。

### 本機能の処理概要

**業務上の目的・背景**：PPRでは、ビルド時に静的シェルをプリレンダリングし、リクエスト時に動的部分のレンダリングを「再開」する。Resume Data Cacheは、プリレンダリング段階で取得されたデータ（use cacheの結果、fetchレスポンス、Server Actionsの暗号化バウンドargs等）をシリアライズして保存し、再開時に再利用することで、不要なデータ再取得を避けパフォーマンスを最適化する。

**機能の利用シーン**：PPRが有効なページのレンダリング再開時、use cacheディレクティブで キャッシュされた関数の結果復元時、プリレンダリングされたfetchレスポンスの再利用時に利用される。

**主要な処理内容**：
1. `PrerenderResumeDataCache`によるプリレンダリング時のミュータブルキャッシュ構築
2. `RenderResumeDataCache`によるレンダリング時のイミュータブルキャッシュ参照
3. `stringifyResumeDataCache`によるキャッシュのシリアライズ（zlib圧縮 + Base64エンコード）
4. `createRenderResumeDataCache`によるキャッシュの復元（Base64デコード + zlib展開）
5. UseCacheCacheStore、FetchCacheStore、EncryptedBoundArgsCacheStoreの管理

**関連システム・外部連携**：PPR（No.38）、Staged Rendering（No.39）、use cacheディレクティブ、fetch API。

**権限による制御**：権限による制御は存在しない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | Resume Data Cacheはレンダリングインフラの内部機構である |

## 機能種別

キャッシュ管理 / データシリアライゼーション

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| resumeDataCache | RenderResumeDataCache \| PrerenderResumeDataCache | Yes（シリアライズ時） | シリアライズ対象のキャッシュ | - |
| persistedCache | string | Yes（復元時） | Base64エンコードされた圧縮キャッシュ文字列 | 有効なBase64文字列 |
| isCacheComponentsEnabled | boolean | Yes（シリアライズ時） | キャッシュコンポーネントの有効フラグ | - |
| maxPostponedStateSizeBytes | number \| undefined | No | 最大圧縮サイズ制限（バイト） | 正の整数 |

### 入力データソース

- プリレンダリングプロセスからのキャッシュデータ
- postponed state（延期された状態）からのシリアライズされたキャッシュ文字列

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| PrerenderResumeDataCache | PrerenderResumeDataCache | ミュータブルなプリレンダリング用キャッシュ |
| RenderResumeDataCache | RenderResumeDataCache | イミュータブルなレンダリング用キャッシュ |
| serializedCache | string | Base64エンコードされた圧縮キャッシュ文字列（'null'の場合は空） |

### 出力先

- 内部メモリ（レンダリング中のキャッシュ参照）
- ファイルシステム（postponed state保存時）

## 処理フロー

### 処理シーケンス

```
1. プリレンダリング段階
   └─ createPrerenderResumeDataCache()で空のミュータブルキャッシュを作成
   └─ use cacheの結果をcacheストアに保存
   └─ fetchレスポンスをfetchストアに保存
   └─ 暗号化バウンドargsをencryptedBoundArgsストアに保存
2. キャッシュのシリアライズ
   └─ stringifyResumeDataCache()を呼び出し
   └─ UseCacheCacheStoreのシリアライズ（ReadableStream → Base64）
   └─ zlib.deflateSync()で圧縮
   └─ Base64エンコード
3. レンダリング再開段階
   └─ createRenderResumeDataCache(persistedCache)を呼び出し
   └─ Base64デコード → zlib.inflateSync()で展開
   └─ JSONパース → イミュータブルキャッシュの構築
   └─ parseUseCacheCacheStore()でストリームを復元
4. キャッシュの利用
   └─ cache.get()でuse cacheの結果を取得
   └─ fetch.get()でfetchレスポンスを取得
```

### フローチャート

```mermaid
flowchart TD
    A[プリレンダリング開始] --> B[createPrerenderResumeDataCache]
    B --> C[データ収集]
    C --> D{cache/fetch/boundArgsデータ}
    D --> E[各ストアにset]
    E --> F[stringifyResumeDataCache]
    F --> G[zlib圧縮 + Base64エンコード]
    G --> H[保存]

    I[レンダリング再開] --> J[createRenderResumeDataCache]
    J --> K[Base64デコード + zlib展開]
    K --> L[JSONパース]
    L --> M[parseUseCacheCacheStore]
    M --> N[イミュータブルキャッシュ構築]
    N --> O[キャッシュ参照]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-40-01 | Edge Runtime非対応 | stringifyResumeDataCacheとcreateRenderResumeDataCacheはEdge Runtimeでは使用不可 | NEXT_RUNTIME === 'edge' |
| BR-40-02 | 空キャッシュ判定 | fetchとcacheの両方のsizeが0の場合、'null'文字列を返す | シリアライズ時 |
| BR-40-03 | 圧縮サイズ制限 | maxPostponedStateSizeBytesの5倍を展開後の最大サイズとする（デフォルト500MB） | 復元時 |
| BR-40-04 | decryptedBoundArgsの非永続化 | decryptedBoundArgsはインメモリのみで使用し、シリアライズには含めない | 常時 |
| BR-40-05 | キャッシュコンポーネント除外 | isCacheComponentsEnabledがtrueでrevalidate===0またはexpire<DYNAMIC_EXPIREのエントリはシリアライズから除外 | シリアライズ時 |

### 計算ロジック

最大展開サイズの計算:
```
maxDecompressedSize = maxPostponedStateSizeBytes ? maxPostponedStateSizeBytes * 5 : 500 * 1024 * 1024
```

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| InvariantError | 内部エラー | Edge Runtimeで呼び出された場合 | Node.jsランタイムで実行する |
| ERR_BUFFER_TOO_LARGE | RangeError | 展開後サイズが制限を超えた場合 | maxPostponedStateSizeBytesを増やすか、キャッシュデータを削減する |
| - | Error | キャッシュエントリの書き込み失敗 | 該当エントリをスキップ（全体は保持） |

### リトライ仕様

個別のキャッシュエントリ書き込み失敗は`.catch(() => null)`で無視され、他のエントリへの影響はない。

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

該当なし。キャッシュ操作はMap操作であり、トランザクション制御は不要。

## パフォーマンス要件

- zlib圧縮/展開は同期処理（deflateSync/inflateSync）で実行されるため、大量データでは処理時間に注意
- ReadableStreamのtee()を使用してシリアライズ中もストリームの利用を継続
- UseCacheCacheStoreのシリアライズはPromise.allで並列実行

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

- encryptedBoundArgsは暗号化された状態で保存され、Server Actionsのクロージャデータを保護する
- decryptedBoundArgsはインメモリのみで使用され、永続化されない
- zlib展開時のzipbomb攻撃対策として、maxOutputLengthを設定

## 備考

- Resume Data Cacheは実験的機能であり、PPRと共に使用される
- キャッシュの圧縮にはNode.js標準のzlibモジュールを使用
- Edge Runtimeでは利用不可（Node.js依存のzlib使用のため）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | cache-store.ts | `packages/next/src/server/resume-data-cache/cache-store.ts` | CacheStore型、FetchCacheStore、UseCacheCacheStore、EncryptedBoundArgsCacheStoreを確認する |

**読解のコツ**: **12-15行目**で`CacheStore<T>`がMapのサブセットとして定義されている。**20行目**の`FetchCacheStore`はCachedFetchValueを値に持つ。**49行目**の`UseCacheCacheStore`はPromise<CacheEntry>を値に持つ（非同期キャッシュ）。

#### Step 2: イミュータブルキャッシュを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | resume-data-cache.ts | `packages/next/src/server/resume-data-cache/resume-data-cache.ts` | RenderResumeDataCacheインターフェースを確認する |

**主要処理フロー**:
- **16-42行目**: `RenderResumeDataCache` - cache、fetch、encryptedBoundArgs、decryptedBoundArgsの4つの読み取り専用ストア

#### Step 3: ミュータブルキャッシュを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | resume-data-cache.ts | `packages/next/src/server/resume-data-cache/resume-data-cache.ts` | PrerenderResumeDataCacheインターフェースを確認する |

**主要処理フロー**:
- **48-77行目**: `PrerenderResumeDataCache` - get/set操作が可能なミュータブル版

#### Step 4: シリアライズ処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | resume-data-cache.ts | `packages/next/src/server/resume-data-cache/resume-data-cache.ts` | stringifyResumeDataCache関数を確認する |

**主要処理フロー**:
1. **102-141行目**: `stringifyResumeDataCache` - Edge Runtimeチェック、空判定、serializeUseCacheCacheStoreによるストリームのBase64変換、JSON.stringify、zlib圧縮、Base64エンコード

#### Step 5: 復元処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | resume-data-cache.ts | `packages/next/src/server/resume-data-cache/resume-data-cache.ts` | createRenderResumeDataCache関数を確認する |

**主要処理フロー**:
- **170-248行目**: `createRenderResumeDataCache` - 3つのオーバーロード。文字列からの復元時はBase64デコード → zlib展開 → JSON解析 → parseUseCacheCacheStoreによるストリーム復元

#### Step 6: ストアのシリアライズ/パースを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | cache-store.ts | `packages/next/src/server/resume-data-cache/cache-store.ts` | parseUseCacheCacheStoreとserializeUseCacheCacheStoreを確認する |

**主要処理フロー**:
- **56-88行目**: `parseUseCacheCacheStore` - シリアライズされたエントリからReadableStreamを復元
- **95-144行目**: `serializeUseCacheCacheStore` - ReadableStreamのtee()でストリームを分岐し、Base64エンコード

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

```
[プリレンダリング]
    │
    ├─ createPrerenderResumeDataCache()
    │      └─ new Map() × 4 (cache, fetch, encryptedBoundArgs, decryptedBoundArgs)
    │
    ├─ [データ収集] cache.set() / fetch.set() / encryptedBoundArgs.set()
    │
    └─ stringifyResumeDataCache()
           ├─ serializeUseCacheCacheStore()
           │      └─ stream.tee() + arrayBufferToString() + btoa()
           ├─ JSON.stringify()
           └─ zlib.deflateSync() + Buffer.toString('base64')

[レンダリング再開]
    │
    └─ createRenderResumeDataCache(persistedCache)
           ├─ Buffer.from(base64) + zlib.inflateSync()
           ├─ JSON.parse()
           └─ parseUseCacheCacheStore()
                  └─ stringToUint8Array() + atob() → ReadableStream
```

### データフロー図

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

use cacheデータ ────▶ PrerenderResumeDataCache ──▶ cache Map
fetchレスポンス ───▶                               fetch Map
暗号化boundArgs ──▶                               encryptedBoundArgs Map
                                   │
                      stringifyResumeDataCache()
                                   │
                      ┌─ serializeUseCacheCacheStore
                      ├─ JSON.stringify
                      └─ zlib.deflateSync + Base64 ──▶ 圧縮文字列
                                   │
                      createRenderResumeDataCache()
                                   │
                      ┌─ Base64 + zlib.inflateSync
                      ├─ JSON.parse
                      └─ parseUseCacheCacheStore ──▶ RenderResumeDataCache
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| resume-data-cache.ts | `packages/next/src/server/resume-data-cache/resume-data-cache.ts` | ソース | Resume Data Cacheの中核実装 |
| cache-store.ts | `packages/next/src/server/resume-data-cache/cache-store.ts` | ソース | キャッシュストア型定義とシリアライズ/パース |
| resume-data-cache.test.ts | `packages/next/src/server/resume-data-cache/resume-data-cache.test.ts` | テスト | Resume Data Cacheのテスト |
| encryption-utils.ts | `packages/next/src/server/app-render/encryption-utils.ts` | ソース | 暗号化ユーティリティ |
