# 機能設計書 77-after()

## 概要

本ドキュメントは、Next.jsの`after()`関数の設計について記述する。`after()`はレスポンス送信後にバックグラウンドでタスクを実行するためのAPI関数であり、ログ記録・分析データ送信・キャッシュウォーミングなど、ユーザーへの応答時間に影響を与えずに実行すべき処理をスケジューリングする。

### 本機能の処理概要

`after()`関数は、レスポンスがクライアントに送信された後に実行されるコールバック関数またはPromiseを登録するAPI関数である。登録されたタスクはレスポンスの`onClose`イベント後に実行され、`waitUntil`メカニズムによりサーバーレス環境でも確実に完了する。

**業務上の目的・背景**：Webアプリケーションでは、レスポンス生成には不要だがリクエストに関連する処理（アクセスログの書き込み、分析イベントの送信、外部サービスへの通知等）が必要になる。これらをレスポンス送信後に遅延実行することで、ユーザー体感のレスポンスタイムを改善できる。`after()`はこのパターンを安全かつ確実に実装するための公式APIである。

**機能の利用シーン**：アクセスログのデータベース書き込み、分析サービスへのイベント送信、外部APIへの通知、キャッシュの事前ウォーミング、バックグラウンドでのデータ同期処理。

**主要な処理内容**：
1. `after(promise)` - Promiseを`waitUntil`に直接登録（即座にバックグラウンド実行）
2. `after(callback)` - コールバック関数をキューに登録（onClose後に実行）
3. AfterContextによるコールバックキュー管理（PromiseQueue使用）
4. `bindSnapshot()`による現在のAsyncLocalStorage実行コンテキストのキャプチャ
5. afterTaskAsyncStorageによるネスト対応（after内のafter）
6. `withExecuteRevalidates()`によるキャッシュ再検証の統合実行

**関連システム・外部連携**：waitUntil API（Vercel等のサーバーレスプラットフォーム）、AsyncLocalStorage、PromiseQueue、CloseController

**権限による制御**：`after()`はリクエストスコープ内でのみ呼び出し可能。`after()`内のコールバックではWorkUnitStore.phase='after'が設定され、一部のRequest API（headers(), cookies()等）の使用が制限される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | after()は画面に直接関連しない。レスポンス送信後のバックグラウンドタスク実行に使用される |

## 機能種別

非同期タスクスケジューリング / バックグラウンド処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| task | AfterTask\<T\> | Yes | 実行するタスク。Promise\<T\>またはAfterCallback\<T\>（() => T \| Promise\<T\>） | Promiseまたは関数であること。それ以外はエラー |

### 入力データソース

- AsyncLocalStorage（workAsyncStorage）から取得されるWorkStoreのafterContext
- プログラマティックな関数/Promise引数

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| - | void | after()は戻り値を返さない |

### 出力先

- タスクの実行結果はバックグラウンドで処理される
- エラーはconsole.errorおよびonTaskErrorコールバックに通知される

## 処理フロー

### 処理シーケンス

```
1. after()呼び出し
   └─ workAsyncStorageからWorkStoreを取得
   └─ WorkStore不在の場合はエラースロー
2. AfterContext.after(task)呼び出し
   └─ Promiseの場合: waitUntil(promise)で即登録
   └─ 関数の場合: addCallback()で遅延登録
3. addCallback()の処理
   └─ waitUntilの可用性チェック
   └─ rootTaskSpawnPhaseの記録（ネストafter対応）
   └─ runCallbacksOnClosePromiseの初回セットアップ
   └─ bindSnapshot()で現在のALS実行コンテキストをキャプチャ
   └─ afterTaskAsyncStorage.runでラップしてキューに追加
4. レスポンスclose後
   └─ runCallbacksOnClose()が実行
   └─ workUnitStore.phaseを'after'に変更
   └─ withExecuteRevalidates()内でキューを開始
   └─ callbackQueue.start() -> コールバック順次実行
5. エラー処理
   └─ Promise: catchでreportTaskError
   └─ 関数: try/catchでreportTaskError
   └─ onTaskError コールバック通知
```

### フローチャート

```mermaid
flowchart TD
    A[after task 呼び出し] --> B{WorkStore存在?}
    B -->|No| Z[Error: リクエストスコープ外]
    B -->|Yes| C{task種別}
    C -->|Promise| D[waitUntil に直接登録]
    C -->|Function| E[addCallback]
    C -->|Other| F[Error: 不正な引数]
    E --> G{waitUntil利用可能?}
    G -->|No| H[Error: waitUntil不可]
    G -->|Yes| I[bindSnapshotでALSキャプチャ]
    I --> J[callbackQueueに追加]
    J --> K{初回登録?}
    K -->|Yes| L[runCallbacksOnClosePromise設定]
    L --> M[waitUntil に登録]
    K -->|No| N[待機]
    M --> O[レスポンス close イベント]
    N --> O
    O --> P[phase = 'after' 設定]
    P --> Q[withExecuteRevalidates]
    Q --> R[callbackQueue.start]
    R --> S[コールバック順次実行]
    D --> T[バックグラウンド実行]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-77-01 | waitUntil必須 | after()の動作にはwaitUntil APIの利用が必要。利用不可の環境ではエラー | 常時 |
| BR-77-02 | phase変更 | after()コールバック実行時にWorkUnitStore.phaseが'after'に変更される | コールバック実行時 |
| BR-77-03 | Request API制限 | after()内ではheaders(), cookies()等のRequest APIの使用が制限される | after()コールバック内 |
| BR-77-04 | ALS継承 | after()コールバックは登録時のAsyncLocalStorage実行コンテキストを引き継ぐ | コールバック登録時 |
| BR-77-05 | ネスト対応 | after()内でafter()を呼び出すことが可能。rootTaskSpawnPhaseが保持される | ネストafter時 |
| BR-77-06 | 順次実行 | コールバックはPromiseQueueにより順次実行される（並列実行なし） | コールバック実行時 |
| BR-77-07 | 再検証統合 | コールバック実行はwithExecuteRevalidates()内で行われ、キャッシュ再検証と統合される | コールバック実行時 |

### 計算ロジック

特になし。

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

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

after()自体はデータベース操作を行わない。ユーザーが登録するコールバック内でデータベース操作を行うことが一般的なユースケースである。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| Error | コンテキストエラー | after()がリクエストスコープ外で呼ばれた場合 | Server Components/Actions/Route Handlers内で呼ぶ |
| Error | waitUntil不可 | waitUntilが利用できない環境で呼ばれた場合 | waitUntilをサポートするプラットフォームを使用する |
| Error | 引数エラー | Promiseでも関数でもない引数が渡された場合 | Promise\<T\>または() => T \| Promise\<T\>を渡す |

### リトライ仕様

after()自体にリトライ機構はない。エラーはreportTaskError()でconsole.errorに出力され、onTaskErrorコールバックに通知される。

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

after()のコールバックはレスポンス送信後に実行されるため、リクエスト処理のトランザクションとは独立している。コールバック内で独自のトランザクション管理が必要な場合は、ユーザーが実装する。

## パフォーマンス要件

- レスポンス送信を遅延させない（onClose後に実行開始）
- PromiseQueue（p-queue）による順次実行で並列負荷を回避
- bindSnapshot()のオーバーヘッドは登録時に1回のみ

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

- after()コールバック内ではRequest APIの使用が制限されるため、必要なデータはafter()登録前に取得しておく
- エラーハンドリングが防御的に実装されている（onTaskErrorのtry-catch）

## 備考

- AfterRunnerクラス（run-with-after.ts）はAfterContextのラッパーで、CloseControllerとAwaiterOnceを統合する
- AwaiterMultiは再帰的なwaitUntil対応（waitUntil内で更にwaitUntilが呼ばれるケース）
- AwaiterOnceは1回のみawait可能な制約を追加したAwaiterMultiのラッパー

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | after.ts | `packages/next/src/server/after/after.ts` | AfterTask, AfterCallback型の定義を確認 |
| 1-2 | after-context.ts | `packages/next/src/server/after/after-context.ts` | AfterContextOpts型とAfterContextクラスのプロパティ構造を確認 |

**読解のコツ**: AfterContextはPromiseQueue（paused状態で初期化）とSet<WorkUnitStore>を保持する。コールバックはpauseされたキューに追加され、onClose後にstart()で実行開始される。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | after.ts | `packages/next/src/server/after/after.ts` | after()関数本体（9-21行目）。WorkStoreからafterContextを取得してafter(task)を呼ぶシンプルな構造 |

**主要処理フロー**:
1. **10行目**: workAsyncStorageからWorkStoreを取得
2. **12-17行目**: WorkStore不在時のエラースロー
3. **19-20行目**: afterContext.after(task)の呼び出し

#### Step 3: AfterContextを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | after-context.ts | `packages/next/src/server/after/after-context.ts` | AfterContextクラスの全メソッド（after, addCallback, runCallbacksOnClose, runCallbacks, reportTaskError）を確認 |

**主要処理フロー**:
- **39-53行目**: after()メソッド。Promise/関数/その他の分岐
- **55-101行目**: addCallback()メソッド。bindSnapshot、afterTaskAsyncStorage、キューへの追加
- **104-107行目**: runCallbacksOnClose()。onCloseイベント待機後にrunCallbacks()
- **109-125行目**: runCallbacks()。phase='after'設定、withExecuteRevalidates内でキュー実行
- **127-151行目**: reportTaskError()。エラーログとonTaskErrorコールバック

#### Step 4: Awaiterを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | awaiter.ts | `packages/next/src/server/after/awaiter.ts` | AwaiterMulti（再帰的waitUntil対応）とAwaiterOnce（1回限りawait）の実装を確認 |

**主要処理フロー**:
- **17-30行目**: AwaiterMulti.waitUntil()。Promiseをセットに追加し、完了時に自動削除
- **32-38行目**: AwaiterMulti.awaiting()。while(size > 0)ループで再帰的にPromise完了を待つ
- **54-60行目**: AwaiterOnce.waitUntil()。done=trueの場合はエラースロー

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

```
after(task) [after.ts:9]
    |
    +-- workAsyncStorage.getStore()
    +-- afterContext.after(task) [after-context.ts:39]
            |
            +-- [Promise] waitUntil(promise.catch(reportTaskError))
            |
            +-- [Function] addCallback(callback) [after-context.ts:55]
                    |
                    +-- workUnitAsyncStorage.getStore()
                    +-- afterTaskAsyncStorage.getStore()
                    +-- bindSnapshot(wrappedCallback) [async-local-storage.ts]
                    +-- callbackQueue.add(wrappedCallback) [p-queue]
                    |
                    +-- [初回] runCallbacksOnClose() [after-context.ts:104]
                    |       +-- onClose(resolve) [CloseController]
                    |       +-- runCallbacks() [after-context.ts:109]
                    |               +-- workUnitStore.phase = 'after'
                    |               +-- withExecuteRevalidates()
                    |                       +-- callbackQueue.start()
                    |                       +-- callbackQueue.onIdle()
                    |
                    +-- waitUntil(runCallbacksOnClosePromise)
```

### データフロー図

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

Promise / Callback -------> after()                        void（即座に返却）
                               |                                 |
                               +-> AfterContext.after()           |
                               |                                 |
                               +-- [Promise]                     |
                               |   waitUntil直接登録 ---------> バックグラウンド実行
                               |                                 |
                               +-- [Callback]                    |
                                   bindSnapshot -> キュー追加     |
                                        |                        |
                              [レスポンスclose後]                  |
                                        |                        |
                                   phase='after' 設定             |
                                   キュー実行開始 ------------>  コールバック順次実行
                                        |                        |
                                   withExecuteRevalidates -----> キャッシュ再検証
                                        |                        |
                                   [エラー時]                     |
                                   reportTaskError ------------> console.error
                                                                 onTaskError
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| after.ts | `packages/next/src/server/after/after.ts` | ソース | after()関数エントリーポイント |
| after-context.ts | `packages/next/src/server/after/after-context.ts` | ソース | AfterContextクラス（コールバック管理の中核） |
| run-with-after.ts | `packages/next/src/server/after/run-with-after.ts` | ソース | AfterRunnerクラス（実行統合） |
| awaiter.ts | `packages/next/src/server/after/awaiter.ts` | ソース | AwaiterMulti/AwaiterOnceクラス |
| index.ts | `packages/next/src/server/after/index.ts` | ソース | 再エクスポート |
| builtin-request-context.ts | `packages/next/src/server/after/builtin-request-context.ts` | ソース | 組み込みリクエストコンテキスト |
| web-on-close.ts | `packages/next/src/server/web/web-on-close.ts` | ソース | CloseControllerクラス |
| revalidation-utils.ts | `packages/next/src/server/revalidation-utils.ts` | ソース | withExecuteRevalidates関数 |
