
Commerce App Storage
- 50 installs
- 13 repo stars
- Updated August 4, 2026
- adobe/aio-commerce-sdk
commerce-app-storage is a Claude Code skill that integrates App Builder Database Storage (@adobe/aio-lib-db) into an Adobe Commerce app and scaffolds a runtime action that reads and writes documents.
About
commerce-app-storage integrates App Builder Database Storage (@adobe/aio-lib-db) into an Adobe Commerce app and scaffolds a runtime action that reads and writes documents. A developer uses it to add persistent, queryable, MongoDB-like storage backing a Commerce app from either a web action or an event/webhook handler. It covers declarative workspace-database provisioning in app.config.yaml and the workaround needed for extension-only apps. It requires a base app initialized with commerce-app-init.
- Integrates App Builder Database Storage into an Adobe Commerce app
- Scaffolds a runtime action that reads and writes documents via @adobe/aio-lib-db
- Covers declarative workspace-database provisioning and its extension-only workaround
Commerce App Storage by the numbers
- 50 all-time installs (skills.sh)
- Ranked #3,241 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
commerce-app-storage capabilities & compatibility
- Capabilities
- commerce storage · database integration · runtime action scaffolding
- Use cases
- database · api development
- Pricing
- Free
What commerce-app-storage says it does
Integrate App Builder Database Storage (@adobe/aio-lib-db) into an Adobe Commerce app and scaffold a runtime action that reads and writes documents.
The library is MongoDB-like: data lives in collections of documents, queried with familiar filters.
npx skills add https://github.com/adobe/aio-commerce-sdk --skill commerce-app-storageAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 50 |
|---|---|
| repo stars | ★ 13 |
| Last updated | August 4, 2026 |
| Repository | adobe/aio-commerce-sdk ↗ |
What it does
Add persistent queryable storage to an Adobe Commerce app and scaffold a runtime action using @adobe/aio-lib-db.
Who is it for?
Adding persistent, queryable, MongoDB-like storage to a Commerce app from a web action or an event/webhook handler.
When should I use this skill?
A user wants persistent, queryable storage backing an Adobe Commerce app.
What you get
A provisioned workspace database plus a runtime action that reads and writes documents via @adobe/aio-lib-db.
- provisioned workspace database
- runtime action that reads and writes documents
By the numbers
- 1:1 workspace-to-database relationship
- 4 database regions (amer, apac, emea, aus)
- requires Node.js 22+
Files
Add Database Storage to a Commerce App
Integrates App Builder Database Storage into an existing Commerce app and scaffolds a runtime action that uses @adobe/aio-lib-db to read and write documents. The library is MongoDB-like: data lives in collections of documents, queried with familiar filters.
The db-access code is identical regardless of action type — what differs is how the action is registered and what its handler returns:
- Web action — HTTP-invokable (
web: "yes"); returns a response built with theresponseshelpers from@adobe/aio-commerce-lib-core. - Event/webhook action — invoked by a Commerce event or webhook; referenced from
app.commerce.config.tsviacommerce-app-eventing(runtimeActions) orcommerce-app-webhooks(runtimeAction).
Prerequisites
- Verify the app is scaffolded and initialized, not merely that the config exists. Require both:
app.commerce.config.tspresent in the project root, and- the project initialized — signalled by the generated
src/commerce-extensibility-1/directory and installednode_modules(the@adobe/aio-commerce-lib-appdependency). - If
app.commerce.config.tsis missing, stop and invokecommerce-app-initfirst (it writes the config, then runs init). - If the config is present but the project is not initialized (no
src/commerce-extensibility-1/ornode_modules), runnpx @adobe/aio-commerce-lib-app initbefore continuing. Init is idempotent — it finds the existing config, skips the interactive prompts, installs dependencies, and generates the project files. - The App Builder Data Services API (API code
AppBuilderDataServicesSDK) must be added to the project in the Adobe Developer Console — in every workspace that uses the database (no special license beyond App Builder). Without it, runtime actions cannot authenticate to the database service.
Step 1 — Provision the workspace database
There is a strict one-to-one relationship between an AIO project workspace and a workspace database. The recommended way to provision it is declaratively in app.config.yaml — the database is provisioned (if not already present) on aio app deploy:
application:
runtimeManifest:
database:
auto-provision: true
region: emea # amer | apac | emea | aus — the single source of truth for the regionExtension-only apps need a workaround. Due to a bug in the aio app CLI plugin (not aio-lib-db), aio app deploy only runs declarative auto-provision when the application runtime manifest has at least one package with a runtime action. Apps built purely with extensions (the recommended layout per the submission guidelines) have no application actions, so deploy silently skips provisioning. Make the application block "real enough" for provisioning to run by adding an empty packages map and a post-app-build hook that creates the directory the provisioning step expects:
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 belowFor local development, declarative auto-provisioning does not run during aio app run / aio app dev. Provision once up front with the CLI fallback (self-service, no special permissions). The aio app db … commands are only available once the storage CLI plugin is installed:
aio plugins install @adobe/aio-cli-plugin-app-storage
aio app db provision --region <amer|apac|emea|aus>Region is a single source of truth. Theregionin the manifestdatabaseblock must match theregionpassed to everyinitDb({ region })call (orAIO_DB_REGION) — in every action and in the install step (Step 6). A mismatch fails the connection. Changing region is destructive:aio app db delete, updatedatabase.regionin the manifest, then re-provision.
Step 2 — Install the library
npm install @adobe/aio-lib-dbStep 3 — Understand intent
Gather from the user:
- Action type: web action or event/webhook action (see the two shapes above).
- Collection name and the operations needed (insert / find / update / delete).
- Region: must match the manifest
database.region(see the region callout in Step 1). Pass it toinit()or setAIO_DB_REGION.
Step 4 — Register the action
Add the action to a user-defined package in src/commerce-extensibility-1/ext.config.yaml (any name except app-management, which is reserved).
`include-ims-credentials: true` is required on every DB action. Without it, aio-lib-db has no IMS token to authenticate with and the connection fails at runtime (and the app installation fails if the action runs during install). Do not omit this annotation.# 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| Field | Constraint |
|---|---|
| Package name | Lowercase alphanumeric + hyphens; never app-management (reserved) |
function | Path relative to src/commerce-extensibility-1/ — not src/... or root-relative |
include-ims-credentials | Must be true — without it init() has no IMS token and the connection fails |
web | "yes" for HTTP-invokable web actions; "no" for event/webhook handlers |
| Collection name | Non-empty string; created on first write if it doesn't exist |
| Region | Must match the manifest database.region (amer \ |
Step 5 — Implement the handler
Every handler follows the same lifecycle: resolve IMS auth → mint token → init → connect → use a collection → always `close` in `finally`.
// 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
}
}For an event/webhook action the only differences are web: "no" in registration and that the payload arrives in params.data:
// Event/webhook handler — same init/connect/close lifecycle
export async function main(params: Record<string, unknown>) {
const data = params.data 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: data.order_id, receivedAt: new Date() });
return ok({ body: { processed: true } });
} finally {
if (client) await client.close();
}
}See assets/db-action.ts for the full annotated reference covering all CRUD operations and cursor iteration.
Step 6 — Set up collections and indexes on install
For an App Management app, create collections and indexes with a custom installation step — a script that runs once when the app is installed from the Commerce Admin, and can be reversed on uninstall. Prefer this over creating them ad-hoc on the first request.
Author the step with defineCustomInstallationStep (an install handler plus an optional uninstall). Inside it, resolve the IMS auth params from `context.params` — _not_ config — then follow the same init → connect → close lifecycle as Step 5, and call createIndex on the collection object:
// ./scripts/setup-database.ts — referenced from config as ./scripts/setup-database.js
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) => {
let client;
try {
const authProvider = getImsAuthProvider(
resolveImsAuthParams(context.params),
);
const token = await authProvider.getAccessToken();
const db = await initDb({ token, region: "emea" });
client = await db.connect();
await client.collection("held_orders").drop();
} finally {
if (client) await client.close();
}
},
});Author the install script as an ES module with `export default` — never `module.exports`. The installation action loads each step viaimport * as step from "<script>"and readsstep.default, so the script must default-export thedefineCustomInstallationStep(...)result. CommonJS breaks this:module.exports.defaultsurfaces asstep.default.defaultand validation fails. Thescriptpath must end in.js; if you author in TypeScript, compile it and keep the emitted.jsan ES module.
Register the step in app.commerce.config.ts under installation.customInstallationSteps. The script path points at the compiled .js output:
// app.commerce.config.ts
installation: {
customInstallationSteps: [
{
script: "./scripts/setup-database.js", // compiled output of setup-database.ts
name: "Set up held-orders collection",
description: "Creates the held_orders collection and a unique index on order_id",
},
],
},| Field | Constraint |
|---|---|
script | Path relative to the project root; must be an ES module (export default) ending in .js (compile from TS if you author it that way) |
name | Non-empty string, ≤ 255 characters; unique across all installation steps |
description | Non-empty string, ≤ 255 characters |
See assets/setup-database.ts for the full annotated install/uninstall reference.
Step 7 — Validate
aio app buildA build failure points directly to the offending config field. To exercise the action against the real database, deploy and invoke it (aio app deploy).
Best practices
- Always close connections in a
finallyblock — leaked connections exhaust resources. - Match the region — the manifest
database.regionis the single source of truth; everyinit()call and the install step must use it (see the region callout in Step 1). A mismatch fails the connection silently from the caller's view. - Use projections (
.project({ field: 1 })) and indexes (createIndex) for frequently queried fields; index fields must total ≤ 2048 bytes. - Iterate large result sets with cursors (
for await (const doc of collection.find(...))) instead oftoArray()to bound memory. - Don't hardcode the region or secrets — prefer
AIO_DB_REGIONand the injected IMS token over inline values. - Prefer the most specific Adobe I/O library in runtime actions over the
@adobe/aio-sdkumbrella — e.g.@adobe/aio-commerce-lib-authfor IMS auth and@adobe/aio-lib-core-loggingfor the logger — to keep action bundles small. - Set up collections and indexes during installation with a custom installation step (
defineCustomInstallationStep, see Step 6) rather than ad-hoc on the first request — it runs once when the app is installed from the Commerce Admin and is reversible on uninstall. A generic App Builderpost-app-deployhook is only an alternative when the app is not installed through App Management.
Common Issues
- Connection fails despite a valid token: the action is missing
include-ims-credentials: true, or the App Builder Data Services API has not been added to the project in the Adobe Developer Console (see Prerequisites). - DB not provisioned after `aio app deploy` (extension-only app): a bug in the
aio appCLI plugin (notaio-lib-db) skips declarative auto-provision when theapplicationruntime manifest has no runtime action. Apply the extension-only workaround from Step 1 — addpackages: {}and apost-app-build: "mkdir -p dist/application/actions"hook underapplication— or provision once with the CLI fallback for local dev. - Connection fails after a region change: the library region doesn't match the manifest
database.region. Moving regions is destructive —aio app db delete, updatedatabase.regionin the manifest, then re-provision (aio app deploy, or the CLI fallback for local dev). - Querying by `_id` from a string returns nothing: convert it first —
new ObjectId(idString)frombson. A raw string never matches the storedObjectId. - `DbError` vs unexpected error: errors thrown by the service have
name === "DbError"; branch on it to separate database failures from application bugs. - Auth fails inside an installation step: resolve the IMS auth params from
context.params(resolveImsAuthParams(context.params)) — which carries the injectedAIO_COMMERCE_AUTH_IMS_*credentials — not fromconfig, which holds no credentials. Use@adobe/aio-commerce-lib-auth, not@adobe/aio-lib-core-auth: the latter'sgenerateAccessTokenexpectsclientId/clientSecretdirectly and cannot consume the injected params. - Installation step fails to load (`must export a default function or object`): the script was authored as CommonJS. Author it as an ES module with
export default;module.exports(ormodule.exports.default) surfaces through the framework'simport * asloader as.default.defaultand fails validation. - `createIndex` errors or has no effect: it must be called on a collection object (
client.collection("name").createIndex({ field: 1 })), not with a collection-name string. Get the collection first, then callcreateIndexon it.
Quality Bar
aio app buildcompletes without errors- Every user-authored DB action declares
include-ims-credentials: truein its annotations - The action closes the client in a
finallyblock and initializes the library in the region declared in the manifestdatabaseblock
Chaining
- Wire the action to an event — invoke
commerce-app-eventingand reference this action in an event'sruntimeActions. - Wire the action to a webhook — invoke
commerce-app-webhooksand reference this action viaruntimeAction.
References
- assets/db-action.ts — Full annotated handler: init/connect, CRUD, cursor iteration, and the close lifecycle
- assets/setup-database.ts — Full annotated custom installation step: install creates a collection and a unique index, uninstall drops it, with the
context.paramsandcreateIndex-on-collection patterns
// Full annotated reference for a Commerce app runtime action backed by
// App Builder Database Storage (@adobe/aio-lib-db).
//
// Lifecycle (identical for web actions and event/webhook handlers):
// resolveImsAuthParams -> getAccessToken -> init -> connect -> use collection -> ALWAYS close.
//
// Registration requirements (in src/commerce-extensibility-1/ext.config.yaml):
// - include-ims-credentials: true (REQUIRED — provides the IMS token below)
// - web: "yes" for an HTTP-invokable web action; "no" for an event/webhook handler
// - the "App Builder Data Services" API must be added to the project in the
// Adobe Developer Console (every workspace that uses the database)
// - the workspace database must be provisioned: declaratively via the
// app.config.yaml database block on `aio app deploy` (CLI `aio app db
// provision --region <r>` is the local-dev fallback)
import {
getImsAuthProvider,
resolveImsAuthParams,
} from "@adobe/aio-commerce-lib-auth";
import { buildErrorResponse, ok } from "@adobe/aio-commerce-lib-core/responses";
import AioLogger from "@adobe/aio-lib-core-logging";
import { init as initDb } from "@adobe/aio-lib-db";
// The connected client returned by db.connect().
type DbClient = Awaited<
ReturnType<Awaited<ReturnType<typeof initDb>>["connect"]>
>;
export async function main(params: Record<string, unknown>) {
const logger = AioLogger("commerce-app-storage", {
level: (params.LOG_LEVEL as string) || "info",
});
// Web actions receive input directly on params; event/webhook handlers
// receive the payload on params.data instead:
// const data = params.data as Record<string, unknown>;
let client: DbClient | undefined;
try {
// 1. Resolve the AIO_COMMERCE_AUTH_IMS_* params injected because
// include-ims-credentials is true, then mint a raw access token string.
const authProvider = getImsAuthProvider(resolveImsAuthParams(params));
const token = await authProvider.getAccessToken();
// 2. Initialize. region MUST match the manifest database.region.
// Omit it to use AIO_DB_REGION or the "amer" default.
const db = await initDb({
token,
region: (params.DB_REGION as string) || "amer", // "amer" | "apac" | "emea" | "aus"
});
// 3. Connect — opens a session that must be closed (see finally).
client = await db.connect();
// 4. Select a collection (created on first write if absent).
const records = client.collection("records");
// --- CRUD reference ---------------------------------------------------
// Insert one
const inserted = await records.insertOne({
name: "Jane Smith",
createdAt: new Date().toISOString(),
});
// Insert many
await records.insertMany([{ name: "Alice" }, { name: "Bob" }]);
// Find one
const one = await records.findOne({ name: "Jane Smith" });
// Find many — returns a cursor. Iterate to bound memory.
for await (const doc of records
.find({ active: true })
.project({ name: 1, _id: 0 })) {
logger.info("record", doc);
}
// ...or load all at once (only for small result sets):
// const all = await records.find({}).toArray();
// Update one (use $set / other operators)
await records.updateOne({ name: "Jane Smith" }, { $set: { active: true } });
// Update many
await records.updateMany(
{ active: { $exists: false } },
{ $set: { active: false } },
);
// Find and update, returning the updated document
await records.findOneAndUpdate(
{ name: "Jane Smith" },
{ $set: { lastSeen: new Date() } },
{ returnDocument: "after" },
);
// Delete one / many
await records.deleteOne({ name: "Bob" });
await records.deleteMany({ active: false });
// Look up a document by its _id supplied as a string:
// import { ObjectId } from "bson";
// await records.findOne({ _id: new ObjectId(idString) });
return ok({ body: { inserted, one } });
} catch (error) {
// Database errors surface with name === "DbError".
const dbError = error as {
name?: string;
message?: string;
statusCode?: number;
};
if (dbError.name === "DbError") {
logger.error("Database error", dbError.message);
} else {
logger.error("Unexpected error", error);
}
return buildErrorResponse(dbError.statusCode ?? 500, {
body: { message: dbError.message ?? "Unexpected error" },
});
} finally {
// 5. ALWAYS close — leaked connections exhaust resources.
if (client) {
await client
.close()
.catch((e: Error) =>
logger.warn("Failed to close DB client", e.message),
);
}
}
}
// Full annotated custom installation step for a Commerce App Management app,
// backed by App Builder Database Storage (@adobe/aio-lib-db).
//
// What it does:
// - install: creates the "held_orders" collection and a UNIQUE index on order_id
// - uninstall: drops the collection to reverse the install
//
// Why a custom installation step (vs. ad-hoc setup on first request):
// It runs exactly once when the app is installed from the Commerce Admin,
// and the uninstall handler lets the app clean up after itself.
//
// Author the install script as an ES module with `export default` — never
// `module.exports`. The installation action loads each step via
// `import * as step from "<script>"` and reads `step.default`, so the script
// must default-export the defineCustomInstallationStep(...) result. The `script`
// path must end in .js; if you author in TypeScript, compile it and keep the
// emitted .js an ES module.
//
// Wiring (in app.commerce.config.ts) — the script path is the COMPILED .js:
// installation: {
// customInstallationSteps: [
// {
// script: "./scripts/setup-database.js",
// name: "Set up held-orders collection",
// description: "Creates the held_orders collection and a unique index on order_id",
// },
// ],
// }
//
// Two easy mistakes this file avoids:
// 1. Resolve the IMS auth params from context.params — NOT config. config is the
// app configuration and carries no credentials; context.params carries the
// injected IMS credentials (AIO_COMMERCE_AUTH_IMS_*) read by
// @adobe/aio-commerce-lib-auth.
// 2. createIndex is called ON A COLLECTION OBJECT, not with a collection-name string.
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";
const COLLECTION = "held_orders";
// Open a DB client using the credentials on context.params. The caller is
// responsible for closing it (see the finally blocks below).
async function openClient(context: { params: Record<string, unknown> }) {
// include-ims-credentials makes the AIO_COMMERCE_AUTH_IMS_* credentials available
// on context.params; resolve them and mint a raw access token string.
const authProvider = getImsAuthProvider(resolveImsAuthParams(context.params));
const token = await authProvider.getAccessToken();
const db = await initDb({
token,
// region MUST match the manifest database.region. Omit to use AIO_DB_REGION.
region: (context.params.DB_REGION as string) || "amer", // "amer" | "apac" | "emea" | "aus"
});
return db.connect();
}
export default defineCustomInstallationStep({
install: async (config, context) => {
const { logger } = context;
logger.info(`Setting up storage for ${config.metadata.displayName}...`);
let client: Awaited<ReturnType<typeof openClient>> | undefined;
try {
client = await openClient(context);
// Get the collection OBJECT first (created on first write if absent),
// then create the index on it — never pass a collection-name string.
const orders = client.collection(COLLECTION);
await orders.createIndex({ order_id: 1 }, { unique: true });
logger.info(`Created "${COLLECTION}" with a unique index on order_id`);
return { status: "success", collection: COLLECTION };
} catch (error) {
if (error instanceof Error && error.name === "DbError") {
logger.error("Database error during install", error.message);
}
throw error; // re-throw so the installation step fails loudly
} finally {
if (client) {
await client
.close()
.catch((e: Error) =>
logger.warn("Failed to close DB client", e.message),
);
}
}
},
uninstall: async (config, context) => {
const { logger } = context;
logger.info(`Removing storage for ${config.metadata.displayName}...`);
let client: Awaited<ReturnType<typeof openClient>> | undefined;
try {
client = await openClient(context);
await client.collection(COLLECTION).drop();
logger.info(`Dropped "${COLLECTION}"`);
} finally {
if (client) {
await client
.close()
.catch((e: Error) =>
logger.warn("Failed to close DB client", e.message),
);
}
}
},
});
{
"skill_name": "commerce-app-storage",
"evals": [
{
"id": 1,
"prompt": "I want to persist customer feedback in my Commerce app. Add a web runtime action that stores a feedback document in App Builder Database Storage and returns the result.",
"expected_output": "The agent installs @adobe/aio-lib-db, registers a web action (web: \"yes\") under a user package (not app-management) in src/commerce-extensibility-1/ext.config.yaml with include-ims-credentials: true, and creates a handler that resolves the injected IMS auth params with @adobe/aio-commerce-lib-auth, mints a raw access token string, calls init then connect, inserts into a collection, and closes the client in a finally block. It notes the workspace database must be provisioned (declaratively via the app.config.yaml database block on deploy, with the aio app db provision CLI as a local-dev fallback) and that the App Builder Data Services API must be added to the project in the Adobe Developer Console.",
"assertions": [
"The action is registered with include-ims-credentials: true",
"The action package name is not 'app-management'",
"The handler calls init() then connect() and obtains a collection",
"The handler closes the client in a finally block",
"An IMS access token is obtained via getImsAuthProvider(resolveImsAuthParams(params)).getAccessToken() from @adobe/aio-commerce-lib-auth and passed to init() as a raw string (not generateAccessToken, not token.access_token)",
"The action is registered with web: \"yes\"",
"Provisioning (declarative auto-provision in app.config.yaml as primary, aio app db provision CLI as local-dev fallback) and the App Builder Data Services API are mentioned"
]
},
{
"id": 2,
"prompt": "When an order is placed in Commerce, I want to record it in our database. The order event handler already needs to be created. Scaffold the event action and have it write the order to a collection.",
"expected_output": "The agent scaffolds an event/webhook action (web: \"no\") registered with include-ims-credentials: true, reading the payload from params.data, and writing to a collection using the init/connect/close lifecycle. After build passes it suggests commerce-app-eventing to wire the action to the order event via runtimeActions.",
"assertions": [
"The action is registered with include-ims-credentials: true",
"The handler reads the event payload from params.data",
"The handler uses init() -> connect() -> collection write -> close() in finally",
"The action is registered with web: \"no\"",
"Agent mentions commerce-app-eventing (or runtimeActions) to wire the action to the event"
]
},
{
"id": 3,
"prompt": "Our workspace database is in emea. Add a runtime action that looks up a product by SKU from a 'products' collection, and make sure queries are efficient.",
"expected_output": "The agent creates a handler that initializes the library with region 'emea' (matching provisioning), connects, queries the products collection by sku, and closes the client. It promotes best practices: creating an index on the queried field, using projections, and iterating cursors for large result sets.",
"assertions": [
"init() is called with region: \"emea\"",
"The handler queries the products collection filtering by sku",
"The client is closed in a finally block",
"The agent recommends an index on the queried field and/or projections for efficiency",
"aio app build completes without errors"
]
},
{
"id": 4,
"prompt": "When my Commerce app installs, create a 'held_orders' collection with a unique index on order_id so duplicate holds are rejected. Set it up as part of the app installation, not on first request.",
"expected_output": "The agent authors a custom installation step with defineCustomInstallationStep (install handler, ideally with an uninstall that drops the collection), exported as an ES module default export (export default, not module.exports), resolves the IMS auth params from context.params via @adobe/aio-commerce-lib-auth and mints a raw access token string, connects, gets the collection object and calls createIndex({ order_id: 1 }, { unique: true }) on it, and closes the client in a finally block. It wires the script into app.commerce.config.ts under installation.customInstallationSteps with a .js script path, a unique name, and a description.",
"assertions": [
"The step is defined with defineCustomInstallationStep from @adobe/aio-commerce-lib-app/management",
"The IMS token is obtained via getImsAuthProvider(resolveImsAuthParams(context.params)).getAccessToken() from @adobe/aio-commerce-lib-auth (from context.params, not config; not generateAccessToken)",
"createIndex is called on a collection object with { order_id: 1 } and { unique: true }",
"The client is closed in a finally block",
"The step is registered under installation.customInstallationSteps in app.commerce.config.ts with a .js script path, a name, and a description",
"The install script is authored as an ES module using export default (not module.exports / CommonJS)"
]
},
{
"id": 5,
"prompt": "My Commerce app is extension-only (no application runtime actions) and uses App Builder Database Storage. After aio app deploy the workspace database isn't provisioned. Configure it so the database is auto-provisioned on deploy.",
"expected_output": "The agent explains that declarative auto-provision is skipped for extension-only apps because the application runtime manifest has no runtime action, and applies the workaround in app.config.yaml: an empty packages map (packages: {}) under application.runtimeManifest plus a post-app-build hook running mkdir -p dist/application/actions, alongside the database auto-provision block with a region. It may also mention the aio app db provision CLI as a local-dev fallback.",
"assertions": [
"Identifies that auto-provision is skipped because the app has no application runtime action",
"Adds packages: {} under application.runtimeManifest",
"Adds a post-app-build hook running mkdir -p dist/application/actions",
"Keeps the database auto-provision block with a region",
"References the aio app db provision CLI as a local-dev fallback"
]
},
{
"id": 6,
"prompt": "Add a web action that stores records in App Builder Database Storage. My repo has an app.commerce.config.ts at the root, but I haven't initialized the project yet — there's no src/commerce-extensibility-1/ directory and no node_modules.",
"expected_output": "The agent recognizes that although the config exists, the project is not initialized (no src/commerce-extensibility-1/ or node_modules), so it runs npx @adobe/aio-commerce-lib-app init before proceeding — without overwriting the existing config. Only after the project is initialized does it install @adobe/aio-lib-db and scaffold the storage web action.",
"assertions": [
"The agent detects the project is not initialized despite the config being present",
"The agent runs npx @adobe/aio-commerce-lib-app init before doing storage work",
"The existing app.commerce.config.ts is not overwritten",
"Storage work (installing @adobe/aio-lib-db, scaffolding the action) proceeds only after initialization"
]
}
]
}
Related skills
FAQ
What storage library is used?
@adobe/aio-lib-db, which is MongoDB-like: data lives in collections of documents queried with familiar filters.
How is the database provisioned?
Declaratively in app.config.yaml with auto-provision on aio app deploy; extension-only apps need a workaround because deploy skips provisioning without an application package with a runtime action.