@adobe-commerce/aio-toolkit 1.0.13 → 1.0.15

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.
@@ -0,0 +1,562 @@
1
+ # AIO Toolkit: Create Event Consumer Action
2
+
3
+ **Command Name:** `aio-toolkit-create-event-consumer-action`
4
+
5
+ **Description:** Creates event consumer actions using @adobe-commerce/aio-toolkit for Adobe I/O Events with InfiniteLoopBreaker support
6
+
7
+ ## Workflow
8
+
9
+ This command creates event consumer actions that handle Adobe I/O Events with optional infinite loop prevention.
10
+
11
+ ### Step 1: Verify Prerequisites
12
+
13
+ 1. Check if `@adobe-commerce/aio-toolkit` is installed in `package.json`
14
+ - If NOT installed, ask user if they want to install it: `npm install @adobe-commerce/aio-toolkit`
15
+ 2. Detect project language (TypeScript or JavaScript)
16
+ - Check for `typescript` in dependencies + `tsconfig.json`
17
+ - Check for `.ts` files in `actions/` or `lib/`
18
+ - Default to JavaScript if ambiguous
19
+ 3. Detect project structure
20
+ - Check for `application:` in `app.config.yaml` (root actions)
21
+ - Check for `extensions:` in `app.config.yaml` (extension point actions)
22
+
23
+ ### Step 2: Collect Configuration
24
+
25
+ Ask the user:
26
+
27
+ 1. **Action Location & Organization**
28
+ - Root application - direct, existing package, or new package
29
+ - Extension point - direct, existing package, or new package
30
+ - If new package with hyphen (e.g., `commerce-product`), ask:
31
+ - Nested: `actions/commerce/product/` (provider-entity)
32
+ - Flat: `actions/commerce-product/` (single-purpose)
33
+
34
+ 2. **Event Handling Pattern**
35
+ - **Single Event Type**: One action handles one specific event
36
+ - **Multiple Event Types**: Consumer routes events to multiple sub-actions
37
+ - Note: Multiple event types always requires package structure
38
+
39
+ #### For Single Event Type:
40
+
41
+ 3. **Action Name** (required)
42
+ - If direct: `product-created`, `order-validator`
43
+ - If packaged: `created`, `updated`, `deleted`
44
+
45
+ 4. **Event Type** (required)
46
+ - Example: `com.adobe.commerce.observer.catalog_product_save_commit_after`
47
+ - Full Adobe I/O event type string
48
+
49
+ 5. **Required Parameters** (comma-separated)
50
+ - Note: `type` is always included automatically
51
+ - Additional parameters: `data.value.sku`, `data.value.email`
52
+
53
+ 6. **InfiniteLoopBreaker** (optional, recommended)
54
+ - Enable to prevent infinite event loops
55
+ - If Yes, ask:
56
+ - **Key Function Name**: e.g., `commerce-product-processing-key`
57
+ - **Fingerprint Fields**: e.g., `sku, description`
58
+ - **Event Types to Monitor**: e.g., `["com.adobe.commerce.observer.catalog_product_save_commit_after"]`
59
+ - **TTL (seconds)**: Default 60 seconds
60
+
61
+ 7. **Authentication**
62
+ - Require Adobe authentication (default: Yes)
63
+
64
+ 8. **Business Logic Description**
65
+ - What should this consumer do?
66
+
67
+ #### For Multiple Event Types:
68
+
69
+ 3. **Consumer Name** (required)
70
+ - Example: `consumer`, `product-consumer`, `order-consumer`
71
+ - Routes events from I/O Events to sub-actions
72
+
73
+ 4. **Sub-actions** (comma-separated)
74
+ - Example: `created, updated, deleted`
75
+ - These are invoked by consumer based on event type
76
+
77
+ 5. **Event Types** (comma-separated)
78
+ - Example: `com.adobe.commerce.observer.catalog_product_save_commit_after, com.adobe.commerce.observer.catalog_product_delete_commit_after`
79
+
80
+ 6. **Required Parameters** (comma-separated)
81
+ - Note: `type` is always included
82
+ - Additional parameters needed for routing/processing
83
+
84
+ 7. **Event Routing Mapping**
85
+ - Map each event type to its sub-action
86
+ - Example: `catalog_product_save_commit_after` → `created`
87
+ - Example: `catalog_product_delete_commit_after` → `deleted`
88
+
89
+ 8. **InfiniteLoopBreaker** (optional)
90
+ - Same sub-questions as single event type
91
+
92
+ 9. **Authentication**
93
+ - Require Adobe authentication (default: Yes)
94
+ - Applies to consumer and all sub-actions
95
+
96
+ 10. **Business Logic**
97
+ - Describe what each sub-action should do
98
+
99
+ ### Step 3: Confirm Configuration
100
+
101
+ Display summary:
102
+
103
+ ```
104
+ 📋 Event Consumer Action Configuration
105
+
106
+ Pattern: [Single Event Type / Multiple Event Types]
107
+ Language: [JavaScript/TypeScript] (auto-detected)
108
+ Location: [Root/Extension]
109
+ Package: [package-name or direct]
110
+
111
+ [If Single Event Type]
112
+ Action Name: [action-name]
113
+ Event Type: [event-type]
114
+ Required Parameters: [params]
115
+ InfiniteLoopBreaker: [Enabled/Disabled]
116
+ [If enabled]
117
+ Key Function: [key-name]
118
+ Fingerprint Fields: [fields]
119
+ Monitored Events: [event-types]
120
+ TTL: [seconds]s
121
+
122
+ [If Multiple Event Types]
123
+ Package: [package-name]
124
+ Consumer: [consumer-name]
125
+ Sub-actions: [action1, action2, action3]
126
+ Event Types: [event-types]
127
+ Event Routing:
128
+ - [event-type-1] → [sub-action-1]
129
+ - [event-type-2] → [sub-action-2]
130
+ InfiniteLoopBreaker: [Enabled/Disabled]
131
+
132
+ Authentication: [Required/Not Required]
133
+ Web Access: No (always for event consumers)
134
+ Business Logic: [description]
135
+
136
+ ✅ Files to Create:
137
+ [If Single Event Type]
138
+ - actions/[path]/[action-name]/index.[js/ts]
139
+ [- actions/[package]/actions.config.yaml if packaged]
140
+ - Update app.config.yaml or ext.config.yaml
141
+
142
+ [If Multiple Event Types]
143
+ - actions/[package]/[consumer-name]/index.[js/ts]
144
+ - actions/[package]/[sub-action1]/index.[js/ts]
145
+ - actions/[package]/[sub-action2]/index.[js/ts]
146
+ - actions/[package]/actions.config.yaml
147
+ - Update app.config.yaml or ext.config.yaml
148
+
149
+ Should I proceed?
150
+ ```
151
+
152
+ ### Step 4: Generate Event Consumer Action
153
+
154
+ **Directory Structures:**
155
+
156
+ **Single Event Type - No Package:**
157
+ ```
158
+ actions/
159
+ └── [action-name]/
160
+ └── index.[js/ts]
161
+ ```
162
+
163
+ **Single Event Type - With Package:**
164
+ ```
165
+ actions/
166
+ └── [package]/
167
+ ├── actions.config.yaml
168
+ └── [action-name]/
169
+ └── index.[js/ts]
170
+ ```
171
+
172
+ **Multiple Event Types:**
173
+ ```
174
+ actions/
175
+ └── [package]/
176
+ ├── actions.config.yaml
177
+ ├── [consumer-name]/
178
+ │ └── index.[js/ts]
179
+ ├── [sub-action1]/
180
+ │ └── index.[js/ts]
181
+ └── [sub-action2]/
182
+ └── index.[js/ts]
183
+ ```
184
+
185
+ #### Single Event Type Consumer Template
186
+
187
+ **JavaScript:**
188
+
189
+ ```javascript
190
+ const {
191
+ EventConsumerAction,
192
+ RuntimeActionResponse,
193
+ HttpStatus,
194
+ InfiniteLoopBreaker, // If enabled
195
+ } = require('@adobe-commerce/aio-toolkit');
196
+ const name = '[action-name]';
197
+
198
+ exports.main = EventConsumerAction.execute(
199
+ name,
200
+ ['type' /* , other required params */],
201
+ [],
202
+ async (params, ctx) => {
203
+ const { logger } = ctx;
204
+ logger.info({ message: `${name}-received`, params: JSON.stringify(params) });
205
+
206
+ // InfiniteLoopBreaker check (if enabled)
207
+ const isLoop = await InfiniteLoopBreaker.isInfiniteLoop({
208
+ keyFn: '[key-function-name]',
209
+ fingerprintFn: () => ({ sku: params.data.value.sku /* fingerprint fields */ }),
210
+ eventTypes: ['[event-type]'],
211
+ event: params.type,
212
+ });
213
+ if (isLoop) {
214
+ logger.info(`Infinite loop detected for event ${params.type}`);
215
+ return RuntimeActionResponse.success(
216
+ `event discarded to prevent infinite loop(${params.type})`
217
+ );
218
+ }
219
+
220
+ try {
221
+ // TODO: Implement business logic
222
+
223
+ logger.info({ message: `${name}-success` });
224
+
225
+ // Store fingerprint (if InfiniteLoopBreaker enabled)
226
+ await InfiniteLoopBreaker.storeFingerPrint(
227
+ '[key-function-name]',
228
+ () => ({ sku: params.data.value.sku /* fingerprint fields */ }),
229
+ 60 // TTL in seconds
230
+ );
231
+
232
+ return RuntimeActionResponse.success({
233
+ success: true,
234
+ message: 'Event consumed successfully',
235
+ });
236
+ } catch (error) {
237
+ logger.error({ message: `${name}-error`, error: error.message, stack: error.stack });
238
+ return RuntimeActionResponse.error(
239
+ HttpStatus.INTERNAL_ERROR,
240
+ `Failed to process event: ${error.message}`
241
+ );
242
+ }
243
+ }
244
+ );
245
+ ```
246
+
247
+ **TypeScript:** Same with type annotations and `import` syntax
248
+
249
+ #### Multiple Event Types Templates
250
+
251
+ **Consumer (JavaScript):**
252
+
253
+ ```javascript
254
+ const {
255
+ EventConsumerAction,
256
+ RuntimeActionResponse,
257
+ Openwhisk,
258
+ HttpStatus,
259
+ InfiniteLoopBreaker, // If enabled
260
+ } = require('@adobe-commerce/aio-toolkit');
261
+ const name = '[package-name]-[consumer-name]';
262
+
263
+ exports.main = EventConsumerAction.execute(
264
+ name,
265
+ ['type' /* , other required params */],
266
+ [],
267
+ async (params, ctx) => {
268
+ const { logger } = ctx;
269
+ const openwhisk = new Openwhisk(params.API_HOST, params.API_AUTH);
270
+ logger.info(`Event type received: ${params.type}`);
271
+
272
+ // InfiniteLoopBreaker check (if enabled)
273
+ const isLoop = await InfiniteLoopBreaker.isInfiniteLoop({
274
+ keyFn: '[key-function-name]',
275
+ fingerprintFn: () => ({ /* fingerprint fields */ }),
276
+ eventTypes: ['[event-type-1]', '[event-type-2]'],
277
+ event: params.type,
278
+ });
279
+ if (isLoop) {
280
+ return RuntimeActionResponse.success('event discarded to prevent infinite loop');
281
+ }
282
+
283
+ let response, statusCode;
284
+
285
+ // Route to sub-action based on event type
286
+ switch (params.type) {
287
+ case '[event-type-1]':
288
+ logger.info('Invoking [sub-action-1]');
289
+ const res1 = await openwhisk.execute(
290
+ '[package-name]/[sub-action-1]',
291
+ params.data.value
292
+ );
293
+ response = res1?.response?.result?.body;
294
+ statusCode = res1?.response?.result?.statusCode;
295
+ break;
296
+
297
+ case '[event-type-2]':
298
+ logger.info('Invoking [sub-action-2]');
299
+ const res2 = await openwhisk.execute(
300
+ '[package-name]/[sub-action-2]',
301
+ params.data.value
302
+ );
303
+ response = res2?.response?.result?.body;
304
+ statusCode = res2?.response?.result?.statusCode;
305
+ break;
306
+
307
+ default:
308
+ return RuntimeActionResponse.error(
309
+ HttpStatus.BAD_REQUEST,
310
+ `Unsupported event type: ${params.type}`
311
+ );
312
+ }
313
+
314
+ if (!response.success) {
315
+ return RuntimeActionResponse.error(statusCode, response.error);
316
+ }
317
+
318
+ // Store fingerprint (if InfiniteLoopBreaker enabled)
319
+ await InfiniteLoopBreaker.storeFingerPrint(
320
+ '[key-function-name]',
321
+ () => ({ /* fingerprint fields */ }),
322
+ 60 // TTL
323
+ );
324
+
325
+ return RuntimeActionResponse.success(response);
326
+ }
327
+ );
328
+ ```
329
+
330
+ **Sub-action (JavaScript):**
331
+
332
+ ```javascript
333
+ const {
334
+ OpenwhiskAction,
335
+ RuntimeActionResponse,
336
+ HttpStatus,
337
+ } = require('@adobe-commerce/aio-toolkit');
338
+ const name = '[sub-action-name]';
339
+
340
+ exports.main = OpenwhiskAction.execute(name, async (params, ctx) => {
341
+ const { logger } = ctx;
342
+ logger.info({ message: `${name}-processing`, params: JSON.stringify(params) });
343
+
344
+ try {
345
+ // TODO: Implement sub-action business logic
346
+
347
+ logger.info({ message: `${name}-success` });
348
+ return RuntimeActionResponse.success({
349
+ success: true,
350
+ message: 'Processed successfully',
351
+ });
352
+ } catch (error) {
353
+ logger.error({ message: `${name}-error`, error: error.message, stack: error.stack });
354
+ return RuntimeActionResponse.error(
355
+ HttpStatus.INTERNAL_ERROR,
356
+ `Failed to process: ${error.message}`
357
+ );
358
+ }
359
+ });
360
+ ```
361
+
362
+ **TypeScript:** Same with type annotations and `import` syntax
363
+
364
+ ### Step 5: Update Configuration Files
365
+
366
+ #### Action Configuration
367
+
368
+ **Single Event Type - Direct in config:**
369
+
370
+ ```yaml
371
+ [action-name]:
372
+ function: actions/[action-name]/index.[js/ts]
373
+ web: 'no'
374
+ runtime: nodejs:22
375
+ inputs:
376
+ LOG_LEVEL: debug
377
+ annotations:
378
+ require-adobe-auth: [true/false]
379
+ final: true
380
+ ```
381
+
382
+ **Single Event Type - Packaged:**
383
+
384
+ Create `actions/[package]/actions.config.yaml`:
385
+ ```yaml
386
+ [action-name]:
387
+ function: [action-name]/index.[js/ts]
388
+ web: 'no'
389
+ runtime: nodejs:22
390
+ inputs:
391
+ LOG_LEVEL: debug
392
+ annotations:
393
+ require-adobe-auth: [true/false]
394
+ final: true
395
+ ```
396
+
397
+ Reference in `app.config.yaml` or `ext.config.yaml`:
398
+ ```yaml
399
+ runtimeManifest:
400
+ packages:
401
+ [package-name]:
402
+ license: Apache-2.0
403
+ actions:
404
+ $include: ./actions/[package]/actions.config.yaml
405
+ ```
406
+
407
+ **Multiple Event Types:**
408
+
409
+ Create `actions/[package]/actions.config.yaml`:
410
+ ```yaml
411
+ [consumer-name]:
412
+ function: [consumer-name]/index.[js/ts]
413
+ web: 'no'
414
+ runtime: nodejs:22
415
+ inputs:
416
+ LOG_LEVEL: debug
417
+ annotations:
418
+ require-adobe-auth: [true/false]
419
+ final: true
420
+
421
+ [sub-action-1]:
422
+ function: [sub-action-1]/index.[js/ts]
423
+ web: 'no'
424
+ runtime: nodejs:22
425
+ inputs:
426
+ LOG_LEVEL: debug
427
+ annotations:
428
+ require-adobe-auth: [true/false]
429
+ final: true
430
+
431
+ [sub-action-2]:
432
+ function: [sub-action-2]/index.[js/ts]
433
+ web: 'no'
434
+ runtime: nodejs:22
435
+ inputs:
436
+ LOG_LEVEL: debug
437
+ annotations:
438
+ require-adobe-auth: [true/false]
439
+ final: true
440
+ ```
441
+
442
+ ### Step 6: Completion
443
+
444
+ Display:
445
+
446
+ ```
447
+ ✅ Event Consumer Action Created Successfully!
448
+
449
+ 📁 Files Created:
450
+ [If Single Event Type]
451
+ - actions/[path]/index.[js/ts]
452
+ [- actions/[package]/actions.config.yaml if packaged]
453
+
454
+ [If Multiple Event Types]
455
+ - actions/[package]/[consumer-name]/index.[js/ts]
456
+ - actions/[package]/[sub-action1]/index.[js/ts]
457
+ - actions/[package]/[sub-action2]/index.[js/ts]
458
+ - actions/[package]/actions.config.yaml
459
+
460
+ 📝 Configuration Updated:
461
+ - app.config.yaml or ext.config.yaml
462
+
463
+ 🚀 Next Steps:
464
+ 1. Implement business logic in consumer/sub-actions
465
+ 2. Register action with Adobe I/O Events in Adobe Developer Console
466
+ 3. Set up event providers and subscriptions
467
+ 4. Test locally: aio app dev
468
+ 5. Deploy: aio app deploy
469
+ 6. Test with real events from Adobe I/O
470
+
471
+ 📖 Documentation:
472
+ - EventConsumerAction: @adobe-commerce/aio-toolkit
473
+ - Adobe I/O Events: https://developer.adobe.com/events/
474
+
475
+ ⚙️ Event Registration:
476
+ [If Single Event Type]
477
+ - Register: [action-name] with Adobe I/O Events
478
+ - Event Type: [event-type]
479
+
480
+ [If Multiple Event Types]
481
+ - Register: [consumer-name] only (not sub-actions)
482
+ - Event Types: [all event types]
483
+ - Consumer routes to sub-actions internally
484
+
485
+ 🔁 InfiniteLoopBreaker:
486
+ [If enabled]
487
+ - Prevents infinite event loops
488
+ - Key Function: [key-function-name]
489
+ - Fingerprint: [fingerprint-fields]
490
+ - TTL: [ttl] seconds
491
+ - Monitors: [event-types]
492
+ ```
493
+
494
+ ### Key Features
495
+
496
+ - **Auto-detection**: Language (TS/JS) and project structure
497
+ - **Two Patterns**: Single event type (simple) or multiple event types (consumer + sub-actions)
498
+ - **InfiniteLoopBreaker**: Prevents infinite event loops with fingerprint-based detection
499
+ - **Package Organization**: Flat or nested structures (e.g., `commerce-product` → `commerce/product/`)
500
+ - **Event Routing**: Consumer uses Openwhisk to invoke sub-actions based on event type
501
+ - **Web Access**: Always `web: 'no'` (internal only, not web-accessible)
502
+ - **Best Practices**: Structured logging, error handling, telemetry-ready
503
+
504
+ ### Action Classes
505
+
506
+ **EventConsumerAction.execute(name, requiredParams, requiredHeaders, actionFn)**
507
+ - Used for all consumers (single and multi-event)
508
+ - Receives events from Adobe I/O Events
509
+ - Provides validation, logging, telemetry, error handling
510
+ - Required params always include `"type"`
511
+
512
+ **OpenwhiskAction.execute(name, actionFn)**
513
+ - Used for sub-actions in multiple event type pattern
514
+ - Invoked by consumer, not directly by I/O Events
515
+ - Provides logging, telemetry, error handling
516
+ - No HTTP method or parameter validation
517
+
518
+ **Openwhisk Class**
519
+ - Constructor: `new Openwhisk(params.API_HOST, params.API_AUTH)`
520
+ - Method: `execute(actionName, params)` - Invokes another action
521
+ - Returns: `{ response: { result: { statusCode, body } } }`
522
+ - Used by consumer to route events to sub-actions
523
+
524
+ **InfiniteLoopBreaker Class**
525
+ - `isInfiniteLoop(config)` - Checks if event is part of infinite loop
526
+ - `keyFn`: Unique key function name for this loop detection
527
+ - `fingerprintFn`: Function returning fingerprint object (e.g., `{ sku, description }`)
528
+ - `eventTypes`: Array of event types to monitor
529
+ - `event`: Current event type from params.type
530
+ - Returns: `true` if loop detected, `false` otherwise
531
+ - `storeFingerPrint(keyFn, fingerprintFn, ttl)` - Stores fingerprint after successful processing
532
+ - `keyFn`: Same key function name used in isInfiniteLoop
533
+ - `fingerprintFn`: Same fingerprint function
534
+ - `ttl`: Time-to-live in seconds (default: 60)
535
+
536
+ ### Package Organization
537
+
538
+ **Flat Structure** (single-purpose):
539
+ - `order-management` → `actions/order-management/`
540
+ - Simple, all actions related to one entity
541
+
542
+ **Nested Structure** (provider-entity):
543
+ - `commerce-product` → `actions/commerce/product/`
544
+ - `sap-order` → `actions/sap/order/`
545
+ - Clear separation of provider and entity
546
+
547
+ ### Important Notes
548
+
549
+ 1. **Web Access**: Event consumers are ALWAYS `web: 'no'` (not web-accessible)
550
+ 2. **Required Parameters**: Always include `"type"` for event type identification
551
+ 3. **Event Registration**:
552
+ - Single event type: Register the action itself
553
+ - Multiple event types: Register only consumer (not sub-actions)
554
+ 4. **InfiniteLoopBreaker**: Essential when action might trigger the same event it's consuming
555
+ 5. **Error Handling**: Return success even for expected errors to avoid retries
556
+ 6. **Routing**: Consumer is lightweight, only routes; sub-actions do the heavy processing
557
+ 7. **Sub-action Reusability**: Sub-actions can be reused by different consumers
558
+
559
+ ### Related Rules
560
+
561
+ - **Setting up New Relic Telemetry**: Add observability to your event consumer
562
+