mcpField
S
safe-action-client

safe-action-client

github.com/next-safe-action開発11.2k 回インストール8 週間前に更新
リポジトリ

safe-action-client について

safe-action-clientは、TypeScriptでnext-safe-actionのサーバーアクションを定義・利用するためのスキルです。チェーン式API、スキーマ検証、ミドルウェア、クライアントフックを扱います。

主な機能

  1. 01チェーン式アクションクライアントの設定
  2. 02スキーマによる入出力の検証
  3. 03検証前後のミドルウェア追加
  4. 04メタデータとバインド引数のスキーマ定義
  5. 05アクション用・ステートフルなクライアントフックの利用

活用シーン

  1. 01型付きサーバーアクションの設定
  2. 02アクションの入力と戻り値の検証
  3. 03フックによるクライアント側状態の管理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/next-safe-action/skills --skill safe-action-client

インストール後、Claude Code で/safe-action-clientを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

client-setup.md
error-handling.md
input-output-validation.md

詳細

next-safe-action クライアントとアクションの定義

クイックスタート

// src/lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action";

export const actionClient = createSafeActionClient();
// src/app/actions.ts
"use server";

import { z } from "zod";
import { actionClient } from "@/lib/safe-action";

export const greetUser = actionClient
  .inputSchema(z.object({ name: z.string().min(1) }))
  .action(async ({ parsedInput: { name } }) => {
    return { greeting: `Hello, ${name}!` };
  });

チェーン可能な API の順序

createSafeActionClient(opts?)
  .use(middleware)              // repeatable, adds pre-validation middleware
  .metadata(data)              // required if defineMetadataSchema is set
  .inputSchema(schema, utils?) // Standard Schema or async factory function
  .bindArgsSchemas([...])      // schemas for .bind() arguments (order with inputSchema is flexible)
  .useValidated(middleware)    // repeatable, adds post-validation middleware (requires inputSchema or bindArgsSchemas before it)
  .outputSchema(schema)        // validates action return value
  .action(serverCodeFn, utils?)      // creates SafeActionFn
  .stateAction(serverCodeFn, utils?) // creates SafeStateActionFn (for useStateAction, useOptimisticStateAction, or React's useActionState)

各メソッドは新しいクライアントインスタンスを返します。チェーンは不変です。

エントリーポイント

エントリーポイント環境エクスポート
next-safe-actionサーバーcreateSafeActionClient、createMiddleware、createValidatedMiddleware、returnValidationErrors、returnServerError、flattenValidationErrors、formatValidationErrors、DEFAULT_SERVER_ERROR_MESSAGE、エラークラス、すべてのコア型
next-safe-action/hooksクライアントuseAction、useOptimisticAction、useStateAction、useOptimisticStateAction、フックの型
next-safe-action/stateful-hooksクライアントuseStateAction(後方互換性のために hooks から再エクスポート)

関連ドキュメント

アンチパターン

// BAD: Missing "use server" directive — action won't work
import { actionClient } from "@/lib/safe-action";
export const myAction = actionClient.action(async () => {});

// GOOD: Always include "use server" in action files
"use server";
import { actionClient } from "@/lib/safe-action";
export const myAction = actionClient.action(async () => {});
// BAD: Calling .action() without .metadata() when metadataSchema is defined
const client = createSafeActionClient({
  defineMetadataSchema: () => z.object({ actionName: z.string() }),
});
client.action(async () => {}); // TypeScript error!

// GOOD: Always provide metadata before .action() when schema is defined
client
  .metadata({ actionName: "myAction" })
  .action(async () => {});
// BAD: Returning an error instead of throwing
export const myAction = actionClient
  .inputSchema(z.object({ email: z.string().email() }))
  .action(async ({ parsedInput }) => {
    const exists = await db.user.findByEmail(parsedInput.email);
    if (exists) {
      return { error: "Email taken" }; // Not type-safe, not standardized
    }
  });

// GOOD: Use returnValidationErrors for field-level errors
import { returnValidationErrors } from "next-safe-action";

export const myAction = actionClient
  .inputSchema(z.object({ email: z.string().email() }))
  .action(async ({ parsedInput }) => {
    const exists = await db.user.findByEmail(parsedInput.email);
    if (exists) {
      returnValidationErrors(z.object({ email: z.string().email() }), {
        email: { _errors: ["Email is already in use"] },
      });
    }
    return { success: true };
  });

想定される検証以外のビジネスエラー(「在庫切れ」「見つかりません」など)には、代わりに returnServerError(payload) を使用してください。これにより、handleServerError を経由せず、result.serverError に型付きペイロードが設定されます。詳しくはサーバーエラーの処理を参照してください。

サーバーコード関数のパラメーター

.action() に渡される関数は、単一のオブジェクトを受け取ります。

.action(async ({
  parsedInput,           // validated input (typed from inputSchema)
  clientInput,           // raw client input (unknown)
  bindArgsParsedInputs,  // validated bind args tuple
  bindArgsClientInputs,  // raw bind args
  ctx,                   // context from middleware chain
  metadata,              // metadata set via .metadata()
}) => {
  // return data
});

.stateAction() の場合、第 2 引数が追加されます。

.stateAction(async ({ parsedInput, ctx }, { prevResult }) => {
  // prevResult is the previous SafeActionResult (structuredClone'd)
  return { count: (prevResult.data?.count ?? 0) + 1 };
});

コメント