@adobe-commerce/aio-toolkit 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -0
- package/README.md +169 -0
- package/dist/aio-toolkit-cli-workflow/bin/cli.js +2048 -0
- package/dist/aio-toolkit-cli-workflow/bin/cli.js.map +1 -0
- package/dist/aio-toolkit-cursor-context/bin/cli.js +16 -0
- package/dist/aio-toolkit-cursor-context/bin/cli.js.map +1 -1
- package/dist/index.d.mts +51 -6
- package/dist/index.d.ts +51 -6
- package/dist/index.js +209 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +213 -0
- package/dist/index.mjs.map +1 -1
- package/files/cursor-context/commands/aio-toolkit-analyze-adobe-commerce-module.md +612 -0
- package/files/cursor-context/commands/aio-toolkit-create-amazon-sqs-consumer.md +445 -0
- package/files/cursor-context/commands/aio-toolkit-create-event-consumer-action.md +6 -0
- package/files/cursor-context/commands/aio-toolkit-create-graphql-action.md +21 -7
- package/files/cursor-context/commands/aio-toolkit-create-openwhisk-action.md +326 -0
- package/files/cursor-context/commands/aio-toolkit-create-runtime-action.md +15 -5
- package/files/cursor-context/commands/aio-toolkit-create-shipping-carrier.md +681 -0
- package/files/cursor-context/commands/aio-toolkit-create-webhook-action.md +22 -9
- package/files/cursor-context/rules/aio-toolkit-create-adobe-commerce-client.mdc +252 -116
- package/files/cursor-context/rules/aio-toolkit-oop-best-practices.mdc +10 -4
- package/files/cursor-context/rules/aio-toolkit-setup-new-relic-telemetry.mdc +167 -2
- package/files/cursor-context/rules/aio-toolkit-use-abdb-collection.mdc +610 -0
- package/files/cursor-context/rules/aio-toolkit-use-abdb-repository.mdc +705 -0
- package/files/cursor-context/rules/aio-toolkit-use-adobe-auth.mdc +442 -0
- package/files/cursor-context/rules/aio-toolkit-use-amazon-sqs-publish.mdc +397 -0
- package/files/cursor-context/rules/aio-toolkit-use-file-repository.mdc +502 -0
- package/files/cursor-context/rules/aio-toolkit-use-publish-event.mdc +510 -0
- package/files/cursor-context/rules/aio-toolkit-use-runtime-api-gateway-service.mdc +542 -0
- package/package.json +4 -2
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Adding RuntimeApiGatewayService to Adobe I/O Runtime actions using @adobe-commerce/aio-toolkit
|
|
3
|
+
globs: '**/{actions,lib}/**/*.{ts,js}'
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Using RuntimeApiGatewayService
|
|
8
|
+
|
|
9
|
+
## Trigger Conditions
|
|
10
|
+
|
|
11
|
+
This rule applies when the user requests to call a web-exposed Adobe I/O Runtime action from within another action using any of these phrases:
|
|
12
|
+
|
|
13
|
+
- "Call a web action from another action"
|
|
14
|
+
- "Invoke action via API Gateway"
|
|
15
|
+
- "HTTP call to a Runtime action"
|
|
16
|
+
- "Use RuntimeApiGatewayService"
|
|
17
|
+
- "Call another action over HTTP"
|
|
18
|
+
- "Orchestrate actions via API Gateway"
|
|
19
|
+
- "Call [action-name] from [action-name]"
|
|
20
|
+
- "Add API Gateway client to [action-name]"
|
|
21
|
+
- "Invoke a web action with IMS auth"
|
|
22
|
+
- "Call Runtime endpoint from my action"
|
|
23
|
+
|
|
24
|
+
**Important**: This rule does NOT create new actions. It integrates `RuntimeApiGatewayService` into existing actions only.
|
|
25
|
+
|
|
26
|
+
**Integration with Other Action Creation Rules:**
|
|
27
|
+
|
|
28
|
+
When using action creation rules (RuntimeAction, WebhookAction, EventConsumerAction, GraphQlAction, OpenwhiskAction) and the developer mentions calling another web action or API Gateway endpoint in the business logic:
|
|
29
|
+
|
|
30
|
+
- **Automatically trigger this rule** after the action is created
|
|
31
|
+
- Examples: "call the product-service action", "invoke another web action", "call action via API Gateway", "HTTP request to runtime endpoint"
|
|
32
|
+
- This rule handles service construction, token generation, response handling, and config wiring
|
|
33
|
+
|
|
34
|
+
**RuntimeApiGatewayService vs the Openwhisk client:**
|
|
35
|
+
|
|
36
|
+
Both call one action from another — choose based on what the target action looks like:
|
|
37
|
+
|
|
38
|
+
| | `Openwhisk` client | `RuntimeApiGatewayService` |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| Protocol | OpenWhisk API (internal) | HTTP via API Gateway |
|
|
41
|
+
| Target must be `web: 'yes'` | No | Yes |
|
|
42
|
+
| Auth | `API_HOST` + `API_AUTH` (auto-injected) | IMS Bearer token (must be generated) |
|
|
43
|
+
| Returns | Full activation result | Raw `node-fetch` Response |
|
|
44
|
+
| Blocking / non-blocking | Configurable | Always async HTTP |
|
|
45
|
+
| Token management | None needed | Must generate + optionally cache |
|
|
46
|
+
| Best for | Internal sub-actions, fan-out pipelines | Web actions, HTTP-level response control |
|
|
47
|
+
|
|
48
|
+
**Use `RuntimeApiGatewayService` when:**
|
|
49
|
+
- The target action is `web: 'yes'`
|
|
50
|
+
- You need full HTTP response control (`response.ok`, headers, status codes)
|
|
51
|
+
- The target is in a different namespace
|
|
52
|
+
- You need IMS-authenticated HTTP calls to public endpoints
|
|
53
|
+
|
|
54
|
+
**Use `Openwhisk` client when:**
|
|
55
|
+
- The target action is `web: 'no'` (internal)
|
|
56
|
+
- You need blocking/non-blocking control
|
|
57
|
+
- You're routing within the same namespace
|
|
58
|
+
- Simplicity matters more than HTTP-level control
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Step 1: Verify Prerequisites
|
|
63
|
+
|
|
64
|
+
Before proceeding, verify:
|
|
65
|
+
|
|
66
|
+
1. **Toolkit installed**: Check `@adobe-commerce/aio-toolkit` in `package.json`
|
|
67
|
+
- If NOT installed: `npm install @adobe-commerce/aio-toolkit`
|
|
68
|
+
|
|
69
|
+
2. **Detect Language (TypeScript vs JavaScript)**:
|
|
70
|
+
|
|
71
|
+
**Check for TypeScript indicators (check ALL of these)**:
|
|
72
|
+
1. **Package Dependencies**: Look for `typescript` in `package.json` dependencies or devDependencies
|
|
73
|
+
2. **Config File**: Check if `tsconfig.json` exists in project root
|
|
74
|
+
3. **TypeScript Loaders**: Look for `ts-loader`, `ts-node`, or `@types/*` packages
|
|
75
|
+
4. **Existing Files**: Check for `.ts` files in `actions/` or `lib/`
|
|
76
|
+
|
|
77
|
+
**Detection Logic**:
|
|
78
|
+
- **TypeScript** if: `typescript` + `tsconfig.json` exist, OR 2+ indicators found, OR `.ts` files in `actions/`/`lib/`
|
|
79
|
+
- **Otherwise**: JavaScript
|
|
80
|
+
|
|
81
|
+
3. **Detect Project Structure**:
|
|
82
|
+
- Check `app.config.yaml` for `application:` (root actions) or `extensions:` (extension points)
|
|
83
|
+
|
|
84
|
+
4. **Check Existing Actions**: List all actions from `actions/` and `[extension-path]/actions/`
|
|
85
|
+
- If NO actions exist: stop and recommend creating an action first
|
|
86
|
+
|
|
87
|
+
5. Only proceed to Step 2 after confirming all prerequisites
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Step 2: Ask Required Questions
|
|
92
|
+
|
|
93
|
+
Before generating any code, ask the user:
|
|
94
|
+
|
|
95
|
+
**Important**: Do not make assumptions — clarify all details before writing code.
|
|
96
|
+
|
|
97
|
+
1. **Target Action**: Which existing action should receive the `RuntimeApiGatewayService` integration?
|
|
98
|
+
- List discovered actions for the user to choose from
|
|
99
|
+
|
|
100
|
+
2. **Endpoint(s) to Call**: What API Gateway endpoint(s) will this action call?
|
|
101
|
+
- Format: `v1/web/{package}/{action-name}` (e.g. `v1/web/my-package/process-order`)
|
|
102
|
+
- Note: The namespace is handled automatically — only provide the path after it
|
|
103
|
+
|
|
104
|
+
3. **HTTP Method(s)**: Which HTTP methods are needed?
|
|
105
|
+
- GET (read data, no payload)
|
|
106
|
+
- POST (send payload, create/trigger)
|
|
107
|
+
- PUT (update with payload)
|
|
108
|
+
- DELETE (remove by path)
|
|
109
|
+
- Multiple methods if the action calls different endpoints differently
|
|
110
|
+
|
|
111
|
+
4. **Payload Structure** (for POST / PUT only):
|
|
112
|
+
- What data is sent in the request body?
|
|
113
|
+
- Example: `{ orderId, customerId, action }`
|
|
114
|
+
|
|
115
|
+
5. **Token Caching Strategy**: How should the IMS token be managed?
|
|
116
|
+
- **Simple** (default, recommended for low-volume actions): generate a fresh token on every invocation using `AdobeAuth.getToken()`
|
|
117
|
+
- **Cached** (recommended for high-frequency actions): check token validity with `BearerToken.info()` before generating; cache with a TTL in-memory or via an external store
|
|
118
|
+
|
|
119
|
+
6. **Additional Headers** (optional): Does any call need custom headers beyond the defaults?
|
|
120
|
+
- Defaults: `Authorization`, `x-gw-ims-org-id`, `Content-Type: application/json`
|
|
121
|
+
- Examples: `x-request-id`, `x-correlation-id`, `x-custom-header`
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Step 3: Confirm Implementation Details
|
|
126
|
+
|
|
127
|
+
After receiving answers to ALL questions, present a comprehensive summary and **wait for user confirmation** before proceeding to Step 4:
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 📋 RuntimeApiGatewayService Integration Summary
|
|
132
|
+
|
|
133
|
+
**Target Action:** `[action-name]`
|
|
134
|
+
**Action Path:** `[path to action file]`
|
|
135
|
+
**Language:** [TypeScript/JavaScript] (auto-detected)
|
|
136
|
+
|
|
137
|
+
**API Gateway Calls:**
|
|
138
|
+
|
|
139
|
+
| Method | Endpoint | Payload |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| [GET/POST/PUT/DELETE] | `v1/web/[package]/[action]` | `{ [fields] }` or — |
|
|
142
|
+
|
|
143
|
+
**Token Strategy:** [Simple (fresh per invocation) / Cached with BearerToken.info()]
|
|
144
|
+
|
|
145
|
+
**URL Pattern:** `https://adobeioruntime.net/apis/{namespace}/v1/web/[package]/[action]`
|
|
146
|
+
|
|
147
|
+
**Environment Variables:**
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# RuntimeApiGatewayService — IMS credentials
|
|
151
|
+
NAMESPACE=$AIO_runtime_namespace # auto-available in App Builder
|
|
152
|
+
IMS_ORG_ID=
|
|
153
|
+
CLIENT_ID=
|
|
154
|
+
CLIENT_SECRET=
|
|
155
|
+
TECHNICAL_ACCOUNT_ID=
|
|
156
|
+
TECHNICAL_ACCOUNT_EMAIL=
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
📍 **Source:** Adobe Developer Console → Your project → Credentials
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### ✅ What Will Be Changed
|
|
164
|
+
|
|
165
|
+
**1. Action Implementation**
|
|
166
|
+
- ✏️ `[action-path]/index.[js/ts]`
|
|
167
|
+
- Import `RuntimeApiGatewayService`, `AdobeAuth` [, `BearerToken` if caching]
|
|
168
|
+
- Generate IMS token via `AdobeAuth.getToken()`
|
|
169
|
+
- Construct `new RuntimeApiGatewayService(params.NAMESPACE, params.IMS_ORG_ID, token, logger)`
|
|
170
|
+
- Call `service.[method](endpoint [, payload])` for each configured call
|
|
171
|
+
- Check `response.ok` and parse with `response.json()` / `response.text()`
|
|
172
|
+
|
|
173
|
+
**2. Action Configuration**
|
|
174
|
+
- ✏️ `app.config.yaml`, `ext.config.yaml`, or `actions.config.yaml`
|
|
175
|
+
- Add `NAMESPACE: $AIO_runtime_namespace`
|
|
176
|
+
- Add IMS credential inputs: `IMS_ORG_ID`, `CLIENT_ID`, `CLIENT_SECRET`, `TECHNICAL_ACCOUNT_ID`, `TECHNICAL_ACCOUNT_EMAIL`
|
|
177
|
+
|
|
178
|
+
**3. Environment Variables**
|
|
179
|
+
- 📄/✏️ `.env` (project root) — add placeholder variables if not already present
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
**Should I proceed with this implementation?**
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Step 4: Generate Implementation
|
|
188
|
+
|
|
189
|
+
### Step 4.1: Update the Action Code
|
|
190
|
+
|
|
191
|
+
#### Simple Pattern (fresh token per invocation)
|
|
192
|
+
|
|
193
|
+
**JavaScript:**
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
const {
|
|
197
|
+
RuntimeApiGatewayService,
|
|
198
|
+
AdobeAuth,
|
|
199
|
+
} = require('@adobe-commerce/aio-toolkit');
|
|
200
|
+
|
|
201
|
+
// Inside your action handler (params, ctx):
|
|
202
|
+
const { logger } = ctx;
|
|
203
|
+
|
|
204
|
+
// Step 1: Generate IMS access token
|
|
205
|
+
const token = await AdobeAuth.getToken(
|
|
206
|
+
params.CLIENT_ID,
|
|
207
|
+
params.CLIENT_SECRET,
|
|
208
|
+
params.TECHNICAL_ACCOUNT_ID,
|
|
209
|
+
params.TECHNICAL_ACCOUNT_EMAIL,
|
|
210
|
+
params.IMS_ORG_ID,
|
|
211
|
+
['openid', 'AdobeID', 'adobeio_api']
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Step 2: Construct the service
|
|
215
|
+
const service = new RuntimeApiGatewayService(
|
|
216
|
+
params.NAMESPACE,
|
|
217
|
+
params.IMS_ORG_ID,
|
|
218
|
+
token,
|
|
219
|
+
logger // optional: enables request/error logging
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Step 3: Make the call — method throws on network failure, so wrap in try/catch
|
|
223
|
+
const response = await service.get('v1/web/[package]/[action]');
|
|
224
|
+
|
|
225
|
+
// Step 4: Check HTTP result — response body is NOT auto-parsed
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
const errorBody = await response.text();
|
|
228
|
+
logger.error({ message: '[action-name]-call-failed', status: response.status, errorBody });
|
|
229
|
+
return RuntimeActionResponse.error(response.status, `Downstream call failed: ${response.status}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const result = await response.json();
|
|
233
|
+
logger.info({ message: '[action-name]-call-success' });
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
#### Simple Pattern — POST with payload
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
const response = await service.post(
|
|
240
|
+
'v1/web/[package]/[action]',
|
|
241
|
+
{ /* payload fields */ }
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
const errorBody = await response.text();
|
|
246
|
+
throw new Error(`POST failed ${response.status}: ${errorBody}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const result = await response.json();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### Cached Token Pattern (recommended for high-frequency actions)
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
const {
|
|
256
|
+
RuntimeApiGatewayService,
|
|
257
|
+
AdobeAuth,
|
|
258
|
+
BearerToken,
|
|
259
|
+
} = require('@adobe-commerce/aio-toolkit');
|
|
260
|
+
|
|
261
|
+
// Inside your action handler (params, ctx):
|
|
262
|
+
const { logger } = ctx;
|
|
263
|
+
|
|
264
|
+
// Check cache first (replace with your preferred cache — module-level Map, Redis, etc.)
|
|
265
|
+
let token = tokenCache.get('ims_token');
|
|
266
|
+
|
|
267
|
+
if (!token || !BearerToken.info(token).isValid) {
|
|
268
|
+
token = await AdobeAuth.getToken(
|
|
269
|
+
params.CLIENT_ID,
|
|
270
|
+
params.CLIENT_SECRET,
|
|
271
|
+
params.TECHNICAL_ACCOUNT_ID,
|
|
272
|
+
params.TECHNICAL_ACCOUNT_EMAIL,
|
|
273
|
+
params.IMS_ORG_ID,
|
|
274
|
+
['openid', 'AdobeID', 'adobeio_api']
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const tokenInfo = BearerToken.info(token);
|
|
278
|
+
if (tokenInfo.isValid && tokenInfo.timeUntilExpiry) {
|
|
279
|
+
// Cache with 10-minute safety buffer
|
|
280
|
+
const ttlMs = tokenInfo.timeUntilExpiry - 600_000;
|
|
281
|
+
tokenCache.set('ims_token', token, ttlMs);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const service = new RuntimeApiGatewayService(
|
|
286
|
+
params.NAMESPACE,
|
|
287
|
+
params.IMS_ORG_ID,
|
|
288
|
+
token,
|
|
289
|
+
logger
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
// Make calls as normal
|
|
293
|
+
const response = await service.get('v1/web/[package]/[action]');
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### With additional headers
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
// Override or extend default headers for a specific call
|
|
300
|
+
const response = await service.get(
|
|
301
|
+
'v1/web/[package]/[action]',
|
|
302
|
+
{ 'x-correlation-id': params.correlationId }
|
|
303
|
+
);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**TypeScript:** Same with `import` syntax:
|
|
307
|
+
```typescript
|
|
308
|
+
import { RuntimeApiGatewayService, AdobeAuth, BearerToken } from '@adobe-commerce/aio-toolkit';
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Step 4.2: Update Action Configuration
|
|
312
|
+
|
|
313
|
+
Add the required inputs to the action's YAML configuration:
|
|
314
|
+
|
|
315
|
+
```yaml
|
|
316
|
+
[action-name]:
|
|
317
|
+
function: [action-path]/index.[js/ts]
|
|
318
|
+
web: 'yes' # or 'no' for event consumers / internal actions
|
|
319
|
+
runtime: nodejs:22
|
|
320
|
+
annotations:
|
|
321
|
+
require-adobe-auth: [true/false]
|
|
322
|
+
final: true
|
|
323
|
+
inputs:
|
|
324
|
+
LOG_LEVEL: debug
|
|
325
|
+
# RuntimeApiGatewayService inputs
|
|
326
|
+
NAMESPACE: $AIO_runtime_namespace # auto-available — no .env entry needed
|
|
327
|
+
IMS_ORG_ID: $IMS_ORG_ID
|
|
328
|
+
CLIENT_ID: $CLIENT_ID
|
|
329
|
+
CLIENT_SECRET: $CLIENT_SECRET
|
|
330
|
+
TECHNICAL_ACCOUNT_ID: $TECHNICAL_ACCOUNT_ID
|
|
331
|
+
TECHNICAL_ACCOUNT_EMAIL: $TECHNICAL_ACCOUNT_EMAIL
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
> `$AIO_runtime_namespace` is automatically available in every App Builder project — it resolves to the current workspace namespace without any manual configuration. Do NOT add it to `.env`.
|
|
335
|
+
|
|
336
|
+
### Step 4.3: Add Environment Variables
|
|
337
|
+
|
|
338
|
+
Update the project root `.env` with credential placeholders (skip `NAMESPACE` — it's auto-provided):
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
# RuntimeApiGatewayService — IMS credentials
|
|
342
|
+
IMS_ORG_ID=your-org@AdobeOrg
|
|
343
|
+
CLIENT_ID=
|
|
344
|
+
CLIENT_SECRET=
|
|
345
|
+
TECHNICAL_ACCOUNT_ID=
|
|
346
|
+
TECHNICAL_ACCOUNT_EMAIL=
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**How to get credentials:**
|
|
350
|
+
1. Log in to [Adobe Developer Console](https://developer.adobe.com/console)
|
|
351
|
+
2. Open your App Builder project
|
|
352
|
+
3. Go to **Credentials** → Service Account (OAuth S2S) or JWT
|
|
353
|
+
4. Copy `Client ID` → `CLIENT_ID`, `Client Secret` → `CLIENT_SECRET`
|
|
354
|
+
5. Copy `Technical Account ID` and `Technical Account Email`
|
|
355
|
+
6. Copy `Organization ID` → `IMS_ORG_ID`
|
|
356
|
+
|
|
357
|
+
**Security Notes:**
|
|
358
|
+
- Never commit `.env` — confirm it is in `.gitignore`
|
|
359
|
+
- `CLIENT_SECRET` is a secret — treat it with the same care as a password
|
|
360
|
+
- Use different credentials per environment (dev / staging / production)
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Step 5: Recommendations
|
|
365
|
+
|
|
366
|
+
### A. Response Handling Strategy
|
|
367
|
+
|
|
368
|
+
`RuntimeApiGatewayService` methods throw on **network-level failure** but return normally for HTTP error status codes. Always check both:
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
try {
|
|
372
|
+
const response = await service.post('v1/web/[package]/[action]', payload);
|
|
373
|
+
|
|
374
|
+
// HTTP-level check
|
|
375
|
+
if (!response.ok) {
|
|
376
|
+
const errorBody = await response.text();
|
|
377
|
+
logger.error({ message: '[action]-downstream-http-error', status: response.status, errorBody });
|
|
378
|
+
return RuntimeActionResponse.error(response.status, `Downstream action returned ${response.status}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const result = await response.json();
|
|
382
|
+
return RuntimeActionResponse.success(result);
|
|
383
|
+
|
|
384
|
+
} catch (error) {
|
|
385
|
+
// Network/connection failure
|
|
386
|
+
logger.error({ message: '[action]-downstream-network-error', error: error.message });
|
|
387
|
+
return RuntimeActionResponse.error(HttpStatus.INTERNAL_ERROR, `Gateway call failed: ${error.message}`);
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### B. Token Caching Decision
|
|
392
|
+
|
|
393
|
+
| Scenario | Strategy |
|
|
394
|
+
|---|---|
|
|
395
|
+
| Action invoked infrequently (< 1 req/min) | **Simple** — generate fresh token per invocation |
|
|
396
|
+
| Action invoked frequently or in a loop | **Cached** — use `BearerToken.info()` + module-level cache |
|
|
397
|
+
| Action runs in a stateful process | **Cached** — share token across invocations |
|
|
398
|
+
|
|
399
|
+
> OpenWhisk actions are cold-started per request — module-level variables do NOT persist between invocations unless the same container is reused. For reliable caching, use an external store (`@adobe/aio-lib-state`, Redis, etc.).
|
|
400
|
+
|
|
401
|
+
### C. No Retries or Timeouts
|
|
402
|
+
|
|
403
|
+
`RuntimeApiGatewayService` has no built-in retry or timeout logic. For reliability-sensitive paths:
|
|
404
|
+
|
|
405
|
+
```javascript
|
|
406
|
+
// Simple retry with exponential backoff
|
|
407
|
+
async function callWithRetry(service, endpoint, retries = 3) {
|
|
408
|
+
for (let i = 0; i < retries; i++) {
|
|
409
|
+
try {
|
|
410
|
+
const response = await service.get(endpoint);
|
|
411
|
+
if (response.ok) return response;
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (i === retries - 1) throw error;
|
|
414
|
+
await new Promise(r => setTimeout(r, 2 ** i * 1000));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### D. URL Construction
|
|
421
|
+
|
|
422
|
+
Endpoint is appended as-is — no leading slash normalization:
|
|
423
|
+
|
|
424
|
+
```javascript
|
|
425
|
+
// ✅ Correct
|
|
426
|
+
await service.get('v1/web/my-package/my-action');
|
|
427
|
+
// → https://adobeioruntime.net/apis/{namespace}/v1/web/my-package/my-action
|
|
428
|
+
|
|
429
|
+
// ⚠️ Extra leading slash produces double slash in URL
|
|
430
|
+
await service.get('/v1/web/my-package/my-action');
|
|
431
|
+
// → https://adobeioruntime.net/apis/{namespace}//v1/web/...
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Key Components from @adobe-commerce/aio-toolkit
|
|
437
|
+
|
|
438
|
+
### RuntimeApiGatewayService Constructor
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
new RuntimeApiGatewayService(
|
|
442
|
+
namespace: string, // Adobe I/O Runtime namespace — use params.NAMESPACE
|
|
443
|
+
imsOrgId: string, // IMS Org ID — use params.IMS_ORG_ID
|
|
444
|
+
imsToken: string, // Bearer token — from AdobeAuth.getToken()
|
|
445
|
+
logger?: any // optional: enables request/error logging
|
|
446
|
+
)
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
> **No constructor validation** — invalid credentials are only detected when the first request is made.
|
|
450
|
+
|
|
451
|
+
### Methods (all return `Promise<Response>`)
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
service.get(endpoint, additionalHeaders?)
|
|
455
|
+
service.post(endpoint, payload, additionalHeaders?)
|
|
456
|
+
service.put(endpoint, payload, additionalHeaders?)
|
|
457
|
+
service.delete(endpoint, additionalHeaders?)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
- All methods **throw** on network failure (after logging if a logger is provided)
|
|
461
|
+
- All methods return the **raw `node-fetch` Response** — you must call `.json()` / `.text()` and check `response.ok`
|
|
462
|
+
- `additionalHeaders` are merged after the defaults and can override `Authorization`, `x-gw-ims-org-id`, or `Content-Type`
|
|
463
|
+
|
|
464
|
+
### Default Headers (sent on every request)
|
|
465
|
+
|
|
466
|
+
```
|
|
467
|
+
Authorization: Bearer <imsToken>
|
|
468
|
+
x-gw-ims-org-id: <imsOrgId>
|
|
469
|
+
Content-Type: application/json
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### AdobeAuth.getToken() — generate IMS token
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
const token = await AdobeAuth.getToken(
|
|
476
|
+
clientId, // params.CLIENT_ID
|
|
477
|
+
clientSecret, // params.CLIENT_SECRET
|
|
478
|
+
technicalAccountId, // params.TECHNICAL_ACCOUNT_ID
|
|
479
|
+
technicalAccountEmail, // params.TECHNICAL_ACCOUNT_EMAIL
|
|
480
|
+
orgId, // params.IMS_ORG_ID
|
|
481
|
+
scopes // ['openid', 'AdobeID', 'adobeio_api']
|
|
482
|
+
);
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### BearerToken.info() — inspect token validity (for caching)
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
const { isValid, timeUntilExpiry } = BearerToken.info(token);
|
|
489
|
+
// isValid: boolean — false if expired or invalid
|
|
490
|
+
// timeUntilExpiry: number (ms) — time until expiry; 0 or undefined if expired
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Important Notes
|
|
496
|
+
|
|
497
|
+
1. **No constructor validation**: Invalid credentials fail on first request, not at construction time — verify credentials are set before constructing
|
|
498
|
+
2. **Raw Response returned**: Always call `.json()` or `.text()` and check `response.ok` — nothing is auto-parsed
|
|
499
|
+
3. **Methods throw on network failure**: Wrap in try/catch to handle connection errors separately from HTTP errors
|
|
500
|
+
4. **No built-in retries or timeouts**: Implement your own retry logic for reliability-sensitive paths
|
|
501
|
+
5. **`NAMESPACE` is auto-provided**: `$AIO_runtime_namespace` is always available in App Builder — do not manually add it to `.env`
|
|
502
|
+
6. **Token caching is application-level**: OpenWhisk containers may not persist module-level state — use external storage for reliable cross-invocation caching
|
|
503
|
+
7. **Endpoint appended as-is**: No leading slash normalization — omit the leading `/` in endpoint strings
|
|
504
|
+
8. **Additional headers override defaults**: Can override `Authorization`, `x-gw-ims-org-id`, and `Content-Type` per-request via `additionalHeaders`
|
|
505
|
+
9. **vs Openwhisk client**: Use `RuntimeApiGatewayService` for `web: 'yes'` targets needing HTTP-level control; use `Openwhisk` client for `web: 'no'` internal actions
|
|
506
|
+
10. **Works with any action type**: RuntimeAction, WebhookAction, EventConsumerAction, GraphQlAction, OpenwhiskAction
|
|
507
|
+
|
|
508
|
+
## Integration with Action Creation Rules
|
|
509
|
+
|
|
510
|
+
**This rule should be triggered when developers mention calling web actions in their business logic:**
|
|
511
|
+
|
|
512
|
+
### Trigger Patterns in Other Rules
|
|
513
|
+
|
|
514
|
+
When developers mention these phrases during action creation:
|
|
515
|
+
|
|
516
|
+
- "Call the [action-name] action"
|
|
517
|
+
- "Invoke web action from my action"
|
|
518
|
+
- "HTTP request to another Runtime action"
|
|
519
|
+
- "Call via API Gateway"
|
|
520
|
+
- "Orchestrate actions with HTTP"
|
|
521
|
+
- Any phrase involving "RuntimeApiGatewayService" or "API Gateway" + "call"/"invoke"/"request"
|
|
522
|
+
|
|
523
|
+
**Action to take:**
|
|
524
|
+
|
|
525
|
+
1. Complete the action creation first
|
|
526
|
+
2. Inform the user: "I see you need to call a web action via API Gateway. Let me integrate RuntimeApiGatewayService."
|
|
527
|
+
3. Trigger/reference the "Using RuntimeApiGatewayService" rule
|
|
528
|
+
4. This rule handles token generation, service construction, response handling, and config wiring
|
|
529
|
+
|
|
530
|
+
**Example Flow:**
|
|
531
|
+
|
|
532
|
+
```
|
|
533
|
+
User: "Create a runtime action that calls the product-service web action to fetch product data"
|
|
534
|
+
AI: ✓ Detected TypeScript project
|
|
535
|
+
AI: Creating runtime action... [generates .ts file]
|
|
536
|
+
AI: I see you need to call a web action. Let me integrate RuntimeApiGatewayService.
|
|
537
|
+
AI: [Adds token generation, service construction, GET call, response handling, and config inputs]
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## Related Rules
|
|
541
|
+
|
|
542
|
+
- **"Using AdobeAuth"** (`aio-toolkit-use-adobe-auth.mdc`) — primary reference for `AdobeAuth.getToken()` usage, caching patterns, scope selection, and error handling for the IMS token this service requires
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe-commerce/aio-toolkit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "A comprehensive TypeScript toolkit for Adobe App Builder applications providing standardized Adobe Commerce integrations, I/O Events orchestration, file storage utilities, authentication helpers, and robust backend development tools with 100% test coverage.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
],
|
|
23
23
|
"bin": {
|
|
24
24
|
"aio-toolkit-cursor-context": "dist/aio-toolkit-cursor-context/bin/cli.js",
|
|
25
|
-
"aio-toolkit-onboard-events": "dist/aio-toolkit-onboard-events/bin/cli.js"
|
|
25
|
+
"aio-toolkit-onboard-events": "dist/aio-toolkit-onboard-events/bin/cli.js",
|
|
26
|
+
"aio-toolkit-cli-workflow": "dist/aio-toolkit-cli-workflow/bin/cli.js"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
29
|
"build": "tsup",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"license": "SEE LICENSE IN LICENSE",
|
|
61
62
|
"dependencies": {
|
|
62
63
|
"@adobe-commerce/aio-services-kit": "^1.0.0",
|
|
64
|
+
"@aws-sdk/client-sqs": "^3.1045.0",
|
|
63
65
|
"amqplib": "^1.0.3",
|
|
64
66
|
"cloudevents": "^8.0.2",
|
|
65
67
|
"dotenv": "^17.3.1",
|