# 機能設計書 91-ファイルシステムストレージ

## 概要

本ドキュメントは、Giselleプラットフォームにおけるローカルファイルシステムへのデータ永続化機能について記述する。

### 本機能の処理概要

**業務上の目的・背景**：ワークフローデータ、生成結果、アップロードファイルなどをローカルファイルシステムに永続化するため、抽象化されたストレージインターフェースの具体実装を提供する。開発環境やセルフホスト環境において、クラウドストレージを使用せずにデータを管理できるようにする。

**機能の利用シーン**：
- 開発環境でのローカルテスト実行時
- セルフホスト環境でのファイル保存時
- ワークスペースデータのJSON形式での保存・読み込み時
- ユーザーがアップロードしたファイル（画像、ドキュメント等）のバイナリ保存時

**主要な処理内容**：
1. JSONデータの読み書き（Zodスキーマによるバリデーション付き）
2. バイナリデータ（Blob）の読み書き（範囲指定読み取り対応）
3. ファイルのコピー・削除・存在確認
4. ファイルサイズの取得
5. 指定プレフィックスでのファイル一覧取得（ページネーション対応）

**関連システム・外部連携**：Node.js fsモジュールを使用してローカルファイルシステムにアクセスする。

**権限による制御**：ファイルシステムのアクセス権限に依存する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能は画面を持たないバックエンドストレージ機能 |

## 機能種別

データ永続化 / ファイル操作

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| config.root | string | Yes | ストレージのルートディレクトリパス | 有効なファイルパス |
| path | string | Yes | 保存先の相対パス | - |
| schema | z.ZodType | No | JSONデータのバリデーションスキーマ | 有効なZodスキーマ |
| data | unknown | Yes (setJson) | 保存するデータ | schemaに準拠 |
| range.start | number | No | 読み取り開始位置 | 0以上 |
| range.end | number | No | 読み取り終了位置 | start以上 |
| prefix | string | No | 一覧取得時のパスプレフィックス | - |
| limit | number | No | 一覧取得時の最大件数 | 正の整数 |
| cursor | string | No | ページネーションカーソル | 数値文字列 |

### 入力データソース

- アプリケーションコードからのAPI呼び出し
- ワークスペースデータ（Workspace型）
- ユーザーアップロードファイル

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| JSON data | z.infer<T> | Zodスキーマでパースされたデータ |
| Blob data | Uint8Array | バイナリデータ |
| exists | boolean | ファイルの存在有無 |
| contentLength | number | ファイルサイズ（バイト） |
| blobs | ListedBlob[] | ファイル一覧 |
| hasMore | boolean | 追加データの有無 |
| cursor | string | 次ページのカーソル |

### 出力先

ローカルファイルシステム（config.root配下）

## 処理フロー

### 処理シーケンス

```
1. getJson処理
   └─ パス結合 → ファイルアクセス確認 → リトライ付きファイル読み込み → JSONパース → Zodスキーマバリデーション

2. setJson処理
   └─ パス結合 → 親ディレクトリ作成 → スキーマバリデーション → JSON文字列化 → ファイル書き込み

3. getBlob処理
   └─ パス結合 → 範囲指定確認 → ファイル読み込み（全体または部分） → Uint8Array変換

4. setBlob処理
   └─ パス結合 → 親ディレクトリ作成 → Uint8Array変換 → ファイル書き込み

5. listBlobs処理
   └─ ディレクトリ再帰走査 → プレフィックスフィルタリング → ソート → ページネーション適用
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{操作種別}
    B -->|getJson| C[ファイルパス結合]
    C --> D[ファイル存在確認]
    D --> E{存在する?}
    E -->|No| F[エラースロー]
    E -->|Yes| G[リトライ付き読み込み]
    G --> H[JSONパース]
    H --> I[Zodバリデーション]
    I --> J[データ返却]

    B -->|setJson| K[ファイルパス結合]
    K --> L[親ディレクトリ作成]
    L --> M[スキーマバリデーション]
    M --> N[JSON文字列化]
    N --> O[ファイル書き込み]
    O --> P[完了]

    B -->|getBlob| Q[ファイルパス結合]
    Q --> R{範囲指定あり?}
    R -->|No| S[全体読み込み]
    R -->|Yes| T[部分読み込み]
    S --> U[Uint8Array変換]
    T --> U
    U --> V[データ返却]

    B -->|listBlobs| W[ディレクトリ走査]
    W --> X[プレフィックスフィルタ]
    X --> Y[ソート]
    Y --> Z[ページネーション]
    Z --> AA[結果返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | リトライ処理 | JSON読み込み失敗時、最大5回まで指数バックオフでリトライ | getJson時 |
| BR-002 | 自動ディレクトリ作成 | 書き込み時、親ディレクトリが存在しない場合は自動作成 | setJson/setBlob時 |
| BR-003 | 範囲クランプ | 範囲指定時、ファイルサイズを超える範囲は自動調整 | getBlob時 |
| BR-004 | ソート順 | ファイル一覧はパス名でアルファベット順にソート | listBlobs時 |

### 計算ロジック

リトライ遅延: `delay = 2^attempt * 100ms`（attempt: 0-4）

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

該当なし（ファイルシステム操作のみ）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ENOENT | ファイル不在 | 指定パスにファイルが存在しない | 上位で適切にハンドリング |
| EACCES | アクセス拒否 | ファイルシステム権限不足 | 権限設定を確認 |
| JSON_PARSE_ERROR | パースエラー | JSONフォーマットが不正 | データの整合性確認 |
| INVALID_CURSOR | カーソル不正 | 無効なカーソル値 | 正しいカーソル値を使用 |

### リトライ仕様

- getJsonで最大5回のリトライ
- 指数バックオフ: 100ms, 200ms, 400ms, 800ms, 1600ms

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

ファイルシステム操作のため、トランザクション保証なし。各操作は独立して実行される。

## パフォーマンス要件

- 大規模ディレクトリ走査時のメモリ使用量に注意
- 範囲指定読み取りで大容量ファイルの部分アクセスをサポート

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

- ルートディレクトリ外へのパストラバーサル攻撃に注意
- ファイルシステム権限の適切な設定が必要

## 備考

本機能はGiselleStorage抽象インターフェースの具体実装であり、他のストレージドライバ（メモリストレージ、クラウドストレージ等）と差し替え可能。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/storage/src/types.ts` | GiselleStorageインターフェース、各メソッドの型定義を理解 |

**読解のコツ**: BlobLike型（Buffer, Uint8Array, ArrayBuffer）の互換性、Zodスキーマとの連携方法を確認する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/storage/src/index.ts` | パッケージのエクスポート構造 |
| 2-2 | fs-storage-driver.ts | `packages/storage/src/fs-storage-driver.ts` | fsStorageDriver関数がストレージインスタンスを生成 |

**主要処理フロー**:
1. **19行目**: fsStorageDriver関数でGiselleStorage実装を返却
2. **21-52行目**: getJson実装（リトライロジック含む）
3. **54-63行目**: setJson実装
4. **65-106行目**: getBlob実装（範囲指定対応）
5. **108-113行目**: setBlob実装
6. **143-243行目**: listBlobs実装（再帰ディレクトリ走査）

#### Step 3: ヘルパー関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | fs-storage-driver.ts | `packages/storage/src/fs-storage-driver.ts` | ensureDir関数（15-17行目）でディレクトリ自動作成 |

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

```
fsStorageDriver(config)
    │
    ├─ getJson(params)
    │      ├─ fs.access(fullPath)
    │      ├─ fs.readFile(fullPath, "utf8") [リトライ付き]
    │      ├─ JSON.parse(content)
    │      └─ schema.parse(obj)
    │
    ├─ setJson(params)
    │      ├─ ensureDir(fullPath)
    │      │      └─ fs.mkdir(dirname, {recursive: true})
    │      ├─ schema.parse(data)
    │      └─ fs.writeFile(fullPath, JSON.stringify(data))
    │
    ├─ getBlob(path, options)
    │      ├─ fs.readFile(fullPath) [範囲なし]
    │      └─ fs.open → fd.read → fd.close [範囲あり]
    │
    ├─ setBlob(path, data)
    │      ├─ ensureDir(fullPath)
    │      └─ fs.writeFile(fullPath, uint8Array)
    │
    ├─ copy(source, destination)
    │      ├─ ensureDir(destPath)
    │      └─ fs.copyFile(srcPath, destPath)
    │
    ├─ remove(path)
    │      └─ fs.rm(fullPath, {recursive: true, force: true})
    │
    ├─ exists(path)
    │      └─ fs.access(fullPath)
    │
    ├─ contentLength(path)
    │      └─ fs.stat(fullPath).size
    │
    └─ listBlobs(params)
           └─ walk(relativeDir) [再帰]
                  ├─ fs.readdir(absoluteDir, {withFileTypes: true})
                  └─ fs.stat(entryPath) [各ファイル]
```

### データフロー図

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

config.root ─────────────┐
                         │
path ────────────────────┼──▶ fsStorageDriver ──▶ ファイルシステム
                         │         │
schema ──────────────────┤         ├──▶ getJson ──▶ バリデート済みJSON
                         │         │
data ────────────────────┤         ├──▶ setJson ──▶ JSONファイル
                         │         │
BlobLike ────────────────┤         ├──▶ setBlob ──▶ バイナリファイル
                         │         │
range ───────────────────┘         └──▶ getBlob ──▶ Uint8Array
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| fs-storage-driver.ts | `packages/storage/src/fs-storage-driver.ts` | ソース | ファイルシステムストレージドライバ実装 |
| types.ts | `packages/storage/src/types.ts` | ソース | GiselleStorageインターフェース定義 |
| index.ts | `packages/storage/src/index.ts` | ソース | パッケージエクスポート |
| memory-storage-driver.ts | `packages/storage/src/memory-storage-driver.ts` | ソース | 比較参照用：インメモリ実装 |
