mcpField
V
vtex-io-session-apps

vtex-io-session-apps

github.com/vtex開発471.1k 回インストール2 週間前に更新
リポジトリ

vtex-io-session-apps について

VTEX IOのセッショントランスフォームを構築する開発者向けのスキルです。依存関係や名前空間の設計、入力の伝播、キャッシュ、計算済みセッション状態の参照方法を扱います。

主な機能

  1. 01セッショントランスフォームの入出力宣言
  2. 02名前空間の所有権と依存関係の設計
  3. 03public.* フィールドによる入力伝播
  4. 04LRU と VBase によるキャッシュ
  5. 05古い状態や競合状態の調査

活用シーン

  1. 01トランスフォームの設定フィールド定義
  2. 02上流状態から業務フィールドを計算
  3. 03ネイティブトランスフォームの再計算を促進
  4. 04B2B セッションや名前空間の競合を診断

インストール

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

$npx skills add https://github.com/vtex/skills --skill vtex-io-session-apps

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

詳細

VTEX IO セッション変換アプリ

このスキルを使用する場面

VTEX IO アプリが VTEX セッションシステム(vtex.session)と連携し、後続の変換処理、ストアフロント、またはチェックアウトが依存する状態を導出、計算、または伝播する場合に、このスキルを使用してください。

  • 上流のセッション状態からカスタムフィールドを計算するセッション変換処理を構築する場合(例:外部バックエンドから価格設定のコンテキストを取得する、組織データから地域化を行う)
  • vtex.session/configuration.json で入力/出力フィールドを宣言する場合
  • アプリが所有する名前空間と、読み取り対象の名前空間を決める場合
  • ネイティブの変換処理(プロフィール、検索、チェックアウト)を再実行するため、値を public.* 入力に伝播する場合
  • アプリ間のセッションフィールドの古い値、競合状態、または名前空間の衝突をデバッグする場合
  • storefront-permissions、カスタム変換処理、チェックアウトが連携するB2B セッションフローを設計する場合

次の用途には、このスキルを使用しないでください。

  • 一般的な IO バックエンドのパターン(vtex-io-service-apps を使用してください)
  • セッション変換処理以外のパフォーマンスパターン(vtex-io-application-performance を使用してください)
  • GraphQL スキーマまたはリゾルバーの設計(vtex-io-graphql-api を使用してください)

判断ルール

名前空間の所有権
  • 各セッションアプリが所有する出力名前空間は、必ず1つです(または、その中の少数のフィールドです)。名前空間名は通常、アプリの概念に一致します(例:rona、myapp、storefront-permissions)。
  • 他のアプリの出力名前空間には書き込まないでください。 storefront-permissions が storefront-permissions.organization を所有している場合、変換処理でその値を上書きしてはいけません。代わりに入力として読み取ってください。
  • org、cost center、postal、country など、VTEX が所有するフィールドが storefront-permissions、profile、checkout、store にすでに存在する場合、それらを自分の名前空間に複製しないでください。自分の名前空間に含めるのは、自分のバックエンドまたは計算処理から取得したデータだけにしてください。
public は入力、private は読み取りモデル
  • public.* フィールドは入力サーフェスです。セッション変換処理を実行するために、購入者やフローが設定する値です(例:ジオロケーション、フラグ、UTM、ユーザーの意図)。ストアフロントのコードで public.* を正規の読み取りモデルとして扱わないでください。
  • private 名前空間(profile、checkout、store、search、storefront-permissions、独自のカスタム名前空間)は読み取りモデルです。入力から導出された計算済みの出力が含まれます。フロントエンドコンポーネントでは、ビジネスルールや表示のために private 名前空間のフィールドを読み取ってください。
  • 変換処理からネイティブアプリに影響を与える必要がある場合(例:cost center の住所から導出した postal code を設定する)、ネイティブアプリが入力として宣言している public.* 入力フィールドを更新してください。これによりプラットフォームが上流の変換処理を再実行し、private の出力が一貫した状態に保たれます。これは真実を複製することではなく、入力の伝播です。
変換処理の順序(DAG)
  • VTEX セッションは、各アプリの vtex.session/configuration.json で宣言された入力/出力の依存関係に基づき、有向非巡回グラフ(DAG)内で変換処理を実行します。
  • 宣言された入力フィールドのいずれかが変更されると、その変換処理が実行されます。storefront-permissions.costcenter に依存する場合、storefront-permissions がそのフィールドを出力した後に変換処理が実行されます。
  • 依存関係の順序を慎重に設定してください。storefront-permissions と profile の両方の出力が必要な場合は、両方を入力として宣言してください。そうすることで、プラットフォームは両方の処理の後に実行されるようスケジュールします。
変換処理内のキャッシュ
  • セッション変換処理は、宣言された入力に変更があったセッション変更ごとに実行されます。処理は高速でなければなりません。
  • 頻繁に参照するデータ(org データ、設定、トークン)には、TTL を短く設定した LRU(プロセス内、ワーカーごと)を使用してください。
  • 短時間の古さを許容できるデータ(外部バックエンドの応答、計算済みのマッピング)には、VBase の stale-while-revalidate を使用してください。古いデータをすぐに返し、バックグラウンドで再検証します。
  • どの IO サービスでも適用されるテナントキーのルールに従ってください。インメモリキャッシュのキーには account と workspace を含める必要があります(vtex-io-application-performance を参照してください)。
フロントエンドでのセッションデータの利用
  • ストアフロントのコンポーネントは、items= クエリパラメーターを使って特定のセッション項目を要求してください(例:items=rona.storeNumber,storefront-permissions.costcenter)。
  • 正規の状態を取得するには、該当するプライベート名前空間(rona.*、storefront-permissions.*、profile.* など)から読み取ってください。
  • public.* への書き込みは、場所の選択やフラグの切り替えなど、ユーザーの意図を設定する場合に限ってください。プライベート名前空間ですでに提供されている値を「キャッシュ」するために public.* へ書き込んではいけません。

厳守すべき制約

制約:別のアプリが出力する名前空間のフィールドを、自分の名前空間に複製しない

セッション変換処理で出力するのは、自分の計算処理またはバックエンドから得られるフィールドのみにしてください。storefront-permissions、profile、checkout がすでに管理している ID、住所、組織のフィールドをコピーすると、部分的な失敗によって食い違う二つの信頼できる情報源が生まれます。

これが重要な理由 — 二つの名前空間に同じ情報(例:costCenterId が自分の名前空間と storefront-permissions の両方にある)が含まれると、セッションの一部のみが更新された後に、コンシューマーが一貫性のない値を読み取るようになります。デバッグにかかる時間が大幅に増え、競合状態も発生します。

検出方法 — 変換処理の出力に、storefront-permissions.* または profile.* の出力と重複する organization、costcenter、postalCode、country などのフィールドが含まれています。または、フロントエンドが同じ論理フィールドを異なる二つの名前空間から読み取っています。

正しい方法 — storefront-permissions.costcenter を入力として読み取り、バックエンド固有のフィールド(例:myapp.priceTable、myapp.storeNumber)の計算に使用し、派生したフィールドのみを出力します。

{
  "my-session-app": {
    "input": {
      "storefront-permissions": ["costcenter", "organization"]
    },
    "output": {
      "myapp": ["priceTable", "storeNumber"]
    }
  }
}

誤った方法 — VTEX が所有するフィールドの複製を出力します。

{
  "my-session-app": {
    "output": {
      "myapp": [
        "costcenter",
        "organization",
        "postalCode",
        "priceTable",
        "storeNumber"
      ]
    }
  }
}
制約:ネイティブ変換処理に影響を与えるには、直接上書きするのではなく、入力の伝播を使用する

変換処理でネイティブアプリが使用する値(例:コストセンターの住所から得られる郵便番号)を導出する場合は、アプリが宣言する入力フィールド(通常は public.postalCode、public.country)として設定してください。checkout.postalCode や search.postalCode に直接書き込んではいけません。

これが重要な理由 — ネイティブ変換処理は、入力フィールドが変更されると、出力フィールドを再計算することを前提としています。出力名前空間に直接書き込むと、再計算が迂回され、派生状態(例:regionId が更新されない、チェックアウトの住所に一貫性がなくなる)が古いまま残ります。

検出方法 — 変換処理が別のアプリの所有する名前空間に出力フィールドを宣言しています(例:output: { checkout: [...] } または output: { search: [...] })。または、自分が所有していない名前空間の値を使ってセッションに PATCH しています。

正しい方法 — ネイティブアプリが入力として使用するフィールドは、そのアプリの vtex.session/configuration.json で確認したうえで、public に出力として宣言します。

{
  "my-session-app": {
    "output": {
      "myapp": ["storeNumber", "priceTable"],
      "public": ["postalCode", "country", "state"]
    }
  }
}

誤った方法 — search または checkout の出力名前空間に直接書き込みます。

{
  "my-session-app": {
    "output": {
      "myapp": ["storeNumber", "priceTable"],
      "checkout": ["postalCode", "country"],
      "search": ["facets"]
    }
  }
}
制約:フロントエンドでは、正規のビジネス状態を public ではなくプライベート名前空間から読み取る

ストアフロントのコンポーネントとミドルウェアは、public.* フィールドではなく、信頼できるプライベート名前空間(例:storefront-permissions.organization、profile.email、myapp.priceTable)からセッションデータを読み取る必要があります。

これが重要な理由 — public.* フィールドは入力値であり、古かったり、ユーザーによって設定されていたり、一部しか設定されていなかったりする可能性があります。プライベート名前空間のフィールドは、すべての変換処理が実行された後の計算済みの正しい値です。プロファイルまたはチェックアウトから導出された値ではなく public.postalCode を読み取ると、古い値や一貫性のない値が表示されることになります。

検出方法 — 対応するプライベートフィールドではなく、public.storeNumber、public.organization、public.costCenter を表示またはビジネスロジックに使用している React コンポーネントやミドルウェアがあります。

正しい方法

// Read from the authoritative namespace
const { data } = useSessionItems([
  "myapp.storeNumber",
  "myapp.priceTable",
  "storefront-permissions.costcenter",
  "storefront-permissions.organization",
]);

誤った方法

// Reading from public as if it were the source of truth
const { data } = useSessionItems([
  "public.storeNumber",
  "public.organization",
  "public.costCenter",
]);

推奨パターン

vtex.session/configuration.json

変換処理の入力依存関係と出力フィールドを宣言します。

{
  "my-session-app": {
    "input": {
      "storefront-permissions": [
        "costcenter",
        "organization",
        "costCenterAddressId"
      ]
    },
    "output": {
      "myapp": ["storeNumber", "priceTable"]
    }
  }
}
変換ハンドラー
// node/handlers/transform.ts
export async function transform(ctx: Context) {
  const { costcenter, organization } = parseSfpInputs(ctx.request.body);

  if (!costcenter) {
    ctx.body = { myapp: {} };
    return;
  }

  const costCenterData = await getCostCenterCached(ctx, costcenter);
  const pricing = await resolvePricing(ctx, costCenterData);

  ctx.body = {
    myapp: {
      storeNumber: pricing.storeNumber,
      priceTable: pricing.priceTable,
    },
  };
}
変換処理内のキャッシュ
// Two-layer cache: LRU (sub-ms) -> VBase (persistent, SWR) -> API
const costCenterLRU = new LRU<string, CostCenterData>({
  max: 1000,
  ttl: 600_000,
});

async function getCostCenterCached(ctx: Context, costCenterId: string) {
  const { account, workspace } = ctx.vtex;
  const key = `${account}:${workspace}:${costCenterId}`;

  const lruHit = costCenterLRU.get(key);
  if (lruHit) return lruHit;

  const result = await staleFromVBaseWhileRevalidate(
    ctx.clients.vbase,
    "cost-centers",
    costCenterId,
    () => fetchCostCenterFromAPI(ctx, costCenterId),
    { ttlMs: 1_800_000 },
  );

  costCenterLRU.set(key, result);
  return result;
}
service.json ルート
{
  "routes": {
    "transform": {
      "path": "/_v/my-session-app/session/transform",
      "public": true
    }
  }
}
セッションエコシステムの把握

transform を構築する際は、ストアの transform DAG を整理します。

authentication-session → impersonate-session → profile-session
profile-session → store-session → checkout-session
profile-session → search-session
authentication-session + checkout-session + impersonate-session → storefront-permissions
storefront-permissions → YOUR-TRANSFORM (reads SFP outputs)

transform は、必要とする依存関係チェーンの末尾に配置します。入力を正しく宣言すれば、プラットフォームは上流のすべての transform の後に実行するようスケジュールできます。

よくある失敗パターン

  • フロントエンドから updateSession 経由で B2B の状態を書き込む — storefront-permissions と独自の transform に B2B セッションフィールドの計算を任せず、フロントエンドから直接 PATCH しています。これにより競合状態や不完全な状態が生じ、信頼できる情報源が重複します。
  • VTEX が所有するフィールドを重複させる — costcenter、organization、postalCode がすでに storefront-permissions または profile に存在するのに、独自の名前空間へコピーしています。
  • キャッシュのない低速な transform — LRU と VBase SWR を使わず、transform が呼び出されるたびに外部 API を呼び出しています。transform は、宣言された入力に変更があるたびに実行されるため、高速でなければなりません。
  • public.* を信頼できる情報源として読み取る — フロントエンドのコンポーネントが、プライベートな名前空間のフィールドではなく public.organization や public.storeNumber を読み取るため、表示が古くなったり一貫性を欠いたりします。
  • 他のアプリの出力名前空間に書き込む — 所有していない checkout、search、storefront-permissions 名前空間に出力フィールドを宣言し、ネイティブの transform 再計算を迂回しています。
  • LRU のテナントキーが不足している — 組織または価格データのインメモリキャッシュを、account:workspace を含めずにエンティティ ID だけでキー付けしています。マルチテナントの共有ポッドでは安全ではありません。

レビューチェックリスト

  • transform の出力は、他の名前空間のフィールドを重複させず、独自の計算またはバックエンドに由来するフィールドのみですか?
  • vtex.session/configuration.json で入力の依存関係を正しく宣言していますか?
  • 出力フィールドは独自の名前空間に限定されていますか(伝播が必要な場合の public.* 入力は除きます)?
  • public.* は、読み取りモデルを二重化せず、入力の伝播にのみ使用していますか?
  • フロントエンドのコンポーネントは、ビジネス状態の取得に public.* ではなくプライベートな名前空間を読み取っていますか?
  • transform 内の上流 API 呼び出しは、transform のレイテンシを低く保つためにキャッシュされていますか(LRU + VBase SWR)?
  • マルチテナント環境で安全性を保つため、インメモリキャッシュのキーに account:workspace が含まれていますか?
  • transform の順序(DAG)は正しく、依存先のすべての transform の後に実行されますか?
  • transform が計算するフィールドについて、フロントエンドコードから updateSession を削除しましたか?

関連スキル

参考資料

コメント