# 機能設計書 75-cookies()

## 概要

本ドキュメントは、Next.jsの`cookies()`関数の設計について記述する。`cookies()`はServer Components、Server Actions、Route Handlers、Middleware内でリクエストCookieの読み取り及びServer ActionsでのCookie書き込みを行うための非同期API関数である。

### 本機能の処理概要

`cookies()`関数は、現在のリクエストのHTTP Cookieに対してアクセスを提供するAPI関数である。Promiseを返し、awaitすることでReadonlyRequestCookiesオブジェクト（Server Actionsではmutableなオブジェクト）が得られる。`headers()`と同様にDynamic APIの1つであり、静的生成時にはプリレンダリングの中断やポストポーンが発生する。

**業務上の目的・背景**：Cookieはセッション管理、認証トークン保持、ユーザー設定の永続化など、Webアプリケーションの基盤となる機能である。`cookies()`はServer Components内でCookieを安全に読み取り、Server Actionsでは書き込みも可能にすることで、サーバーサイドでのCookie操作を一貫したAPIで提供する。

**機能の利用シーン**：セッションCookieの確認、認証トークンの読み取り、ユーザー設定Cookieの取得、Server ActionsでのCookie設定・削除、ドラフトモードのCookie管理との連携。

**主要な処理内容**：
1. workAsyncStorage/workUnitAsyncStorageから現在の実行コンテキストを取得
2. 実行コンテキストの種類に応じた分岐処理（headers()と同様のパターン）
3. Server Actions（phase === 'action'）ではmutableCookiesを返し、Cookie設定・削除が可能
4. それ以外のコンテキストでは読み取り専用のCookiesオブジェクトを返却
5. Cookie操作時にworkStore.pathWasRevalidatedフラグを更新（再検証トリガー）

**関連システム・外部連携**：AsyncLocalStorage、RequestCookies/ResponseCookies（@edge-runtime/cookies）、MutableRequestCookiesAdapter、Dynamic Rendering制御

**権限による制御**：Server Actions（phase === 'action'）内でのみCookieの書き込み（set/delete）が可能。その他のコンテキストでは読み取り専用。render/afterフェーズでset/deleteを呼ぶとReadonlyRequestCookiesErrorがスローされる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | cookies()は画面に直接関連しない。サーバーサイドのCookieアクセスに使用される |

## 機能種別

データ参照・データ更新（Server Actionsのみ） / Dynamic API

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| - | - | - | cookies()は引数を取らない | - |

### 入力データソース

- AsyncLocalStorage（workAsyncStorage）から取得されるWorkStoreの状態
- AsyncLocalStorage（workUnitAsyncStorage）から取得されるWorkUnitStoreのcookiesプロパティ
- HTTPリクエストヘッダーのCookieフィールド（リクエスト処理時にRequestStoreに格納済み）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| cookies | Promise\<ReadonlyRequestCookies\> | リクエストCookieオブジェクト。Server Actionsではset/delete可能、それ以外では読み取り専用 |

### 出力先

Server Components、Server Actions、Route Handlers、Middleware内のコードで使用される。

## 処理フロー

### 処理シーケンス

```
1. workAsyncStorage/workUnitAsyncStorageから実行コンテキスト取得
2. after()内実行チェック
   └─ after()内かつisRequestAPICallableInsideAfter()がfalseの場合エラー
3. forceStatic チェック
   └─ trueの場合、空のCookiesオブジェクトを返す
4. dynamicShouldError チェック
   └─ dynamic="error"設定時にStaticGenBailoutErrorをスロー
5. WorkUnitStore種別による分岐
   └─ cache/unstable-cache: エラースロー
   └─ prerender: ハンギングPromise生成
   └─ prerender-ppr: postponeWithTrackingでポストポーン
   └─ prerender-legacy: throwToInterruptStaticGenerationで静的生成中断
   └─ prerender-runtime: delayUntilRuntimeStageでランタイムステージまで遅延
   └─ request: 動的レンダリング追跡
6. request時のCookieオブジェクト選択
   └─ phase === 'action': userspaceMutableCookiesを返却（書き込み可能）
   └─ それ以外: 読み取り専用cookiesを返却
7. 結果返却
   └─ Promise<ReadonlyRequestCookies>として返却
```

### フローチャート

```mermaid
flowchart TD
    A[cookies 呼び出し] --> B{WorkStore存在?}
    B -->|No| Z[throwForMissingRequestStore]
    B -->|Yes| C{after 内?}
    C -->|Yes & 不許可| Y[Error]
    C -->|No| D{forceStatic?}
    D -->|Yes| E[空Cookies返却]
    D -->|No| F{dynamicShouldError?}
    F -->|Yes| G[StaticGenBailoutError]
    F -->|No| H{WorkUnitStore種別}
    H -->|cache| I[Error]
    H -->|unstable-cache| J[Error]
    H -->|prerender| K[ハンギングPromise]
    H -->|prerender-ppr| L[postponeWithTracking]
    H -->|prerender-legacy| M[throwToInterrupt]
    H -->|prerender-runtime| N[delayUntilRuntimeStage]
    H -->|request| O{areCookiesMutable?}
    O -->|Yes action| P[mutableCookies返却]
    O -->|No| Q[readonlyCookies返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-75-01 | Dynamic API | cookies()はDynamic APIであり、呼び出すと動的レンダリングが強制される | 常時 |
| BR-75-02 | 書き込み可能フェーズ | Cookie書き込み（set/delete）はphase === 'action'（Server Actions）でのみ可能 | Server Actions実行時 |
| BR-75-03 | 読み取り専用フェーズ | render/afterフェーズではset/deleteはReadonlyRequestCookiesErrorをスロー | render/afterフェーズ |
| BR-75-04 | キャッシュ内使用不可 | "use cache"やunstable_cache内でのcookies()呼び出しはエラー | cache/unstable-cacheコンテキスト |
| BR-75-05 | 再検証トリガー | Cookie変更時にworkStore.pathWasRevalidatedがActionDidRevalidateStaticAndDynamicに設定される | Cookie書き込み時 |
| BR-75-06 | 非同期API | cookies()はPromiseを返す。awaitまたはReact.use()で値を取得する必要がある | 常時 |

### 計算ロジック

特になし。

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

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

cookies()は直接的なデータベース操作を行わない。ただし、Cookie変更によるpathWasRevalidated設定を通じてキャッシュ再検証がトリガーされる場合がある。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| Error | コンテキストエラー | cookies()がリクエストスコープ外で呼ばれた場合 | Server Components/Actions/Route Handlers/Middleware内で呼ぶ |
| Error | after()内使用エラー | after()コールバック内でcookies()を呼んだ場合 | after()の外でcookies()を呼び、結果を渡す |
| Error | cache内使用エラー | "use cache"内でcookies()を呼んだ場合 | キャッシュの外で呼び、引数として渡す |
| StaticGenBailoutError | 静的生成エラー | dynamic="error"のルートでcookies()を呼んだ場合 | dynamic設定を変更する |
| ReadonlyRequestCookiesError | 読み取り専用エラー | render/afterフェーズでcookies().set()/delete()した場合 | Server Actions内でのみ書き込む |

### リトライ仕様

該当なし。

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

該当なし。Cookie操作はHTTP Set-Cookieヘッダーを通じてクライアントに反映される。

## パフォーマンス要件

- CachedCookies WeakMapによる同一リクエスト内でのPromiseキャッシュ
- 開発モードでは同期アクセス警告のためのProxyオーバーヘッドあり

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

- Cookie書き込みはServer Actionsに限定されており、不正なタイミングでのCookie変更を防止
- httpOnly, secure, sameSite等のCookieセキュリティ属性はResponseCookiesを通じて設定可能
- Set-CookieヘッダーはonUpdateCookiesコールバックを通じてレスポンスに反映される

## 備考

- `ReadonlyRequestCookies`型はRequestCookiesのget系メソッドとResponseCookiesのset/deleteメソッドの組み合わせ型
- `MutableRequestCookiesAdapter.wrap()`がRequestCookiesをResponseCookiesでラップし、変更追跡を行う
- `SYMBOL_MODIFY_COOKIE_VALUES`シンボルで変更されたCookie値の追跡が行われる

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | request-cookies.ts | `packages/next/src/server/web/spec-extension/adapters/request-cookies.ts` | ReadonlyRequestCookies型、RequestCookiesAdapter.seal()、MutableRequestCookiesAdapter.wrap()の実装を確認 |
| 1-2 | cookies.ts | `packages/next/src/server/web/spec-extension/cookies.ts` | RequestCookies/ResponseCookiesの再エクスポート元を確認 |

**読解のコツ**: `MutableRequestCookiesAdapter.wrap()`はRequestCookiesをResponseCookiesでラップし、set/delete操作時にSYMBOL_MODIFY_COOKIE_VALUESで変更追跡を行う。onUpdateCookiesコールバックでSet-Cookieヘッダーが更新される。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | cookies.ts | `packages/next/src/server/request/cookies.ts` | cookies()関数本体（33-142行目）。headers()と同様のパターンでWorkUnitStore種別による分岐を行う |

**主要処理フロー**:
1. **35-36行目**: workAsyncStorage/workUnitAsyncStorageからストアを取得
2. **39-48行目**: after()内での使用チェック
3. **50-55行目**: forceStaticチェックと空Cookies返却
4. **57-61行目**: dynamicShouldErrorチェック
5. **63-137行目**: WorkUnitStore種別による分岐処理
6. **108-133行目**: requestコンテキストでのareCookiesMutableInCurrentPhase判定とCookieオブジェクト選択

#### Step 3: Cookie書き込み機構を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | request-cookies.ts | `packages/next/src/server/web/spec-extension/adapters/request-cookies.ts` | MutableRequestCookiesAdapter.wrap()（104-178行目）。Proxyによるset/deleteのインターセプトとupdateResponseCookies()を確認 |
| 3-2 | request-cookies.ts | `packages/next/src/server/web/spec-extension/adapters/request-cookies.ts` | areCookiesMutableInCurrentPhase()（208-210行目）とensureCookiesAreStillMutable()（219-226行目）を確認 |

#### Step 4: 開発モード警告を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cookies.ts | `packages/next/src/server/request/cookies.ts` | instrumentCookiesPromiseWithDevWarnings()（228-247行目）。headers()と同様のパターンでPromise上にProxyプロパティを設定 |

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

```
cookies() [cookies.ts:33]
    |
    +-- workAsyncStorage.getStore()
    +-- workUnitAsyncStorage.getStore()
    |
    +-- [forceStatic]
    |       +-- createEmptyCookies()
    |               +-- RequestCookiesAdapter.seal(new RequestCookies(new Headers({})))
    |
    +-- [prerender]
    |       +-- makeHangingCookies()
    |
    +-- [request]
            +-- trackDynamicDataInDynamicRender()
            +-- areCookiesMutableInCurrentPhase() [phase === 'action']
            |       +-- userspaceMutableCookies（書き込み可能）
            |
            +-- [phase !== 'action']
            |       +-- readonlyCookies（読み取り専用）
            |
            +-- makeUntrackedCookies() / makeUntrackedCookiesWithDevWarnings()
```

### データフロー図

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

AsyncLocalStorage -------> cookies()                   Promise<ReadonlyRequestCookies>
  (WorkStore,                 |                                  |
   WorkUnitStore)             +-> コンテキスト判定                 |
                              +-> 種別分岐                        |
HTTPリクエストCookie            |                                  |
  (RequestStore.cookies) --> +-> [action] mutableCookies ------->+
                              +-> [render] readonlyCookies ----->+
                              |                                  |
                              +-> [set/delete時]                 |
                                   +-> onUpdateCookies() ----> Set-Cookie
                                   +-> pathWasRevalidated更新    ヘッダー
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| cookies.ts | `packages/next/src/server/request/cookies.ts` | ソース | cookies()関数本体 |
| request-cookies.ts | `packages/next/src/server/web/spec-extension/adapters/request-cookies.ts` | ソース | RequestCookiesAdapter/MutableRequestCookiesAdapter |
| cookies.ts (spec) | `packages/next/src/server/web/spec-extension/cookies.ts` | ソース | RequestCookies/ResponseCookies再エクスポート |
| headers.ts (api) | `packages/next/src/api/headers.ts` | ソース | cookies再エクスポート元 |
| dynamic-rendering.ts | `packages/next/src/server/app-render/dynamic-rendering.ts` | ソース | 動的レンダリング制御 |
