Best practices

Server Function のエラーハンドリング

想定内の結果をデータとして返し、Server Function の失敗には安全なメッセージを表示します。

Server Function には二種類のエラーハンドリングがあります。想定内の業務上の結果はデータとして返し、無効な入力や実行時の失敗はクライアント境界で扱います。 これは Mutation、Query、Stream の呼び出しに共通です。

想定内の結果をデータとして返す

予約済みの名前のように想定内の結果は、ハンドラーからタグ付きの成功値として返します。 Mutation の呼び出し側は useActionState でその値を表示でき、Query と Stream の呼び出し側は通常の結果と同じように表示できます。

// src/greet.ts
"use server";

import { Effect, Schema } from "effect";
import { EFFRONT } from "./effront";

export const greet = EFFRONT.ServerFn.make({
  input: Schema.Struct({ name: Schema.NonEmptyString }),
  handler: ({ name }) =>
    Effect.succeed(
      name === "Admin"
        ? { _tag: "ReservedName" as const }
        : { _tag: "Greeting" as const, message: `Hello, ${name}.` },
    ),
});

こうすると、想定内の結果を入力検証や予期しない失敗と区別できます。

安全な失敗メッセージを表示する

query と stream は、クライアント側の実行時の失敗を @effront/core/query の ServerFnError として Effect のエラーチャネルで返します。

エラー意味利用者に表示する内容
ServerFnInputError入力をデコードまたは検証できなかった。入力が不正である説明。
ServerFnDefectハンドラーで予期しない失敗が起きた。再試行を促す一般的なメッセージ。
ServerFnTransportErrorブラウザーがリクエストを完了できなかった。接続と再試行を案内するメッセージ。

既知のタグを扱い、defect の detail や stack を表示しないでください。 次の例は Query ガイドの lookupTicketを利用します。

// src/ticket-message.ts
"use client";

import { query } from "@effront/core/query";
import { Effect } from "effect";
import { lookupTicket } from "./ticket";

const lookupMessage = (ticketCode: string) =>
  query(lookupTicket)({ ticketCode }).pipe(
    Effect.map(({ status }) => status),
    Effect.catchTags({
      ServerFnInputError: () => Effect.succeed("チケット番号が無効です。"),
      ServerFnDefect: () => Effect.succeed("確認に失敗しました。"),
      ServerFnTransportError: () => Effect.succeed("通信できません。再試行してください。"),
    }),
  );

Mutation では Schema のデコードと予期しないハンドラーの失敗は、useActionState の state を更新するのではなく action の失敗になります。周辺のエラー UI は React の useActionState リファレンスを参考に選びます。