mcpField
C
commerce-app-storage

commerce-app-storage

github.com/adobeデータ・データベース316269 回インストール今日更新
リポジトリ

commerce-app-storage について

既存の Adobe Commerce アプリに App Builder Database Storage を追加するスキルです。開発者向けに、データベースのプロビジョニング、ランタイムアクションの実装、インストール時のセットアップを案内します。

主な機能

  1. 01ワークスペースデータベースの構成
  2. 02Web・イベントアクションのパターン
  3. 03ドキュメントの CRUD 操作
  4. 04インストール時のコレクションとインデックス作成
  5. 05認証とリージョンの設定ガイド

活用シーン

  1. 01Commerce アプリのデータをドキュメントとして保存
  2. 02データベースを使うアクションで HTTP リクエストを処理
  3. 03Commerce イベントや Webhook の処理
  4. 04アプリのインストール時にコレクションとインデックスを準備

インストール

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

$npx skills add https://github.com/adobe/skills --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 トークンを取得できず、実行時に接続に失敗します(インストール中にアクションが実行される場合は、アプリのインストールも失敗します)。このアノテーションを省略しないでください。

# 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 the injected AIO_COMMERCE_AUTH_IMS_* params, 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 がプロビジョニングされない(拡張機能のみのアプリ):application ランタイムマニフェストにランタイムアクションがない場合、aio app CLI プラグインのバグにより、宣言的な自動プロビジョニングがスキップされます(aio-lib-db のバグではありません)。手順 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 を参照してください。

  • インストール手順内で認証に失敗する:認証情報がない config ではなく、注入された AIO_COMMERCE_AUTH_IMS_* 認証情報を持つ context.params から IMS 認証パラメーター(resolveImsAuthParams(context.params))を解決してください。@adobe/aio-lib-core-auth ではなく、@adobe/aio-commerce-lib-auth を使用してください。前者の generateAccessToken は clientId/clientSecret を直接渡すことを想定しており、注入されたパラメーターを使用できません。

  • インストール手順の読み込みに失敗する(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 パターンも含まれています

コメント