# 機能設計書 63-pipeToNodeWritable() / pipeToWebWritable()

## 概要

本ドキュメントは、Vue 3の`@vue/server-renderer`パッケージが提供する`pipeToNodeWritable()`と`pipeToWebWritable()`の2つの関数の設計仕様を記述する。これらの関数は、VueコンポーネントのSSRレンダリング結果を既存のWritableStreamインスタンスにパイプ出力する機能を提供する。

### 本機能の処理概要

`pipeToNodeWritable()`はNode.jsの`Writable`ストリームに、`pipeToWebWritable()`はWeb Streams APIの`WritableStream`に、それぞれVueコンポーネントのSSRレンダリング結果をパイプ出力する。いずれも内部で`renderToSimpleStream()`を呼び出し、各ストリーム規格のWriteインターフェースにアダプター変換する。

**業務上の目的・背景**：SSRでは、HTTPレスポンスオブジェクトやファイルストリーム等の既存のWritableStreamにレンダリング結果を直接出力したいケースが多い。`renderToNodeStream()`や`renderToWebStream()`はReadableStreamを返すため、別途パイプ処理が必要になる。`pipeToNodeWritable()`/`pipeToWebWritable()`は、既存のWritableStreamに直接出力することで、中間のReadableStreamを生成するオーバーヘッドを排除する。特にESMビルドでは`renderToNodeStream()`が使用不可のため、`pipeToNodeWritable()`が代替手段となる。

**機能の利用シーン**：Express/Koa等のNode.jsフレームワークでHTTPレスポンスに直接SSR結果を出力する場合、Cloudflare Workers等のエッジ環境でWritableStreamにSSR結果をパイプする場合に使用される。

**主要な処理内容**：
1. `pipeToNodeWritable()`: SimpleReadableアダプターを作成し、push時にwritable.write()、終了時にwritable.end()を呼び出す
2. `pipeToWebWritable()`: WritableStreamからWriterを取得し、TextEncoderでエンコードしてwriter.write()を呼び出す
3. 両関数とも内部で`renderToSimpleStream()`を使用してレンダリング処理を実行する

**関連システム・外部連携**：Node.js Streams（`node:stream` Writable）、Web Streams API（`WritableStream`）。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | サーバーサイドで実行されるため直接的な画面関連なし |

## 機能種別

データ変換処理（VNodeツリー -> Writable Stream出力）

## 入力仕様

### 入力パラメータ

#### pipeToNodeWritable()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| input | `App \| VNode` | Yes | レンダリング対象 | - |
| context | `SSRContext \| undefined` | No | SSRコンテキスト。デフォルト`{}` | - |
| writable | `Writable` | Yes | Node.jsのWritableストリーム | node:streamのWritable型 |

#### pipeToWebWritable()

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| input | `App \| VNode` | Yes | レンダリング対象 | - |
| context | `SSRContext \| undefined` | No | SSRコンテキスト。デフォルト`{}` | - |
| writable | `WritableStream` | Yes | Web Streams APIのWritableStream | - |

### 入力データソース

- Vueアプリケーションインスタンスまたは直接のVNode
- 既存のWritableStreamインスタンス（HTTPレスポンス、ファイルストリーム等）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| pipeToNodeWritable戻り値 | `void` | 戻り値なし。Writableに直接出力 |
| pipeToWebWritable戻り値 | `void` | 戻り値なし。WritableStreamに直接出力 |

### 出力先

- `pipeToNodeWritable()`: Node.jsのWritableストリームに`write()`で出力し、完了時に`end()`で終了
- `pipeToWebWritable()`: WritableStreamのWriterに`write()`で出力し、完了時に`close()`で終了

## 処理フロー

### 処理シーケンス

#### pipeToNodeWritable()
```
1. renderToSimpleStream()呼び出し
   └─ SimpleReadableアダプターを渡す
      ├─ push(content): contentがnullでなければwritable.write(content)、nullならwritable.end()
      └─ destroy(err): writable.destroy(err)
```

#### pipeToWebWritable()
```
1. writable.getWriter()でWriterを取得
2. TextEncoderを生成
3. writer.readyの存在確認（CloudFlare Workers対応）
4. renderToSimpleStream()呼び出し
   └─ SimpleReadableアダプターを渡す
      ├─ push(content): hasReady時はawait writer.ready後、contentがnullでなければencodeしてwriter.write()、nullならwriter.close()
      └─ destroy(err): console.logでエラー出力後writer.close()
```

### フローチャート

```mermaid
flowchart TD
    subgraph pipeToNodeWritable
        A1[呼び出し] --> B1[renderToSimpleStream]
        B1 --> C1{push content}
        C1 -->|content != null| D1[writable.write content]
        C1 -->|content == null| E1[writable.end]
        B1 --> F1{destroy err}
        F1 --> G1[writable.destroy err]
    end

    subgraph pipeToWebWritable
        A2[呼び出し] --> B2[writable.getWriter]
        B2 --> C2[TextEncoder生成]
        C2 --> D2{writer.readyチェック}
        D2 --> E2[renderToSimpleStream]
        E2 --> F2{push content}
        F2 -->|hasReady| G2[await writer.ready]
        G2 --> H2{content != null?}
        F2 -->|!hasReady| H2
        H2 -->|Yes| I2[writer.write encoder.encode]
        H2 -->|No| J2[writer.close]
        E2 --> K2{destroy err}
        K2 --> L2[console.log + writer.close]
    end
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-63-01 | Node.js Writable終了 | pipeToNodeWritable()ではpush(null)時にwritable.end()を呼び出す | pipeToNodeWritable使用時 |
| BR-63-02 | Node.js エラー伝播 | pipeToNodeWritable()ではエラー時にwritable.destroy(err)を呼び出す | エラー発生時 |
| BR-63-03 | Web Writer ready対応 | pipeToWebWritable()ではwriter.readyのPromise対応をチェックする（#4287 CloudFlare Workers対応） | pipeToWebWritable使用時 |
| BR-63-04 | TextEncoderエンコード | pipeToWebWritable()ではTextEncoderでUint8Arrayにエンコードしてから書き込む | pipeToWebWritable使用時 |
| BR-63-05 | Web Writer終了 | pipeToWebWritable()ではpush(null)時にwriter.close()を呼び出す | 正常完了時 |

### 計算ロジック

特になし。

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | レンダリングエラー (Node) | コンポーネントレンダリング中のエラー | writable.destroy(err)で伝播 |
| - | レンダリングエラー (Web) | コンポーネントレンダリング中のエラー | console.logでエラー出力後writer.close() |

### リトライ仕様

リトライは行わない。

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

該当なし。

## パフォーマンス要件

- 中間のReadableStreamを生成しないため、`renderToNodeStream()`/`renderToWebStream()`と比較してメモリ効率が良い
- `pipeToWebWritable()`ではwriter.readyのawaitにより、バックプレッシャーに対応

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

- HTMLエスケープは内部レンダリングエンジンで実施される
- SSRコンテキストへのユーザーデータ格納時は呼び出し側でサニタイズが必要

## 備考

- `pipeToWebWritable()`のエラーハンドリングは`console.log`でエラーを出力するのみで、ソースコード内に「TODO better error handling?」のコメントがある（190行目付近）
- CloudFlare Workersではwriter.readyプロパティが実装されていないため、try-catchで存在確認を行うワークアラウンドが実装されている（#4287）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | SimpleReadableインターフェース（16-19行目）。push/destroyの2メソッド |
| 1-2 | render.ts | `packages/server-renderer/src/render.ts` | SSRContext型（47-58行目） |

**読解のコツ**: pipeToNodeWritable()とpipeToWebWritable()は、SimpleReadableインターフェースのアダプターパターンで実装されている。各ストリーム規格のWriteAPIをSimpleReadableにマッピングしている点に注目。

#### Step 2: pipeToNodeWritable()を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | pipeToNodeWritable()関数（126-143行目） |

**主要処理フロー**:
1. **131行目**: renderToSimpleStream()を呼び出し
2. **132-137行目**: push()アダプター。content != nullならwritable.write(content)、nullならwritable.end()
3. **139-141行目**: destroy()アダプター。writable.destroy(err)

#### Step 3: pipeToWebWritable()を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | pipeToWebWritable()関数（182-214行目） |

**主要処理フロー**:
1. **187行目**: writable.getWriter()でWriterを取得
2. **188行目**: TextEncoderを生成
3. **191-194行目**: writer.readyの存在確認。CloudFlare Workers (#4287) ではreadyプロパティが未実装のためtry-catchで確認
4. **196行目**: renderToSimpleStream()を呼び出し
5. **197-205行目**: push()アダプター（async）。hasReadyならawait writer.ready、content != nullならencodeしてwriter.write()、nullならwriter.close()
6. **207-212行目**: destroy()アダプター。console.logでエラー出力後writer.close()

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

```
pipeToNodeWritable(input, context, writable)
    │
    └─ renderToSimpleStream(input, context, { push, destroy })
           │
           ├─ push(content) → writable.write(content) / writable.end()
           └─ destroy(err)  → writable.destroy(err)

pipeToWebWritable(input, context, writable)
    │
    ├─ writable.getWriter() → writer
    ├─ new TextEncoder() → encoder
    │
    └─ renderToSimpleStream(input, context, { push, destroy })
           │
           ├─ push(content) → await writer.ready → writer.write(encoder.encode(content)) / writer.close()
           └─ destroy(err)  → console.log(err) → writer.close()
```

### データフロー図

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

App / VNode ──▶ renderToSimpleStream()
                       │
                       └─ SSRBuffer展開
                              │
                              ├─ [Node.js] string ──▶ writable.write() ──▶ Node.js Writable
                              │                       writable.end()
                              │
                              └─ [Web] string ──▶ TextEncoder.encode()
                                                      │
                                                      └─ Uint8Array ──▶ writer.write() ──▶ WritableStream
                                                                        writer.close()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| renderToStream.ts | `packages/server-renderer/src/renderToStream.ts` | ソース | pipeToNodeWritable()、pipeToWebWritable()の実装 |
| render.ts | `packages/server-renderer/src/render.ts` | ソース | SSRContext型定義、renderComponentVNode() |
| index.ts | `packages/server-renderer/src/index.ts` | ソース | パッケージエクスポート |
