mcpField
S
salla-app-functions-handler

salla-app-functions-handler

github.com/sallaapp開発278 回インストール4 日前に更新
リポジトリ

salla-app-functions-handler について

Salla App Functionのハンドラーを実装するコーディングスキルです。アプリ開発者がトリガー固有の型に基づく簡潔なハンドラーを作り、ビジネスロジックを自社APIに委ねる方法や、レスポンス契約とサンドボックスの制約を確認できます。

主な機能

  1. 01実装前にトリガーのペイロード型を確認
  2. 02APIアダプターとしてハンドラーを簡潔化
  3. 03必須のレスポンス契約に準拠
  4. 04V8 isolateの制約に対応
  5. 05外部fetch呼び出しを制限・検証

活用シーン

  1. 01Sallaトリガー用ハンドラーを実装
  2. 02APIレスポンスをトリガーの応答へ変換
  3. 03関数内で未対応のNode.js APIを回避
  4. 04遅延や形式不正のAPIレスポンスに備える

インストール

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

$npx skills add https://github.com/sallaapp/salla-partners-agent-kit --skill salla-app-functions-handler

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

詳細

App Functions — ハンドラーを記述する

トリガーのデフォルトの template と types を使用します。salla_functions action=get、 app_id、trigger で両方を取得します。template をそのままコピーし、コードはすべて 本文の中に記述します。ラッパーの最初と最後の行は、指定されたとおりに変更しないでください(完全なルールとローカルの型チェック → salla-app-functions-validate)。

このスキルは、同期か非同期かが salla-app-functions-design ですでに決定されていることを前提とします(同期の merchant_actions トリガーは操作をブロックまたは変更します。非同期イベントはファイア・アンド・フォーゲットです)。

ハンドラーは最小限に — 独自の API に処理を委譲する

ベストプラクティス:この関数は薄い変換レイヤーとし、ビジネスロジックは含めません。 実際の 処理は独自のバックエンド APIで行い、App Function は(1)トリガーペイロードの読み取り、 (2)API の呼び出し、(3)レスポンスをトリガーが期待する Resp に変換するためにのみ使用します。サンドボックスは制約のある V8 アイソレートで、実行時間に厳しい上限(同期は 5 秒/非同期は 30 秒)があり、npm も使えません — 重いロジック、SDK、永続化、時間のかかる処理は、ここではなくサーバー側で行います。そのため、同期アクション(例:shipment.creating)では、入力を検証 → API を fetch → 結果をトリガーの Resp にマッピング、という処理を行います。最小限で簡潔にすれば、制限時間内に収まり、トリガーの型に対する型チェックも容易になります。

1. 取得した型定義からペイロードのフィールド名を先に確認する(厳守)

厳守事項 — App Function の新規作成と変更のどちらにも適用されます。 ペイロードのプロパティアクセスを記述または変更する前に、salla_functions action=get(app_id、trigger)が返す types 配列内のすべての URLを取得し、.d.ts 型定義から正確なフィールド名と構造を確認してください。ハンドラーが読み取る型付きペイロードは context.payload.data です — 実際に .d.ts で宣言されている名前(および参照されているネストされた構造や列挙型)に基づいて、すべてのプロパティアクセスを記述してください。想定した名前を使ってはいけません。配送 AWB 関数の場合は、sender_address/receiver_address/weight と決めつけるのではなく、型定義に記載された実際の Shipments ペイロードのフィールド(住所、荷物、重量のフィールドなど)を確認します。同じ types URL は salla-app-functions-validate のローカル型チェックにも使われ、推測したフィールドが存在しない場合はビルドが失敗します。ただし、最初に型定義を読むことで、最初から正しい本文を記述できます。

確認事項:「本文を新規作成または変更する前に、ハンドラーが context.payload.data から読み取るすべてのフィールドについて、(再)取得した types の .d.ts で実際に宣言されていることを確認したか — 推測したフィールドはないか?」

export default async (context: Shipments): Promise<Resp> => {
  // ↑ template's first line, unchanged    ↓ ALL your code goes inside the body
  console.log("Shipment cancelled event triggered");
  return Resp.success().setData({});
};

Context オブジェクト

説明用の構造です — 正式な型定義は、salla_functions action=get で取得するトリガーの types の .d.ts です。これらに対して型チェックしてください(data の構造はトリガーごとに異なります)。

context.merchant; // { id: number | string, … }
context.payload; // { event, created_at, merchant, data: { … } } — data varies by trigger
context.settings; // per-merchant App Settings; optional (undefined/null until configured)

関数内から Salla Admin API を呼び出す場合、通常はトークンの配線は不要です — ランタイムが注入する正確な認証情報をトリガーの types/関数コンテキストで確認し、Admin API を直接呼び出してください。呼び出すエンドポイントに必要な最小限の OAuth スコープをリクエストしてください(スコープの設定とトークンの処理 → salla-app-auth)。ベース URL、エンドポイント、エラーの構造 → salla-api-core。設定:context.settings?.apiKey(オプショナルチェーン — 設定されるまでは undefined)。

レスポンスの契約(必須の戻り値)

すべてのハンドラーは、Response の契約に準拠するオブジェクトを返します (レスポンス):

フィールド型必須意味
successbooleanはいロジックが完了したかどうか(同期処理では、続行するか拒否するかを決定します)
dataobjectいいえ返すペイロード/エンティティに適用するデータ(同期アクション)
errorstringいいえ人が読めるエラー。同期アクションで success:false の場合は必須です
statusnumberいいえ任意の HTTP ステータス(ビルダー経由)。デフォルトは 200 です
messagestringいいえ任意の補足メッセージ(ビルダー経由)

2 つの戻り値のスタイルがあり、どちらも有効です:

// 1. Plain object — simplest, lowest overhead (good for sync actions, simple events)
return { success: true, data: {} }; // data is optional; include it (even {}) on sync actions

// 2. Response utility class — recommended for Customer Events and complex Merchant Events
return Resp.success().setData({ order_id: id }); // .setData({}) is the recommended convention
return Resp.error().setMessage("Something went wrong").setStatus(500);

ビルダーメソッド(チェーン可能):Resp.success()/Resp.error() でレスポンスを開始します。.setData(obj) は省略可能ですが、推奨されます。空の場合も {} を渡してください。特に同期アクションでは、返された data がエンティティにマージ/適用されるため重要です。.setStatus(code) は省略可能(デフォルトは 200)です。.setMessage(msg) も省略可能です。Resp はあらかじめ宣言されたランタイムグローバルです。インポートせず、そのまま使用してください。

エンティティ名付きビルダー。 一部のトリガーでは、汎用の Resp の代わりにエンティティ固有のビルダーが公開されます。.success()/.error() の形式は同じで、エンティティ用のセッターが追加されています。shipment.creating では Shipment クラスが必須です(コンテキスト型は Shipments、戻り値の型は Promise<Shipment>)。return Shipment.success().setShipmentNumber(id)(成功時は必ず .setShipmentNumber() を呼び出してください)、return Shipment.error().setMessage("…") を使用します。使用する前に、トリガーの types で正確なビルダーとセッターを確認してください(salla-app-functions-validate を参照)。

同期アクション:戻り値の役割

同期アクション(例:shipment.creating)では、マーチャントは処理の完了を待たされます:

  • success: true → アクションが続行され、data が結果のエンティティにマージ/適用されます。
  • success: false → アクションは拒否され、error/.setMessage() が直ちにマーチャントに表示されます。内容は明確で、次に取るべき対応がわかるようにしてください。

非同期イベントでは、戻り値は情報提供のみで、ログに記録されるだけで、すでに完了した操作には影響しません。それでも、どの経路でも必ずレスポンスを返してください。

サンドボックスの制約(V8 アイソレート — 完全な Node.js ではありません)

App Functions は Node.js ランタイムではなく、V8 アイソレートで実行されます (Node.js のサポート)。標準の Node API には制限があります:

  • 外部パッケージは使用できません — npm/外部ライブラリはインポートできません(将来のバージョンで対応予定)。あらかじめ宣言されたグローバルと Web 標準 API のみ使用できます。
  • サポートされていない Node コアモジュール(使用するとエラーになります):fs、fs/promises、net、tls、dgram、http/https サーバー、child_process、cluster、worker_threads、repl、readline、zlib。
  • 一部のみサポート: crypto = Web Crypto API のみ(globalThis.crypto)、buffer =基本機能、stream =WHATWG ストリームのみ、os/path/util/events =一部機能。
  • 外部への HTTP 通信には、標準の fetch() API を使用します(http/https クライアントモジュールやサーバーは使用できません)。外部 API または Salla API にアクセスする方法としてサポートされているのは、これだけです。

外部の各 fetch にタイムアウトを設定する

各 fetch を AbortController で制限し、上流の応答が遅くても予算を超えないようにします。各呼び出しはトリガーの予算を大幅に下回るようにしてください。同期アクションでは合計 500 ms 未満、非同期イベントでは最大 30 s です(正確な上限を確認してください → salla-app-functions-design)。適用する前にレスポンスを検証して整形します。必要な特定のフィールドを抽出して、それらだけを返すことで、不正な形式または悪意のある上流の本文がエンティティに渡らないようにします。

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 2000); // < 2s per call in a sync action
try {
  const res = await fetch("https://api.example.com/data", {
    signal: controller.signal,
  });
  if (!res.ok) return Resp.error().setMessage("Upstream error").setStatus(502);

  const body = await res.json();
  // Validate the shape and extract only the fields you trust — don't return raw JSON.
  if (typeof body?.tracking_number !== "string") {
    return Resp.error().setMessage("Invalid upstream response").setStatus(502);
  }
  return Resp.success().setData({ tracking_number: body.tracking_number });
} catch (error) {
  return Resp.error().setMessage("Upstream timeout").setStatus(504);
} finally {
  clearTimeout(timeout);
}

送信呼び出しの安全対策:

  • 適用前に検証する — res.ok を確認してから、本文の構造を検証し、必要なフィールドだけを返します(上記参照)。これにより、エラー本文や余分なフィールドがエンティティに渡らないようにします。
  • 自分のエンドポイントを信頼する — ハードコードされた URL を呼び出します。URL が設定から取得される場合は、fetch の前に、自分が管理するホストの許可リストと照合します。
  • マーチャントの入力をサニタイズする — context.settings はマーチャントが制御します。型と形式を検証し、送信リクエストに含める前に、それぞれの値を用途に応じてエンコード/エスケープします(パス/クエリのエンコード、ヘッダー値)。
  • ログにシークレットを含めない — context.settings やトークンを決してログに記録しないでください。

確認項目:「すべてのパスで Resp.success().setData(...) を返す、npm を使わずサポート対象外のコア機能を使わない、すべての fetch にタイムアウトを設定する、使用前にレスポンスを検証して整形する、設定をサニタイズする、シークレットをログに記録しない、という要件を満たしていますか?」 → salla-app-functions-validate。

コメント