@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,510 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Adding PublishEvent to Adobe I/O Runtime actions using @adobe-commerce/aio-toolkit
|
|
3
|
+
globs: '**/{actions,lib}/**/*.{ts,js}'
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Using PublishEvent
|
|
8
|
+
|
|
9
|
+
## Trigger Conditions
|
|
10
|
+
|
|
11
|
+
This rule applies when the user requests to publish events to Adobe I/O Events from within an action using any of these phrases:
|
|
12
|
+
|
|
13
|
+
- "Publish event to I/O Events"
|
|
14
|
+
- "Emit a CloudEvent"
|
|
15
|
+
- "Send event to Adobe I/O"
|
|
16
|
+
- "Fan-out events"
|
|
17
|
+
- "Trigger downstream events with PublishEvent"
|
|
18
|
+
- "Use PublishEvent in [action-name]"
|
|
19
|
+
- "Add event publishing to [action-name]"
|
|
20
|
+
- "Publish [event-type] event"
|
|
21
|
+
- "Integrate PublishEvent"
|
|
22
|
+
- "Notify via I/O Events"
|
|
23
|
+
|
|
24
|
+
**Important**: This rule does NOT create new actions. It integrates `PublishEvent` into existing actions only.
|
|
25
|
+
|
|
26
|
+
**Integration with Other Action Creation Rules:**
|
|
27
|
+
|
|
28
|
+
When using action creation rules (RuntimeAction, WebhookAction, EventConsumerAction, OpenwhiskAction) and the developer mentions publishing events in the business logic:
|
|
29
|
+
|
|
30
|
+
- **Automatically trigger this rule** after the action is created
|
|
31
|
+
- Examples: "publish an event after processing", "emit a CloudEvent", "fan-out to other consumers", "notify via I/O Events"
|
|
32
|
+
- This rule handles all PublishEvent setup, credential wiring, config changes, and code integration
|
|
33
|
+
|
|
34
|
+
**If you need to create a new action first:**
|
|
35
|
+
|
|
36
|
+
- Use the appropriate action creation command first
|
|
37
|
+
- Then this rule will add PublishEvent integration to that action
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Step 1: Verify Prerequisites
|
|
42
|
+
|
|
43
|
+
Before proceeding, verify:
|
|
44
|
+
|
|
45
|
+
1. **Toolkit installed**: Check `@adobe-commerce/aio-toolkit` in `package.json` dependencies
|
|
46
|
+
- If NOT installed: `npm install @adobe-commerce/aio-toolkit`
|
|
47
|
+
|
|
48
|
+
2. **AIO SDK installed**: Check `@adobe/aio-sdk` in `package.json` dependencies
|
|
49
|
+
- Required for `Core.AuthClient.generateAccessToken(params)` to obtain an IMS access token
|
|
50
|
+
- If NOT installed: `npm install @adobe/aio-sdk`
|
|
51
|
+
- Note: App Builder projects typically include this — run `npm list @adobe/aio-sdk` to confirm
|
|
52
|
+
|
|
53
|
+
3. **Detect Language (TypeScript vs JavaScript)**:
|
|
54
|
+
|
|
55
|
+
**Check for TypeScript indicators (check ALL of these)**:
|
|
56
|
+
1. **Package Dependencies**: Look for `typescript` in `package.json` dependencies or devDependencies
|
|
57
|
+
2. **Config File**: Check if `tsconfig.json` exists in project root
|
|
58
|
+
3. **TypeScript Loaders**: Look for `ts-loader`, `ts-node`, or `@types/*` packages in `package.json`
|
|
59
|
+
4. **Existing Files**: Check for `.ts` or `.tsx` files in key directories (`actions/`, `src/`, `lib/`)
|
|
60
|
+
|
|
61
|
+
**Detection Logic**:
|
|
62
|
+
- **If ANY of the following are true → TypeScript project**:
|
|
63
|
+
- `typescript` package installed AND `tsconfig.json` exists
|
|
64
|
+
- Multiple TypeScript indicators (2 or more from above list)
|
|
65
|
+
- Existing `.ts` files found in `actions/` or `lib/` directories
|
|
66
|
+
- **Otherwise → JavaScript project**
|
|
67
|
+
|
|
68
|
+
4. **Detect Project Structure**:
|
|
69
|
+
- Check `app.config.yaml` for `application:` section (root actions)
|
|
70
|
+
- Check `app.config.yaml` for `extensions:` section (extension point actions)
|
|
71
|
+
- If extensions exist, parse the `$include` path for the extension directory
|
|
72
|
+
|
|
73
|
+
5. **Check Existing Actions**: List all existing actions and their file paths
|
|
74
|
+
- From root `actions/` directory
|
|
75
|
+
- From `[extension-path]/actions/` directory
|
|
76
|
+
- If NO actions exist: Stop and recommend creating an action first
|
|
77
|
+
|
|
78
|
+
6. Only proceed to Step 2 after confirming all prerequisites
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Step 2: Ask Required Questions
|
|
83
|
+
|
|
84
|
+
Before generating any code, ask the user the following questions:
|
|
85
|
+
|
|
86
|
+
**Important**: Do not make assumptions — keep asking until all details are clear.
|
|
87
|
+
|
|
88
|
+
1. **Target Action**: Which existing action do you want to add PublishEvent to?
|
|
89
|
+
- List all discovered actions for the user to choose from
|
|
90
|
+
|
|
91
|
+
2. **Provider ID**: What is the Adobe I/O Events provider ID?
|
|
92
|
+
- Source: Adobe Developer Console → Your project → I/O Events → Provider ID
|
|
93
|
+
- Will be stored as `PROVIDER_ID` env var (recommended) or hardcoded (not recommended)
|
|
94
|
+
|
|
95
|
+
3. **Event Code(s)**: What event type(s) will this action publish?
|
|
96
|
+
- Format: reverse-DNS, e.g. `com.adobe.commerce.order.created`
|
|
97
|
+
- Multiple codes if the action publishes different event types based on conditions
|
|
98
|
+
- Each code becomes the CloudEvent `type` field
|
|
99
|
+
|
|
100
|
+
4. **Payload**: What data should the event carry?
|
|
101
|
+
- Describe the structure: e.g. `{ orderId, customerId, amount }`
|
|
102
|
+
- This becomes the CloudEvent `data` field
|
|
103
|
+
|
|
104
|
+
5. **Subject** (optional): Should events include a `subject` field?
|
|
105
|
+
- Used for routing and filtering in downstream consumers
|
|
106
|
+
- Example: `orders/${orderId}`, `products/${sku}`
|
|
107
|
+
- If not needed, subject is omitted entirely
|
|
108
|
+
|
|
109
|
+
6. **Event ID**: How should the event ID be assigned?
|
|
110
|
+
- **Auto-generate** (default): UUID v4 generated automatically per event
|
|
111
|
+
- **Custom**: Provide a specific ID (e.g. from a correlation ID in `params`)
|
|
112
|
+
|
|
113
|
+
7. **Fan-out Pattern**: Does this action publish multiple events per invocation?
|
|
114
|
+
- **Single event**: One `publisher.execute()` call
|
|
115
|
+
- **Multiple events**: `Promise.all()` fan-out pattern (e.g. notify multiple downstream systems)
|
|
116
|
+
- If yes, ask: what are all the event codes and their payloads?
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Step 3: Confirm Implementation Details
|
|
121
|
+
|
|
122
|
+
After receiving answers to ALL questions, present a comprehensive summary and **wait for user confirmation** before proceeding to Step 4:
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### 📋 PublishEvent Integration Summary
|
|
127
|
+
|
|
128
|
+
**Target Action:** `[action-name]`
|
|
129
|
+
**Action Path:** `[path to action file]`
|
|
130
|
+
**Language:** [TypeScript/JavaScript] (auto-detected)
|
|
131
|
+
|
|
132
|
+
**Event Publishing Configuration:**
|
|
133
|
+
|
|
134
|
+
- **Provider ID:** `$PROVIDER_ID` (from env var) or `[hardcoded-value]`
|
|
135
|
+
- **Pattern:** [Single event / Fan-out with Promise.all()]
|
|
136
|
+
|
|
137
|
+
**Event(s) to Publish:**
|
|
138
|
+
|
|
139
|
+
[For single event:]
|
|
140
|
+
- **Event Code:** `[event-code]`
|
|
141
|
+
- **Payload:** `{ [payload fields] }`
|
|
142
|
+
- **Subject:** `[subject pattern]` / Not included
|
|
143
|
+
- **Event ID:** Auto-generated UUID / `[source field]`
|
|
144
|
+
|
|
145
|
+
[For fan-out:]
|
|
146
|
+
| Event Code | Payload | Subject |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `[code-1]` | `{ [fields] }` | `[subject]` or — |
|
|
149
|
+
| `[code-2]` | `{ [fields] }` | `[subject]` or — |
|
|
150
|
+
|
|
151
|
+
**Environment Variables:**
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Adobe I/O Events — PublishEvent credentials
|
|
155
|
+
IMS_ORG_ID=
|
|
156
|
+
API_KEY=
|
|
157
|
+
PROVIDER_ID=
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
📍 **Source:** Adobe Developer Console → Your project → Credentials + I/O Events
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### ✅ What Will Be Changed
|
|
165
|
+
|
|
166
|
+
**1. Action Implementation**
|
|
167
|
+
- ✏️ `[action-path]/index.[js/ts]`
|
|
168
|
+
- Import `PublishEvent` from `@adobe-commerce/aio-toolkit`
|
|
169
|
+
- Import `Core` from `@adobe/aio-sdk`
|
|
170
|
+
- Generate IMS access token via `Core.AuthClient.generateAccessToken(params)`
|
|
171
|
+
- Construct `new PublishEvent(params.IMS_ORG_ID, params.API_KEY, accessToken, logger)`
|
|
172
|
+
- Call `publisher.execute(...)` [with `Promise.all()` if fan-out]
|
|
173
|
+
- Handle `result.status === 'failed'` responses
|
|
174
|
+
|
|
175
|
+
**2. Action Configuration**
|
|
176
|
+
- ✏️ `app.config.yaml` or `ext.config.yaml` (or `actions.config.yaml` if packaged)
|
|
177
|
+
- Add `include-ims-credentials: true` annotation (required for token generation)
|
|
178
|
+
- Add `IMS_ORG_ID: $IMS_ORG_ID`, `API_KEY: $API_KEY`, `PROVIDER_ID: $PROVIDER_ID` inputs
|
|
179
|
+
|
|
180
|
+
**3. Environment Variables**
|
|
181
|
+
- 📄/✏️ `.env` (project root) — add placeholder variables if not already present
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
**Should I proceed with this implementation?**
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Step 4: Generate Implementation
|
|
190
|
+
|
|
191
|
+
### Step 4.1: Update the Action Code
|
|
192
|
+
|
|
193
|
+
Add `PublishEvent` integration to the selected action.
|
|
194
|
+
|
|
195
|
+
#### Single Event — JavaScript
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
const { PublishEvent } = require('@adobe-commerce/aio-toolkit');
|
|
199
|
+
const { Core } = require('@adobe/aio-sdk');
|
|
200
|
+
|
|
201
|
+
// Inside your action handler (params, ctx):
|
|
202
|
+
const { logger } = ctx;
|
|
203
|
+
|
|
204
|
+
// Step 1: Generate IMS access token
|
|
205
|
+
// Requires include-ims-credentials: true in app.config.yaml
|
|
206
|
+
const tokenResponse = await Core.AuthClient.generateAccessToken(params);
|
|
207
|
+
const accessToken = tokenResponse && tokenResponse.access_token;
|
|
208
|
+
|
|
209
|
+
// Step 2: Construct publisher — throws if credentials are blank
|
|
210
|
+
const publisher = new PublishEvent(
|
|
211
|
+
params.IMS_ORG_ID,
|
|
212
|
+
params.API_KEY,
|
|
213
|
+
accessToken,
|
|
214
|
+
logger // optional: enables debug/info/error logs from the publisher
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Step 3: Publish the event
|
|
218
|
+
const result = await publisher.execute(
|
|
219
|
+
params.PROVIDER_ID, // required: target event provider
|
|
220
|
+
'[event-code]', // required: CloudEvent type field
|
|
221
|
+
{ /* payload fields */ }, // required: CloudEvent data field
|
|
222
|
+
undefined, // optional: omit for auto-generated UUID
|
|
223
|
+
undefined // optional: subject for routing/filtering
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Step 4: Handle result — execute() NEVER throws, always check status
|
|
227
|
+
if (result.status === 'failed') {
|
|
228
|
+
logger.error({ message: '[action-name]-publish-failed', error: result.error });
|
|
229
|
+
// Decide whether to return an error or continue depending on business requirements
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
logger.info({ message: '[action-name]-published', eventId: result.eventId });
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**TypeScript:** Same with `import` syntax and optional type imports:
|
|
236
|
+
```typescript
|
|
237
|
+
import { PublishEvent } from '@adobe-commerce/aio-toolkit';
|
|
238
|
+
import type { PublishEventResult } from '@adobe-commerce/aio-toolkit';
|
|
239
|
+
import { Core } from '@adobe/aio-sdk';
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### Fan-out Pattern — JavaScript
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
const { PublishEvent } = require('@adobe-commerce/aio-toolkit');
|
|
246
|
+
const { Core } = require('@adobe/aio-sdk');
|
|
247
|
+
|
|
248
|
+
// Inside your action handler (params, ctx):
|
|
249
|
+
const { logger } = ctx;
|
|
250
|
+
|
|
251
|
+
const tokenResponse = await Core.AuthClient.generateAccessToken(params);
|
|
252
|
+
const accessToken = tokenResponse && tokenResponse.access_token;
|
|
253
|
+
|
|
254
|
+
const publisher = new PublishEvent(
|
|
255
|
+
params.IMS_ORG_ID,
|
|
256
|
+
params.API_KEY,
|
|
257
|
+
accessToken,
|
|
258
|
+
logger
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// Publish multiple events in parallel
|
|
262
|
+
const events = [
|
|
263
|
+
{ code: '[event-code-1]', data: { /* payload */ }, subject: undefined },
|
|
264
|
+
{ code: '[event-code-2]', data: { /* payload */ }, subject: undefined },
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
const results = await Promise.all(
|
|
268
|
+
events.map(({ code, data, subject }) =>
|
|
269
|
+
publisher.execute(params.PROVIDER_ID, code, data, undefined, subject)
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// Collect failures — execute() never throws, so all results are always returned
|
|
274
|
+
const failed = results.filter(r => r.status === 'failed');
|
|
275
|
+
const published = results.filter(r => r.status === 'published');
|
|
276
|
+
|
|
277
|
+
if (failed.length > 0) {
|
|
278
|
+
logger.error({
|
|
279
|
+
message: '[action-name]-publish-partial-failure',
|
|
280
|
+
failures: failed.map(r => r.error),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
logger.info({
|
|
285
|
+
message: '[action-name]-published',
|
|
286
|
+
publishedCount: published.length,
|
|
287
|
+
failedCount: failed.length,
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### With Custom Event ID and Subject
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
const result = await publisher.execute(
|
|
295
|
+
params.PROVIDER_ID,
|
|
296
|
+
'[event-code]',
|
|
297
|
+
{ /* payload */ },
|
|
298
|
+
params.correlationId, // use existing correlation ID as event ID
|
|
299
|
+
`[resource-type]/${params.id}` // subject for routing — e.g. 'orders/123'
|
|
300
|
+
);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Step 4.2: Update Action Configuration
|
|
304
|
+
|
|
305
|
+
Add `include-ims-credentials: true` and required inputs to the action's configuration.
|
|
306
|
+
|
|
307
|
+
**In `app.config.yaml`, `ext.config.yaml`, or `actions.config.yaml`:**
|
|
308
|
+
|
|
309
|
+
```yaml
|
|
310
|
+
[action-name]:
|
|
311
|
+
function: [action-path]/index.[js/ts]
|
|
312
|
+
web: 'yes' # or 'no' for event consumers
|
|
313
|
+
runtime: nodejs:22
|
|
314
|
+
annotations:
|
|
315
|
+
require-adobe-auth: [true/false]
|
|
316
|
+
final: true
|
|
317
|
+
include-ims-credentials: true # REQUIRED: injects IMS credentials for token generation
|
|
318
|
+
inputs:
|
|
319
|
+
LOG_LEVEL: debug
|
|
320
|
+
# Adobe I/O Events — PublishEvent credentials
|
|
321
|
+
IMS_ORG_ID: $IMS_ORG_ID
|
|
322
|
+
API_KEY: $API_KEY
|
|
323
|
+
PROVIDER_ID: $PROVIDER_ID
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
> **Why `include-ims-credentials: true`?** This annotation tells Adobe App Builder to inject the IMS credential set into `params` at runtime. `Core.AuthClient.generateAccessToken(params)` reads from these injected fields to obtain a fresh access token per activation. Without this annotation, `generateAccessToken` will fail.
|
|
327
|
+
|
|
328
|
+
### Step 4.3: Add Environment Variables
|
|
329
|
+
|
|
330
|
+
Update the project root `.env` file with credential placeholders:
|
|
331
|
+
|
|
332
|
+
1. **Check for `.env`**: Look in the project root directory
|
|
333
|
+
2. **Create if missing**: Create `.env` in project root if it doesn't exist
|
|
334
|
+
3. **Add only missing variables**: Do not overwrite values already present
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
# Adobe I/O Events — PublishEvent credentials
|
|
338
|
+
IMS_ORG_ID=your-org@AdobeOrg
|
|
339
|
+
API_KEY=
|
|
340
|
+
PROVIDER_ID=
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**How to get credentials:**
|
|
344
|
+
1. Log in to [Adobe Developer Console](https://developer.adobe.com/console)
|
|
345
|
+
2. Select your App Builder project
|
|
346
|
+
3. **`IMS_ORG_ID`**: Organization → click your org name, copy the Org ID
|
|
347
|
+
4. **`API_KEY`**: Credentials section → copy the Client ID
|
|
348
|
+
5. **`PROVIDER_ID`**: I/O Events section → Event Providers → copy the provider ID
|
|
349
|
+
|
|
350
|
+
**Security Notes:**
|
|
351
|
+
- Never commit `.env` to version control — confirm `.env` is in `.gitignore`
|
|
352
|
+
- `IMS_ORG_ID` and `API_KEY` are not secrets themselves but `API_KEY` combined with an access token grants API access — treat them accordingly
|
|
353
|
+
- Access tokens are generated fresh per activation — no token storage required
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Step 5: Recommendations
|
|
358
|
+
|
|
359
|
+
### A. Error Handling Strategy
|
|
360
|
+
|
|
361
|
+
`execute()` **never throws** — it always returns a `PublishEventResult`. Choose your error handling based on business requirements:
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
// Option 1: Hard fail — event publishing is critical
|
|
365
|
+
if (result.status === 'failed') {
|
|
366
|
+
return RuntimeActionResponse.error(HttpStatus.INTERNAL_ERROR, `Event publish failed: ${result.error}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Option 2: Soft fail — log and continue, event publishing is best-effort
|
|
370
|
+
if (result.status === 'failed') {
|
|
371
|
+
logger.warn({ message: 'publish-skipped', error: result.error });
|
|
372
|
+
// continue processing
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Option 3: Fan-out partial failure — succeed if at least some events published
|
|
376
|
+
const failed = results.filter(r => r.status === 'failed');
|
|
377
|
+
if (failed.length === results.length) {
|
|
378
|
+
return RuntimeActionResponse.error(HttpStatus.INTERNAL_ERROR, 'All event publishes failed');
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### B. Constructor Throws vs execute() Does Not
|
|
383
|
+
|
|
384
|
+
| Operation | Throws? | Action |
|
|
385
|
+
|---|---|---|
|
|
386
|
+
| `new PublishEvent(...)` | **Yes** — if any credential is blank | Wrap in try/catch, or ensure env vars are set |
|
|
387
|
+
| `publisher.execute(...)` | **Never** — always returns `PublishEventResult` | Always check `result.status` |
|
|
388
|
+
|
|
389
|
+
Since credentials come from `params` (action inputs), a blank credential will cause the constructor to throw inside your handler. `RuntimeAction`/`EventConsumerAction` catch all unhandled throws and return `500 'server error'` — but a more informative pattern is to check credentials before constructing:
|
|
390
|
+
|
|
391
|
+
```javascript
|
|
392
|
+
if (!params.IMS_ORG_ID || !params.API_KEY || !accessToken) {
|
|
393
|
+
logger.error({ message: '[action-name]-missing-credentials' });
|
|
394
|
+
return RuntimeActionResponse.error(HttpStatus.INTERNAL_ERROR, 'Missing event publishing credentials');
|
|
395
|
+
}
|
|
396
|
+
const publisher = new PublishEvent(params.IMS_ORG_ID, params.API_KEY, accessToken, logger);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### C. Testing & Verification
|
|
400
|
+
|
|
401
|
+
1. **Unit tests**: Mock `Core.AuthClient.generateAccessToken` and `PublishEvent` — verify result handling
|
|
402
|
+
2. **Integration tests**: Use a real I/O Events provider in a dev environment — confirm events appear in your consumer
|
|
403
|
+
3. **Local**: `aio app dev` + `aio app logs` — check for `publish-failed` log entries
|
|
404
|
+
4. **Adobe Developer Console**: I/O Events Debug Tracing shows published events in real time
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Key Components from @adobe-commerce/aio-toolkit
|
|
409
|
+
|
|
410
|
+
### PublishEvent Constructor
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
new PublishEvent(
|
|
414
|
+
imsOrgId: string, // Adobe IMS Org ID — throws if blank
|
|
415
|
+
apiKey: string, // Adobe API Key (Client ID) — throws if blank
|
|
416
|
+
accessToken: string, // IMS access token — throws if blank
|
|
417
|
+
logger?: any // optional: enables publisher-level logging
|
|
418
|
+
)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Source for credentials:** `params.IMS_ORG_ID`, `params.API_KEY`, and a fresh token from `Core.AuthClient.generateAccessToken(params)`.
|
|
422
|
+
|
|
423
|
+
### publishEvent.execute()
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
async execute(
|
|
427
|
+
providerId: string, // required: target I/O Events provider
|
|
428
|
+
eventCode: string, // required: CloudEvent type (e.g. 'com.myapp.order.created')
|
|
429
|
+
payload: any, // required: event data — cannot be null/undefined
|
|
430
|
+
eventId?: string, // optional: custom ID, auto-generates UUID v4 if omitted
|
|
431
|
+
subject?: string // optional: routing/filtering hint — omitted if falsy
|
|
432
|
+
): Promise<PublishEventResult>
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Always resolves — never rejects.** All internal errors (blank providerId/eventCode, null payload, SDK init failure, publish failure) are caught and returned as `{ status: 'failed', error: '...' }`.
|
|
436
|
+
|
|
437
|
+
### PublishEventResult
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
interface PublishEventResult {
|
|
441
|
+
eventId: string; // event ID assigned on success; new UUID on failure
|
|
442
|
+
status: 'published' | 'failed'; // always check this field
|
|
443
|
+
publishedAt: string; // ISO 8601 UTC timestamp
|
|
444
|
+
error?: string; // present only when status === 'failed'
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### CloudEvent Structure (built automatically)
|
|
449
|
+
|
|
450
|
+
| Field | Value |
|
|
451
|
+
|---|---|
|
|
452
|
+
| `id` | `eventId` or auto-generated UUID v4 |
|
|
453
|
+
| `source` | `` `urn:uuid:${providerId}` `` |
|
|
454
|
+
| `type` | `eventCode` |
|
|
455
|
+
| `datacontenttype` | `'application/json'` (fixed) |
|
|
456
|
+
| `data` | `payload` |
|
|
457
|
+
| `subject` | `subject` argument — omitted if falsy |
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Important Notes
|
|
462
|
+
|
|
463
|
+
1. **Constructor throws, execute() does not**: Validate credentials before constructing, handle `result.status` after executing
|
|
464
|
+
2. **`include-ims-credentials: true` is mandatory when using `Core.AuthClient`**: Without it, `Core.AuthClient.generateAccessToken(params)` returns nothing and `accessToken` will be blank, causing the constructor to throw
|
|
465
|
+
3. **Alternative: `AdobeAuth.getToken()` for explicit S2S credentials**: If the action uses a dedicated Events technical account with explicit credentials (rather than the App Builder runtime's own injected credentials), use `AdobeAuth.getToken(params.IMS_CLIENT_ID, ..., ['AdobeID', 'openid', 'adobeio_api', 'event_receiver_api'])` instead of `Core.AuthClient`. No `include-ims-credentials: true` needed in this case — add IMS credential inputs to YAML instead. See "Using AdobeAuth" rule for details.
|
|
466
|
+
4. **Token is generated fresh per activation**: No caching needed — each action invocation generates its own access token
|
|
467
|
+
4. **Fan-out with `Promise.all()`**: Multiple `execute()` calls are independent — one failure does not cancel others
|
|
468
|
+
5. **Subject is optional but useful**: Include it for downstream consumers that filter or route by subject
|
|
469
|
+
6. **No custom event ID needed in most cases**: Auto-generated UUID v4 is fine unless you need correlation with an existing ID
|
|
470
|
+
7. **Works with any action type**: RuntimeAction, WebhookAction, EventConsumerAction, GraphQlAction, OpenwhiskAction
|
|
471
|
+
8. **Credentials from `params`**: Always inject `IMS_ORG_ID`, `API_KEY`, `PROVIDER_ID` as action inputs in the YAML config — they are not auto-injected
|
|
472
|
+
9. **`.env` security**: Never commit credentials; ensure `.env` is in `.gitignore`
|
|
473
|
+
10. **Peer dependency**: `@adobe/aio-sdk` must be installed in your project — it provides `Core.AuthClient`
|
|
474
|
+
|
|
475
|
+
## Integration with Action Creation Rules
|
|
476
|
+
|
|
477
|
+
**This rule should be referenced in other action creation rules when event publishing is mentioned:**
|
|
478
|
+
|
|
479
|
+
### Trigger Patterns in Other Rules
|
|
480
|
+
|
|
481
|
+
When developers mention these phrases during action creation in the "Business Logic" question:
|
|
482
|
+
|
|
483
|
+
- "Publish an event after processing"
|
|
484
|
+
- "Emit a CloudEvent"
|
|
485
|
+
- "Notify via I/O Events"
|
|
486
|
+
- "Fan-out to other consumers"
|
|
487
|
+
- "Send event to Adobe I/O"
|
|
488
|
+
- "Trigger downstream processing via events"
|
|
489
|
+
- Any phrase containing "PublishEvent" or "I/O Events" + "publish"/"emit"/"send"
|
|
490
|
+
|
|
491
|
+
**Action to take:**
|
|
492
|
+
|
|
493
|
+
1. Complete the action creation first
|
|
494
|
+
2. Then inform the user: "I see you need to publish events to Adobe I/O Events. Let me help you integrate PublishEvent."
|
|
495
|
+
3. Trigger/reference the "Using PublishEvent" rule
|
|
496
|
+
4. This rule handles credentials, config changes, and code integration
|
|
497
|
+
|
|
498
|
+
**Example Flow:**
|
|
499
|
+
|
|
500
|
+
```
|
|
501
|
+
User: "Create a runtime action that processes orders and publishes an order.created event"
|
|
502
|
+
AI: ✓ Detected TypeScript project
|
|
503
|
+
AI: Creating runtime action... [generates .ts file]
|
|
504
|
+
AI: I see you need to publish events to Adobe I/O Events. Let me integrate PublishEvent.
|
|
505
|
+
AI: [Integrates PublishEvent into the action with token generation, config update, and env vars]
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## Related Rules
|
|
509
|
+
|
|
510
|
+
- **"Using AdobeAuth"** (`aio-toolkit-use-adobe-auth.mdc`) — use when `PublishEvent` needs a token from explicit S2S credentials instead of runtime-injected credentials (`Core.AuthClient`)
|