<!-- このファイルは KrileWorks が公開している Claude Code 用スキルです。
     出典: https://krileworks.com/agent-skills
     手元の .claude/skills/ に置くと、エージェントが Apex Stem の API を
     記憶ではなくこの記述に従って書くようになります。 -->

---
name: apex-lwc-result
description: >
  Use this skill when implementing or reviewing an Apex Usecase that is invoked from LWC or Flow
  (i.e. exposed through an @AuraEnabled controller method) and must return a structured Result DTO.
  Covers the inner `class Result { isSuccess / message / data }` convention, the try/catch +
  UsecaseException flow inside invoke(), how the @AuraEnabled controller delegates in one line, and
  how the LWC JS side branches on the result instead of catching exceptions.
  発火する典型: `@AuraEnabled` / `UsecaseException` / inner `class Result` / `result.isSuccess` /
  `invoke()` の戻り値を DTO で返す Usecase。
---

# apex-lwc-result — LWC/Flow 経由 Usecase の Result DTO 規約

LWC (または Flow) から `@AuraEnabled` 経由で呼ばれる Usecase は、**例外を投げて画面に投げ返すのではなく、`invoke()` が `Result` DTO を返す**。成功/失敗・メッセージ・データを 1 つのオブジェクトに畳んで返し、LWC 側は `isSuccess` で分岐する。Trigger/Batch 駆動の Usecase が `void` を返すのと対になる規約。

## 核となる形

```apex
public with sharing class GetActivityRequestDetailUsecase {
  // ① 戻り値 DTO は inner class 固定名 `Result`
  public class Result {
    @AuraEnabled
    public Boolean isSuccess { get; set; }
    @AuraEnabled
    public String message { get; set; }
    @AuraEnabled
    public ActivityRequet__c data { get; set; }   // data の型は処理に応じて (SObject / List / inner DTO / Integer 件数 等)
  }

  private Id requestId;
  private Trace t = Trace.of('活動依頼詳細の取得');
  private IEloquent eloquent;

  // 本番用コンストラクタ (Controller から呼ばれる)
  public GetActivityRequestDetailUsecase(Id requestId) {
    this(requestId, null);
  }

  // テスト用コンストラクタ (DI)
  @TestVisible
  private GetActivityRequestDetailUsecase(Id requestId, IEloquent eloquent) {
    this.requestId = requestId;
    this.eloquent = eloquent ?? new Eloquent();
  }

  // ② public は invoke() のみ。戻り値は Result
  public Result invoke() {
    this.t.start();
    Result result = new Result();
    try {
      if (this.requestId == null) {
        throw new UsecaseException('活動依頼IDが指定されていません。');   // ③ 業務エラーは UsecaseException
      }
      ActivityRequet__c record = (ActivityRequet__c) this.eloquent.firstAsSObject(this.buildScribe());
      if (record == null) {
        throw new UsecaseException('活動依頼が見つからないか、アクセス権限がありません。');
      }
      result.isSuccess = true;
      result.data = record;
      this.t.finish('完了');
    } catch (UsecaseException ex) {
      // ④ 想定内の業務エラー: メッセージをそのまま画面へ透過
      result.isSuccess = false;
      result.message = ex.getMessage();
      this.t.skip('業務エラー: ' + ex.getMessage() + '\n' + ex.getStackTraceString());
    } catch (Exception ex) {
      // ⑤ 想定外: 詳細はユーザーに見せず、ログ(Trace)にだけ残す
      result.isSuccess = false;
      result.message = '予期しないエラーが発生しました。時間をおいて再度お試しください。';
      this.t.skip('予期しないエラー: ' + ex.getMessage() + '\n' + ex.getStackTraceString());
    }
    return result;
  }

  private Scribe buildScribe() { /* ... */ return null; }
}
```

## 規約まとめ

| 要素 | ルール |
|---|---|
| inner class 名 | **`Result` 固定** (Usecase ごとに内包。`XxxUsecase.Result` で参照される) |
| `isSuccess` | `Boolean`。成功で `true`、業務エラー/想定外で `false` |
| `message` | `String`。失敗時のみ詰める。**業務エラーは生メッセージ、想定外は当たり障りのない定型文** |
| `data` | 処理結果の本体。型は自由 (取得系=SObject / `List<SObject>` / inner DTO、更新系=更新件数 `Integer` や更新済 Id リスト等)。不要なら省略可 |
| `@AuraEnabled` | **3 プロパティすべてに付与**。LWC に渡すには必須。`{ get; set; }` を明示する |
| 例外設計 | 想定内の業務エラーは `UsecaseException` を throw → `catch` で `message` に透過。想定外 `Exception` は別 catch で握りつぶし、定型文に差し替えて内部だけ Trace に残す |
| Trace | 成功 `t.finish()`、失敗 (業務/想定外とも) `t.skip()`。冒頭 `t.start()`。スタックトレースは `t.skip()` の引数に入れて握る (詳細は `apex-trace` skill) |

## UsecaseException の役割

```apex
/**
 * Usecase 内の業務エラーを表す例外。
 * LWC/フロー経由の Usecase では catch して Result.message に透過し、
 * トリガー経由の Usecase では addError 相当の停止に使う。
 */
public with sharing class UsecaseException extends Exception {}
```

- **「ユーザーに見せてよい想定内エラー」** の目印。バリデーション失敗・対象なし・権限なし等。
- LWC 経由: `catch (UsecaseException)` で `result.message = ex.getMessage()` に透過 → そのまま toast 表示できる文言にしておく。
- 想定外の `Exception` (NPE, クエリ例外等) は**別の catch** で受け、`message` には内部詳細を出さず定型文を返す。原因は `t.skip()` でログに残す。

## Controller (@AuraEnabled エントリ) は 1 行で委譲

Controller は Handler 相当。**ロジックを書かず、Usecase を生成して `invoke()` した戻り値をそのまま返す**だけ。戻り型は `XxxUsecase.Result`。

```apex
public with sharing class ActivityRequestController {
  @AuraEnabled
  public static GetActivityRequestDetailUsecase.Result getActivityRequestDetail(Id requestId) {
    return new GetActivityRequestDetailUsecase(requestId).invoke();
  }

  @AuraEnabled
  public static UpdateStatusesUsecase.Result updateStatuses(List<Id> requestIds, String newStatus) {
    return new UpdateStatusesUsecase(requestIds, newStatus).invoke();
  }
}
```

- `@AuraEnabled(cacheable=true)` を使うのは副作用のない取得系のみ。更新系には付けない。
- Controller 自身は `try/catch` しない (Usecase 内で握って Result に畳むのが前提)。`AuraHandledException` を投げる設計にはしない。

## LWC (JS) 側の受け方

例外ではなく**戻り値で分岐する**のがこの規約の眼目。`isSuccess` を見てから `data` / `message` を使う。

```js
import getDetail from '@salesforce/apex/ActivityRequestController.getActivityRequestDetail';

async load(requestId) {
  const result = await getDetail({ requestId });
  if (!result.isSuccess) {
    this.showToast('エラー', result.message, 'error');
    return;
  }
  this.record = result.data;          // 成功時のみ data を使う
}
```

- Apex が throw しない設計なので、JS 側は `.catch()` を「通信/システム例外の最後の砦」としてだけ持ち、業務エラーは `result.isSuccess` で扱う。
- `result.message` は表示用に整っている前提 (業務エラーは UsecaseException の文言、想定外は定型文)。

## 使い分けの境界

| Usecase の駆動元 | `invoke()` 戻り値 | エラーの扱い |
|---|---|---|
| **LWC / Flow** (`@AuraEnabled`) | `Result` DTO | `UsecaseException` を catch → `message` 透過 |
| **Trigger** | `void` | `UsecaseException` 等で `addError` 相当の停止 |
| **Batch / Schedulable** | `void` | ログ (Trace) + 必要なら再試行設計 |

> 「public は invoke() のみ / 生焼けオブジェクトを作らない」という Usecase 標準規約は駆動元を問わず共通。Result DTO はそこに**「LWC/Flow には構造化して返す」**を足したもの。

## 深掘り (元ドキュメント)

- https://krileworks.com/document/ja/handler-usecase-architecture.md — Handler+Usecase の2段構え。invoke() の戻り型を駆動元で選ぶ思想 (LWC=Result DTO / Trigger=void) の出典。
- 関連 skill: ログの仕込み方は `apex-trace`、`data` に詰めるクエリ結果の組み立ては `apex-eloquent`。
- 実装の生きた参照: `force-app/main/default/classes/Usecases/activityRequet/` 配下 (`GetActivityRequestDetailUsecase` 等) と `ActivityRequestController.cls`。
