Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9a7f23e
CEXT-6160: store and expose Commerce system config during app associa…
vinayrao2000 May 19, 2026
92644b9
CEXT-6160: add association-data spec
vinayrao2000 May 22, 2026
9f2d51e
address review feedback
vinayrao2000 May 27, 2026
204f842
address review feedback
vinayrao2000 May 29, 2026
07a1aae
CEXT-6160: rename to getCommerceInstance/getCommerceClient
vinayrao2000 Jun 3, 2026
9e3fef5
Merge branch 'main' of https://github.com/adobe/aio-commerce-sdk into…
vinayrao2000 Jun 3, 2026
bd84bf8
CEXT-6160: throw AppNotAssociatedError instead of returning null
vinayrao2000 Jun 3, 2026
ccd61de
CEXT-6160: use system.association as storage key
vinayrao2000 Jun 3, 2026
0457e1e
CEXT-6160: split storage into generic + typed layers
vinayrao2000 Jun 3, 2026
42dfccc
CEXT-6160: update spec to reference correct modules
vinayrao2000 Jun 3, 2026
4eed582
CEXT-6160: clarify generic lib-config module and helper layering in spec
vinayrao2000 Jun 3, 2026
434ebe2
CEXT-6160: document that system config bypasses scope tree
vinayrao2000 Jun 3, 2026
2ffbd15
CEXT-6160: drop TTL refresh, document two-layer storage
vinayrao2000 Jun 3, 2026
2b1c005
CEXT-6160: nest system module under configuration/
vinayrao2000 Jun 3, 2026
bb79d73
CEXT-6160: add generic system config submodule to lib-config
vinayrao2000 Jun 4, 2026
dbbb395
CEXT-6160: add association module and public helpers to lib-app
vinayrao2000 Jun 4, 2026
1b1a82e
CEXT-6160: add association runtime action and scaffolding
vinayrao2000 Jun 4, 2026
3a1f2b4
CEXT-6160: add tests, changeset, and docs for association helpers
vinayrao2000 Jun 5, 2026
f80b47e
Merge branch 'main' into CEXT-6160-commerce-system-config-on-association
vinayrao2000 Jun 5, 2026
034f895
resloved conflicts in usage.md
vinayrao2000 Jun 9, 2026
46ed932
CEXT-6160: clarify association adoption docs for new vs existing apps
vinayrao2000 Jun 10, 2026
f54668c
CEXT-6160: resolve merge conflict in lib-app usage.md
vinayrao2000 Jun 10, 2026
6ab289f
CEXT-6160: validate commerceBaseUrl as a URL in association schema
vinayrao2000 Jun 11, 2026
305860a
CEXT-6160: document system-repository helpers and read files directly
vinayrao2000 Jun 11, 2026
fb2dfc4
CEXT-6160: move commerce helpers out of index barrel
vinayrao2000 Jun 12, 2026
80a7526
CEXT-6160: test 500 path when association storage fails
vinayrao2000 Jun 12, 2026
8b61c1e
CEXT-6160: reuse CommerceSdkErrorBaseOptions in AppNotAssociatedError
vinayrao2000 Jun 12, 2026
4847086
CEXT-6160: document 401/403 on association routes
vinayrao2000 Jun 12, 2026
90f2491
CEXT-6160: use response presets and unwrap prose in usage.md
vinayrao2000 Jun 12, 2026
0a4de5c
CEXT-6160: invalidate system-config cache on write failure
vinayrao2000 Jun 12, 2026
b2409d9
CEXT-6160: move runtime helpers to access module
vinayrao2000 Jun 16, 2026
bf31144
CEXT-6160: make getCommerceClient composable, drop unused param
vinayrao2000 Jun 16, 2026
04b6559
CEXT-6160: keep AppNotAssociatedError options as a named seam
vinayrao2000 Jun 16, 2026
de6e29f
CEXT-6160: reuse configuration-repository for system config storage
vinayrao2000 Jun 16, 2026
b290319
CEXT-6160: restore caching-failure comment in configuration-repository
vinayrao2000 Jun 16, 2026
5e948fe
CEXT-6160: read config file directly instead of list-then-read
vinayrao2000 Jun 16, 2026
3f09333
CEXT-6160: bubble up persisted config delete errors
vinayrao2000 Jun 16, 2026
f1130a7
CEXT-6160: reuse shared storage mocks in system-config tests
vinayrao2000 Jun 17, 2026
9b42d0e
CEXT-6160: move SYSTEM_NAMESPACE into system-config and default getSy…
vinayrao2000 Jun 17, 2026
b83a64f
CEXT-6160: add changeset for CommerceSdkErrorBaseOptions export
vinayrao2000 Jun 17, 2026
36fa0c9
CEXT-6160: removed implementation detail from changeset
vinayrao2000 Jun 17, 2026
6d4c3c3
CEXT-6160: split internal rationale out of AppNotAssociatedErrorOptio…
vinayrao2000 Jun 17, 2026
871f7fb
CEXT-6160: move association under management, restrict getCommerceCli…
vinayrao2000 Jun 17, 2026
cb74abd
CEXT-6160: harden getCommerceClient (IMS-only, fetch options) and ret…
vinayrao2000 Jun 17, 2026
8898984
CEXT-6160: address PR review feedback
vinayrao2000 Jun 17, 2026
8ff826d
Merge branch 'main' into CEXT-6160-commerce-system-config-on-association
vinayrao2000 Jun 17, 2026
120de50
CEXT-6160: use CommerceEnv from lib-core for association env
vinayrao2000 Jun 17, 2026
835cb89
CEXT-6160: test association repository via round-trip storage
vinayrao2000 Jun 17, 2026
0cc7dde
CEXT-6160: rename stale index test to access/commerce-instance
vinayrao2000 Jun 17, 2026
dd4c61c
CEXT-6160: align spec with final association design
vinayrao2000 Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/commerce-instance-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@adobe/aio-commerce-lib-app": minor
---

Add helpers to retrieve the Commerce instance an app is associated with from any runtime action: `getCommerceInstance()` returns the stored instance data, and `getCommerceClient(auth)` returns a ready-to-use Commerce HTTP client built from that instance.
5 changes: 5 additions & 0 deletions .changeset/error-base-options-export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@adobe/aio-commerce-lib-core": minor
---

Export the `CommerceSdkErrorBaseOptions` type so consumers can type the options passed when constructing or extending `CommerceSdkErrorBase`.
5 changes: 5 additions & 0 deletions .changeset/system-config-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@adobe/aio-commerce-lib-config": minor
---

Add `getSystemConfigByKey` and `setSystemConfigByKey` to store and retrieve generic SDK system configuration under a dedicated `system.*` namespace.
111 changes: 111 additions & 0 deletions packages/aio-commerce-lib-app/docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2590,6 +2590,117 @@
},
"tags": ["Business Configuration"]
}
},
"/association": {
"post": {
"operationId": "setAssociation",
"summary": "Store Commerce Association",
"description": "Persists the Commerce instance the app is associated with so runtime actions can later retrieve it via the SDK helpers.",
"parameters": [
{
"name": "x-gw-ims-org-id",
"in": "header",
"description": "Adobe IMS organization ID that identifies the organization for the request.",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"commerceBaseUrl": {
"description": "Commerce API base URL of the associated instance.",
"type": "string",
"format": "uri"
},
"commerceEnv": {
"description": "Deployment type of the associated Commerce instance.",
"type": "string",
"enum": ["saas", "paas"]
}
},
"required": ["commerceBaseUrl", "commerceEnv"]
}
}
}
},
"responses": {
"204": {
"description": "Association data stored successfully."
},
"400": {
"description": "Bad request, the request body is invalid.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "You are not authorized to access this resource. Ensure the IMS token and the x-gw-ims-org-id header are correctly set and valid."
},
"403": {
"description": "The access token is valid, but it is not allowed to access the requested organization or operation."
},
"500": {
Comment thread
vinayrao2000 marked this conversation as resolved.
"description": "Internal server error while storing the association data.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"tags": ["Association"]
},
"delete": {
"operationId": "clearAssociation",
"summary": "Clear Commerce Association",
"description": "Removes the stored Commerce instance details. Called when the app is unassociated.",
"parameters": [
{
"name": "x-gw-ims-org-id",
"in": "header",
"description": "Adobe IMS organization ID that identifies the organization for the request.",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Association data cleared successfully."
},
"401": {
"description": "You are not authorized to access this resource. Ensure the IMS token and the x-gw-ims-org-id header are correctly set and valid."
},
"403": {
"description": "The access token is valid, but it is not allowed to access the requested organization or operation."
},
"500": {
"description": "Internal server error while clearing the association data.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"tags": ["Association"]
}
}
},
"components": {
Expand Down
80 changes: 80 additions & 0 deletions packages/aio-commerce-lib-app/docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ The `@adobe/aio-commerce-lib-app` library provides:
- **Business Configuration**: Generate and manage the runtime actions that power the `commerce/configuration/1` extension point.
- **Installation Management**: Generate and manage the runtime action that powers the app installation flow.
- **Admin UI Configuration** (`commerce/backend-ui/2`): Generate and manage the runtime action and `workerProcess` declarations for Admin UI extensions on `commerce/backend-ui/2`. Currently supports grid column extensions, mass actions, order view buttons, and menu declarations.
- **Admin UI SDK Configuration** (`commerce/backend-ui/1`, _deprecated_): Generate and manage the runtime action for the legacy Admin UI SDK extension point. Will be removed from the SDK — use `adminUi` and `commerce/backend-ui/2` for new apps.
- **Association Helpers**: Retrieve the Commerce instance the app is associated with from any runtime action via `getCommerceClient` and `getCommerceInstance`.

## Reference

Expand Down Expand Up @@ -915,6 +917,84 @@ try {
}
```

### Accessing the Associated Commerce Instance from Runtime Actions

After an app is associated with a Commerce instance via App Management, the SDK stores the Commerce base URL and deployment type (`saas` or `paas`) so any runtime action can retrieve them — without custom storage setup or threading parameters through every layer of the call stack.

Two helpers are exposed from the root entrypoint:

- `getCommerceClient(auth, fetchOptions?)` — returns a ready-to-use [`AdobeCommerceHttpClient`](../../aio-commerce-lib-api/docs/usage.md). Use this when you need to call the Commerce API. The base URL and flavor come from the stored association data; you supply the resolved IMS auth. App Management requires IMS, so this accepts only IMS auth: resolve params with `resolveImsAuthParams`, or pass an `ImsAuthProvider` built with `getImsAuthProvider` / `forwardImsAuthProvider` from [`@adobe/aio-commerce-lib-auth`](../../aio-commerce-lib-auth/docs/usage.md). The optional `fetchOptions` are forwarded to the underlying client (e.g. `headers`, `timeout`, `retry`).
- `getCommerceInstance()` — returns the raw `{ baseUrl, env }`. Use this when you only need the metadata (e.g. for logging or building a custom client).

Both helpers throw `AppNotAssociatedError` if the app is not currently associated, was unassociated, or was associated by an older SDK that did not store this data. Re-associating the app resolves the error.

#### Primary pattern — get a ready-to-use client

```ts
import { getCommerceClient } from "@adobe/aio-commerce-lib-app";
import { resolveImsAuthParams } from "@adobe/aio-commerce-lib-auth";

export async function main(params) {
const client = await getCommerceClient(resolveImsAuthParams(params));
const products = await client.get("products").json();
}
```

#### Low-level pattern — get the raw instance data

```ts
import { getCommerceInstance } from "@adobe/aio-commerce-lib-app";

export async function main() {
const instance = await getCommerceInstance();

// instance.baseUrl — Commerce API base URL
// instance.env — "saas" | "paas"
}
```

#### Handling the unassociated state

If your action needs to gracefully handle the case where the app is not associated yet, wrap the call in `try/catch`:

```ts
import { badRequest, ok } from "@adobe/aio-commerce-lib-core/responses";
import {
AppNotAssociatedError,
getCommerceClient,
} from "@adobe/aio-commerce-lib-app";
import { resolveImsAuthParams } from "@adobe/aio-commerce-lib-auth";

export async function main(params) {
try {
const client = await getCommerceClient(resolveImsAuthParams(params));
return ok({ body: await client.get("products").json() });
} catch (error) {
if (error instanceof AppNotAssociatedError) {
return badRequest({
body: { message: "App is not associated with a Commerce instance." },
});
}
throw error;
}
}
```

The data is managed automatically by the SDK during the app association lifecycle: a standalone `association` runtime action (always deployed alongside `app-config`) stores it on association and clears it on unassociation. Apps scaffolded with a version of the SDK that includes this feature have the `association` action wired in from the start — no extra setup beyond your normal deploy.

#### Adopting association in an existing app

Apps scaffolded before this feature was introduced do not have the `association` action yet. After upgrading `@adobe/aio-commerce-lib-app`, regenerate the runtime actions and redeploy so the `/association` endpoint exists:

```bash
npx @adobe/aio-commerce-lib-app generate actions
aio app deploy
```

A plain `aio app deploy` on its own does not add the action: the `pre-app-build` hook only regenerates actions already declared in `ext.config.yaml`. Only `generate actions` (or `generate all`) rebuilds the manifest to pick up newly added SDK actions. Until the app is redeployed with the endpoint, the App Management client skips the store call and the helpers throw `AppNotAssociatedError`.

For an app that was already associated under the older SDK, re-associate it after redeploying so the store call runs and backfills the instance data — a redeploy alone does not populate data for an existing association.

## Best Practices

1. **Use `defineConfig` for type safety** - Get autocompletion and type checking in your IDE
Expand Down
11 changes: 11 additions & 0 deletions packages/aio-commerce-lib-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@
"registry": "https://registry.npmjs.org",
"access": "public",
"exports": {
".": {
"import": {
"types": "./dist/es/index.d.mts",
"default": "./dist/es/index.mjs"
},
"require": {
"types": "./dist/cjs/index.d.cts",
"default": "./dist/cjs/index.cjs"
}
},
"./actions/*": {
"import": {
"types": "./dist/es/actions/*/index.d.mts",
Expand Down Expand Up @@ -69,6 +79,7 @@
}
},
"exports": {
".": "./source/index.ts",
"./actions/*": "./source/actions/*/index.ts",
"./config": "./source/config/index.ts",
"./management": "./source/management/index.ts",
Expand Down
95 changes: 95 additions & 0 deletions packages/aio-commerce-lib-app/source/access/commerce-instance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { AdobeCommerceHttpClient } from "@adobe/aio-commerce-lib-api";

import { AppNotAssociatedError } from "../errors/app-not-associated-error";
import { getAssociationData } from "../management/association/association-repository";

import type { CommerceHttpClientParams } from "@adobe/aio-commerce-lib-api";
import type {
ImsAuthParams,
ImsAuthProvider,
} from "@adobe/aio-commerce-lib-auth";
import type { AssociatedCommerceInstance } from "../management/association/types";

/**
* Returns the Commerce instance this app is currently associated with.
*
* @throws {AppNotAssociatedError} If the app is not associated, was
* unassociated, or was associated by an older SDK that did not store this
* data. Re-associating the app resolves the error.
*
* @example
* ```ts
* import { getCommerceInstance } from "@adobe/aio-commerce-lib-app";
*
* export async function main() {
* const instance = await getCommerceInstance();
*
* // instance.baseUrl — e.g. "https://my-store.example.com"
* // instance.env — "saas" | "paas"
* }
* ```
*/
export async function getCommerceInstance(): Promise<AssociatedCommerceInstance> {
const instance = await getAssociationData();
if (instance === null) {
throw new AppNotAssociatedError();
}
return instance;
}

/**
* Returns an initialised `AdobeCommerceHttpClient` for the Commerce instance
* this app is currently associated with.
*
* The base URL and flavor come from the stored association data
* ({@link getCommerceInstance}); only the auth credentials are supplied by the
* caller, already resolved. App Management requires IMS, so this accepts only
* IMS auth: resolve params with `resolveImsAuthParams`, or pass an
* `ImsAuthProvider` built with `getImsAuthProvider` / `forwardImsAuthProvider`
* from `@adobe/aio-commerce-lib-auth`.
*
* @param auth - Resolved IMS auth params or an IMS auth provider.
* @param fetchOptions - Optional global fetch options forwarded to the
* underlying `AdobeCommerceHttpClient` (e.g. `headers`, `timeout`, `retry`).
* @throws {AppNotAssociatedError} If the app is not associated, was
* unassociated, or was associated by an older SDK that did not store this
* data. Re-associating the app resolves the error.
*
* @example
* ```ts
* import { getCommerceClient } from "@adobe/aio-commerce-lib-app";
* import { resolveImsAuthParams } from "@adobe/aio-commerce-lib-auth";
*
* export async function main(params) {
* const client = await getCommerceClient(resolveImsAuthParams(params));
* const products = await client.get("products").json();
* }
* ```
*/
export async function getCommerceClient(
auth: ImsAuthParams | ImsAuthProvider,
fetchOptions?: CommerceHttpClientParams["fetchOptions"],
): Promise<AdobeCommerceHttpClient> {
const instance = await getCommerceInstance();

// `CommerceHttpClientParams` is a flavor-discriminated union; the stored env
// is a runtime value TypeScript cannot narrow against the auth union, so the
// assembled params are asserted to the resolved shape.
return new AdobeCommerceHttpClient({
auth,
config: { baseUrl: instance.baseUrl, flavor: instance.env },
fetchOptions,
} as CommerceHttpClientParams);
}
29 changes: 29 additions & 0 deletions packages/aio-commerce-lib-app/source/actions/association/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { router } from "./router";

import type { RuntimeActionParams } from "@adobe/aio-commerce-lib-core/params";

/**
* Factory to create the route handler for the `association` action.
*
* The `association` action manages the lifecycle of the Commerce instance the
* app is associated with — `POST /` stores the data when the app is associated,
* and `DELETE /` clears it on unassociation. Runtime actions consume the data
* via `getCommerceInstance` / `getCommerceClient` from the root entrypoint.
*/
export const associationRuntimeAction =
() => async (params: RuntimeActionParams) => {
const handler = router.handler();
return await handler({ ...params });
};
Loading