mcpField
C
commerce-app-storage

commerce-app-storage

github.com/adobe開発1364 回インストール1 日前に更新
リポジトリ

commerce-app-storage について

Adobe Commerce App Builder プロジェクトにデータベースストレージを追加する開発スキルです。ワークスペースのデータベース設定から、ドキュメントを読み書きするランタイムアクションの作成、インストール時の準備、検証までを案内します。

主な機能

  1. 01ワークスペースデータベースの設定とプロビジョニング
  2. 02Web またはイベント処理アクションの作成
  3. 03コレクションでのドキュメント CRUD
  4. 04インストール時のコレクションとインデックス作成
  5. 05デプロイ前の設定検証

活用シーン

  1. 01Commerce アプリで注文レコードを保存
  2. 02イベントや Webhook に応じたデータの読み書き
  3. 03アプリのインストール時にコレクションとインデックスを初期化

インストール

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

$npx skills add https://github.com/adobe/aio-commerce-sdk --skill commerce-app-storage

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

同梱ファイル

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

assets/
db-action.ts
setup-database.ts
evals/
evals.json

詳細

コマースアプリにデータベースストレージを追加する

App Builder Database Storage を既存のコマースアプリに統合し、@adobe/aio-lib-db を使用してドキュメントを読み書きするランタイムアクションをスキャフォールドします。このライブラリは MongoDB に似ており、データはドキュメントのコレクションに保存され、使い慣れたフィルターでクエリできます。

データベースアクセスのコードはアクションの種類にかかわらず同一です。異なるのは、アクションの登録方法とハンドラーが返す内容です。

  • Web アクション — HTTP から呼び出し可能(web: "yes")です。@adobe/aio-commerce-lib-core の responses ヘルパーを使って構築したレスポンスを返します。
  • イベント/Webhook アクション — Commerce イベントまたは Webhook によって呼び出されます。commerce-app-eventing(runtimeActions)または commerce-app-webhooks(runtimeAction)を介して、app.commerce.config.ts から参照されます。

前提条件

  • アプリがスキャフォールドされ、初期化済みであることを確認してください。設定ファイルが存在するだけでは不十分です。次の両方が必要です。
    • プロジェクトルートに app.commerce.config.ts が存在すること。
    • プロジェクトが初期化済みであること。生成された src/commerce-extensibility-1/ ディレクトリと、インストール済みの node_modules(@adobe/aio-commerce-lib-app 依存関係)で確認できます。
  • app.commerce.config.ts が存在しない場合は、処理を中止し、まず commerce-app-init を実行してください(設定ファイルを書き込み、その後 init を実行します)。
  • 設定ファイルが存在するが、プロジェクトが初期化されていない場合(src/commerce-extensibility-1/ または node_modules がない場合)は、続行する前に npx @adobe/aio-commerce-lib-app init を実行してください。init はべき等です。既存の設定ファイルを検出して対話型プロンプトをスキップし、依存関係をインストールしてプロジェクトファイルを生成します。
  • アクションとカスタムインストールスクリプトを TypeScript で記述できるのは、プロジェクトに TypeScript のビルド設定(webpack-config.cjs とルートの tsconfig.json)がある場合のみです。この設定は、TypeScript の Commerce 設定向けに init がスキャフォールドします。詳細は commerce-app-init を参照してください。それ以外の場合は JavaScript で記述してください。
  • App Builder Data Services API(API コード AppBuilderDataServicesSDK)を、データベースを使用するすべてのワークスペースで、Adobe Developer Console を通じてプロジェクトに追加する必要があります(App Builder 以外の特別なライセンスは不要です)。これがないと、ランタイムアクションはデータベースサービスに認証できません。

ステップ 1 — ワークスペースのデータベースをプロビジョニングする

AIO プロジェクトのワークスペースとワークスペースデータベースには、厳密な 1 対 1 の関係があります。プロビジョニングには、app.config.yaml で宣言的に設定する方法を推奨します。aio app deploy の実行時に、データベースが(まだ存在しない場合は)プロビジョニングされます。

application:
  runtimeManifest:
    database:
      auto-provision: true
      region: emea # amer | apac | emea | aus — the single source of truth for the region

拡張機能のみのアプリには回避策が必要です。 aio-lib-db ではなく aio app CLI プラグインのバグにより、aio app deploy は、application ランタイムマニフェストにランタイムアクションを含むパッケージが少なくとも 1 つある場合にのみ、宣言的な自動プロビジョニングを実行します。extensions のみで構築されたアプリ(提出ガイドラインで推奨されるレイアウト)には application アクションがないため、デプロイ時にプロビジョニングが気づかれないままスキップされます。空のパッケージマップと、プロビジョニングの手順で必要なディレクトリを作成する post-app-build フックを追加し、プロビジョニングが実行される程度に application ブロックを整えてください。

application:
  hooks:
    post-app-build: "mkdir -p dist/application/actions" # provisioning expects this dir to exist
  runtimeManifest:
    packages: {} # empty map — required by the config schema so the application block validates with no actions
    database:
      auto-provision: true
      region: emea # single source of truth — see the region callout below

ローカル開発では、aio app run/aio app dev の実行時に宣言的な自動プロビジョニングは行われません。CLI のフォールバックを使って、事前に一度プロビジョニングしてください(セルフサービスで、特別な権限は不要です)。aio app db … コマンドは、storage CLI plugin をインストールした後にのみ使用できます。

aio plugins install @adobe/aio-cli-plugin-app-storage
aio app db provision --region <amer|apac|emea|aus>

リージョンの情報源は 1 か所に統一してください。 マニフェストの database ブロックにある region は、すべてのアクションとインストール手順(ステップ 6)で、各 initDb({ region }) 呼び出し(または AIO_DB_REGION)に渡す region と一致している必要があります。一致しない場合、接続に失敗します。リージョンを変更するとデータは破壊的に削除されます。aio app db delete を実行し、マニフェストの database.region を更新してから、再プロビジョニングしてください。

ステップ 2 — ライブラリをインストールする

npm install @adobe/aio-lib-db

ステップ 3 — 意図を理解する

ユーザーから以下を確認します。

  • アクションの種類:Web アクションまたはイベント/Webhook アクション(上記の 2 つの形式を参照)。
  • 必要なコレクション名と操作(insert/find/update/delete)。
  • リージョン:マニフェストの database.region と一致している必要があります(ステップ 1 のリージョンに関する説明を参照)。init() に渡すか、AIO_DB_REGION を設定してください。

ステップ 4 — アクションを登録する

src/commerce-extensibility-1/ext.config.yaml 内のユーザー定義パッケージにアクションを追加します(予約済みの app-management 以外であれば、任意の名前を使用できます)。

すべての DB アクションで include-ims-credentials: true が必須です。 これがないと、aio-lib-db は認証に必要な IMS トークンを取得できず、実行時に接続が失敗します(インストール中にアクションが実行される場合は、アプリのインストールも失敗します)。このアノテーションを省略しないでください。

OAuth Server-to-Server の認証情報を AIO_COMMERCE_AUTH_IMS_* の入力で手動設定しないでください。 この従来のフォールバックは非推奨であり、今後のメジャーリリースで削除される予定です。

# src/commerce-extensibility-1/ext.config.yaml
runtimeManifest:
  packages:
    app-management:
      # ... auto-generated — do not edit
    my-app: # any name except "app-management"
      actions:
        store-record:
          function: actions/store-record/index.js # relative to src/commerce-extensibility-1/
          runtime: nodejs:24
          web: "yes" # "yes" for a web action; "no" for an event/webhook action
          annotations:
            include-ims-credentials: true # REQUIRED for aio-lib-db auth
フィールド制約
パッケージ名小文字の英数字とハイフンのみ。予約済みの app-management は使用できません
functionsrc/commerce-extensibility-1/ からの相対パス。src/... やルート相対パスは不可
include-ims-credentialstrue が必須。これがないと init() が IMS トークンを取得できず、接続に失敗します
webHTTP から呼び出す Web アクションでは "yes"、イベント/Webhook ハンドラーでは "no"
コレクション名空でない文字列。存在しない場合、最初の書き込み時に作成されます
リージョンマニフェストの database.region(amer | apac | emea | aus)と一致している必要があります

ステップ 5 — ハンドラーを実装する

すべてのハンドラーは同じライフサイクルに従います。IMS 認証を解決 → トークンを発行 → init → connect → コレクションを使用 → 必ず finally で close。

// Web action — src/commerce-extensibility-1/actions/store-record/index.ts
import { buildErrorResponse, ok } from "@adobe/aio-commerce-lib-core/responses";
import {
  getImsAuthProvider,
  resolveImsAuthParams,
} from "@adobe/aio-commerce-lib-auth";
import { init as initDb } from "@adobe/aio-lib-db";

export async function main(params: Record<string, unknown>) {
  let client;
  try {
    // Resolve credentials injected by include-ims-credentials, then mint a raw token string.
    const authProvider = getImsAuthProvider(resolveImsAuthParams(params));
    const token = await authProvider.getAccessToken();
    const db = await initDb({ token, region: "emea" }); // must match the manifest database.region
    client = await db.connect();
    const records = client.collection("records");

    const result = await records.insertOne({
      ...(params.document as object),
      createdAt: new Date().toISOString(),
    });
    return ok({ body: { result } });
  } catch (error: any) {
    return buildErrorResponse(error.statusCode || 500, {
      body: { message: error.message },
    });
  } finally {
    if (client) await client.close(); // always close — avoids connection leaks
  }
}

イベントアクションと Webhook アクションはどちらも web: "no" で登録しますが、ペイロードの形式は異なります。同じものとして扱わないでください。

  • イベントハンドラー(commerce-app-eventing 経由で接続):抽出されたフィールドは params.data.value に格納されます。params.data には _metadata(Commerce インスタンスのメタデータ)と source(Commerce のイベント設定から取得した、マーチャント/環境 ID のペア)も含まれます。いずれもハンドラーへの入力ではありません。
  • Webhook ハンドラー(commerce-app-webhooks 経由で接続):data のラッパーはありません。Commerce 操作のペイロードは params に直接格納されます(例:params.order)。レスポンスには、上記で使用した通常の ok/buildErrorResponse 形式ではなく、@adobe/aio-commerce-lib-webhooks/responses の操作ヘルパーを使用する必要があります(commerce-app-webhooks を参照)。
// Event handler — src/commerce-extensibility-1/actions/store-order/index.ts
export async function main(params: Record<string, unknown>) {
  const data = params.data as Record<string, unknown>;
  const value = data.value as Record<string, unknown>;
  let client;
  try {
    const authProvider = getImsAuthProvider(resolveImsAuthParams(params));
    const token = await authProvider.getAccessToken();
    const db = await initDb({ token, region: "emea" });
    client = await db.connect();
    await client
      .collection("orders")
      .insertOne({ orderId: value.order_id, receivedAt: new Date() });
    return ok({ body: { processed: true } });
  } finally {
    if (client) await client.close();
  }
}
// Webhook handler — src/commerce-extensibility-1/actions/log-order/index.ts
import {
  ok,
  successOperation,
} from "@adobe/aio-commerce-lib-webhooks/responses";

export async function main(params: Record<string, unknown>) {
  const order = params.order as Record<string, unknown>; // Commerce operation payload, directly on params
  let client;
  try {
    const authProvider = getImsAuthProvider(resolveImsAuthParams(params));
    const token = await authProvider.getAccessToken();
    const db = await initDb({ token, region: "emea" });
    client = await db.connect();
    await client
      .collection("orders")
      .insertOne({ orderId: order.entity_id, receivedAt: new Date() });
    return ok(successOperation());
  } finally {
    if (client) await client.close();
  }
}

すべての CRUD 操作とカーソルの反復処理を網羅した、注釈付きの完全なリファレンスについては、assets/db-action.ts を参照してください。

ステップ 6 — インストール時にコレクションとインデックスを設定する

App Management アプリでは、カスタムインストールステップ(Commerce Admin からアプリをインストールしたときに一度だけ実行され、アンインストール時に元に戻すことができるスクリプト)を使ってコレクションとインデックスを作成します。最初のリクエスト時にその場で作成する方法より、こちらを優先してください。

defineCustomInstallationStep(install ハンドラーと、任意の uninstall ハンドラー)を使ってステップを作成します。内部では、IMS 認証パラメーターを config ではなく context.params から取得し、ステップ 5 と同じ初期化 → 接続 → close のライフサイクルに従い、コレクションオブジェクトに対して createIndex を呼び出します。

// ./scripts/setup-database.ts — referenced from config as ./scripts/setup-database.ts
import { defineCustomInstallationStep } from "@adobe/aio-commerce-lib-app/management";
import {
  getImsAuthProvider,
  resolveImsAuthParams,
} from "@adobe/aio-commerce-lib-auth";
import { init as initDb } from "@adobe/aio-lib-db";

export default defineCustomInstallationStep({
  install: async (config, context) => {
    let client;
    try {
      // context.params carries the injected IMS credentials — NOT config.
      const authProvider = getImsAuthProvider(
        resolveImsAuthParams(context.params),
      );
      const token = await authProvider.getAccessToken();
      const db = await initDb({ token, region: "emea" }); // must match the manifest database.region
      client = await db.connect();

      const orders = client.collection("held_orders"); // get the collection object first
      await orders.createIndex({ order_id: 1 }, { unique: true }); // createIndex on the collection, not a name string
      return { status: "success" };
    } finally {
      if (client) await client.close(); // always close — avoids connection leaks
    }
  },
  uninstall: async (config, context) => {
    // Tear down your database state here.
    // Leave empty to preserve data across reinstalls.
  },
});

インストールスクリプトは export default を使った ES モジュールとして作成してください。module.exports は使わないでください。 インストールアクションは import * as step from "<script>" を使って各ステップを読み込み、step.default を参照するため、スクリプトでは defineCustomInstallationStep(...) の結果をデフォルトエクスポートする必要があります。CommonJS を使うと module.exports.default が step.default.default として扱われ、検証に失敗します。script のパスは .js または .ts で終わる必要があります。TypeScript で直接作成できるため、別途コンパイルする必要はありません。

app.commerce.config.ts の installation.customInstallationSteps でステップを登録します。script のパスは .ts ファイルを直接指定します。

// app.commerce.config.ts
installation: {
  customInstallationSteps: [
    {
      script: "./scripts/setup-database.ts",
      name: "Set up held-orders collection",
      description: "Creates the held_orders collection and a unique index on order_id",
    },
  ],
},
フィールド制約
scriptプロジェクトルートからの相対パス。ES モジュール(export default)であり、.js または .ts で終わる必要があります
name空でない文字列、255 文字以下。すべてのインストールステップで一意である必要があります
description空でない文字列、255 文字以下

インストールおよびアンインストールの詳細なリファレンスは、assets/setup-database.ts を参照してください。

ステップ 7 — installation アクションを再生成する

このカスタムインストールステップが、設定内でインストールを必要とする最初のドメインである場合(その他は webhooks、eventing.commerce、eventing.external、adminUi です)、次を再実行します。

npx @adobe/aio-commerce-lib-app init

これにより、ext.config.yaml の app-management パッケージに installation アクションが追加されます。aio app build/aio app deploy は既存のファイルを読み取るだけで、再生成はしません。この手順を省略すると、コードに登録され、DB アクションがデプロイされていても、スクリプトは実行されません。アクションがすでに存在する場合でも、安全に再実行できます。

ステップ 8 — 検証する

aio app build

ビルドに失敗した場合は、問題のある設定フィールドが直接示されます。実際のデータベースに対してアクションを実行するには、デプロイして呼び出します(aio app deploy)。このステップでステップ 7 が必要だった場合は、ext.config.yaml の app-management 配下に actions.installation が追加されていることも確認してください。

ベストプラクティス

  • finally ブロックで必ず接続を閉じてください。接続がリークすると、リソースが枯渇します。
  • リージョンを一致させてください。マニフェストの database.region が唯一の信頼できる情報源です。すべての init() 呼び出しとインストール手順でその値を使用する必要があります(ステップ 1 のリージョンに関する注記を参照)。値が一致しない場合、呼び出し元からは分からないまま接続に失敗します。
  • 頻繁にクエリするフィールドには、プロジェクション(.project({ field: 1 }))とインデックス(createIndex)を使用してください。インデックスのフィールドは合計 2048 バイト以下にする必要があります。
  • toArray() ではなく、カーソルを使って大きな結果セットを反復処理してください(for await (const doc of collection.find(...)))。メモリ使用量を抑えられます。
  • リージョンやシークレットをハードコードしないでください。インライン値ではなく、AIO_DB_REGION と注入された IMS トークンを使用してください。
  • ランタイムアクションでは、@adobe/aio-sdk という包括的なパッケージよりも、最も用途に特化した Adobe I/O ライブラリを優先してください。たとえば、IMS 認証には @adobe/aio-commerce-lib-auth、ロガーには @adobe/aio-lib-core-logging を使用すると、アクションバンドルを小さく保てます。
  • 初回リクエスト時に場当たり的に設定するのではなく、カスタムインストール手順(defineCustomInstallationStep。ステップ 6 を参照)を使って、インストール時にコレクションとインデックスを設定してください。この手順は Commerce Admin からアプリをインストールしたときに一度だけ実行され、アンインストール時には元に戻せます。汎用の App Builder post-app-deploy フックは、アプリを App Management 経由でインストールしない場合に限り、代替手段として使用できます。

よくある問題

  • 有効なトークンがあるのに接続に失敗する:アクションに include-ims-credentials: true が設定されていないか、Adobe Developer Console のプロジェクトに App Builder Data Services API が追加されていません(「前提条件」を参照)。

  • aio app deploy の後に DB がプロビジョニングされない(拡張機能のみのアプリ):aio app CLI プラグインのバグ(aio-lib-db のバグではありません)により、application ランタイムマニフェストにランタイムアクションがない場合、宣言型の自動プロビジョニングがスキップされます。ステップ 1 の拡張機能のみの回避策を適用します。application の下に packages: {} と post-app-build: "mkdir -p dist/application/actions" フックを追加するか、ローカル開発では CLI フォールバックを使って一度プロビジョニングします。

  • リージョンの変更後に接続に失敗する:ライブラリのリージョンがマニフェストの database.region と一致していません。リージョンの移動は破壊的な変更です。aio app db delete を実行し、マニフェストの database.region を更新してから、再プロビジョニングします(aio app deploy、またはローカル開発では CLI フォールバックを使用)。

  • 文字列から _id で検索しても何も返らない:まず bson の new ObjectId(idString) で変換します。生の文字列は、保存された ObjectId とは一致しません。

  • DbError と予期しないエラーの違い:サービスがスローするエラーの name は "DbError" です。これを条件分岐に使い、データベースの障害とアプリケーションのバグを区別します。

  • 一致するものがない場合に findOne が null を返さず、例外をスローする:MongoDB とは異なり、一致するものがない場合は成功した null の結果ではなく、"Document not found" を含むメッセージを持つ DbError がスローされます。すべての DbError(実際の接続障害を含む)がこの名前を持つため、name === "DbError" だけでは判別できません。error.message も確認してください。

    function isDocumentNotFoundError(error: unknown) {
      const e = error as { name?: string; message?: string };
      return e.name === "DbError" && e.message?.includes("Document not found");
    }
    
    let existing;
    try {
      existing = await records.findOne({ order_id: orderId });
    } catch (error) {
      if (!isDocumentNotFoundError(error)) {
        throw error;
      }
    }
    

    詳細なリファレンスについては、assets/db-action.ts を参照してください。

  • インストールステップ内で認証に失敗する:注入された IMS 認証パラメーターは、config(認証情報を保持していません)ではなく、resolveImsAuthParams(context.params) を使って context.params から解決します。アクションに include-ims-credentials: true が注釈として設定されていることを確認してください。手動で設定した AIO_COMMERCE_AUTH_IMS_* 認証情報は非推奨です。

  • インストールステップの読み込みに失敗する(must export a default function or object):スクリプトが CommonJS として作成されています。export default を使った ES モジュールとして作成してください。module.exports(または module.exports.default)は、フレームワークの import * as ローダーを介すると .default.default として扱われ、検証に失敗します。

  • createIndex でエラーが発生する、または効果がない:コレクション名の文字列ではなく、コレクションオブジェクトに対して呼び出す必要があります(client.collection("name").createIndex({ field: 1 }))。まずコレクションを取得してから、そのコレクションに対して createIndex を呼び出してください。

  • カスタムインストールステップを登録してデプロイしたのに、実行されない:インストールが必要な最初のドメインを登録した後(ステップ 7)に init を再実行していません。installation アクションがないため、インストールエンドポイントもありません。

品質基準

  • aio app build がエラーなく完了する
  • ユーザーが作成したすべての DB アクションの注釈で include-ims-credentials: true が宣言されている
  • アクションが finally ブロックでクライアントを閉じ、マニフェストの database ブロックに宣言されたリージョンでライブラリを初期化している
  • このカスタムインストールステップで必要な場合、ext.config.yaml に installation アクションが存在する

連携

  • アクションをイベントに接続する — commerce-app-eventing を呼び出し、イベントの runtimeActions でこのアクションを参照します。
  • アクションを Webhook に接続する — commerce-app-webhooks を呼び出し、runtimeAction でこのアクションを参照します。
  • Admin UI からアクションをトリガーする — commerce-app-admin-ui を呼び出して、このランタイムアクションを呼び出す一括アクション、注文ビューのボタン、またはグリッド列を追加します。

参考資料

  • assets/db-action.ts — 注釈付きのハンドラー全体:初期化/接続、CRUD、カーソルの反復処理、クローズのライフサイクル
  • assets/setup-database.ts — 注釈付きのカスタムインストールステップ全体:インストール時にコレクションと一意のインデックスを作成し、アンインストール時に削除します。context.params の使用方法と、コレクションに対して createIndex を呼び出すパターンも含まれます

コメント