@adobe-commerce/aio-toolkit 1.0.0

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/README.md ADDED
@@ -0,0 +1,658 @@
1
+ # @adobe-commerce/aio-toolkit
2
+
3
+ [![npm version](https://badge.fury.io/js/@adobe-commerce%2Faio-toolkit.svg)](https://www.npmjs.com/package/@adobe-commerce/aio-toolkit)
4
+ [![License](https://img.shields.io/badge/license-SEE%20LICENSE%20IN%20LICENSE-blue.svg)](LICENSE)
5
+
6
+ ## Overview
7
+
8
+ 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.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @adobe-commerce/aio-toolkit
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ The toolkit is organized into four main modules:
19
+
20
+ ### 🛠️ Framework Components
21
+
22
+ **Core infrastructure for Adobe App Builder applications**
23
+
24
+ #### `RuntimeAction`
25
+ HTTP request handling and business logic execution for Adobe I/O Runtime.
26
+
27
+ ```typescript
28
+ const {
29
+ RuntimeAction,
30
+ RuntimeActionResponse,
31
+ HttpMethod,
32
+ HttpStatus,
33
+ } = require('@adobe-commerce/aio-toolkit');
34
+
35
+ // Create a simple action
36
+ const myAction = RuntimeAction.execute(
37
+ 'process-user', // Action name
38
+ [HttpMethod.POST], // Allowed HTTP methods
39
+ ['userId'], // Required parameters
40
+ ['authorization'], // Required headers
41
+ async (params, ctx) => {
42
+ // Your business logic here
43
+ const { userId } = params;
44
+ const { logger } = ctx;
45
+
46
+ logger.info(`Processing request for user: ${userId}`);
47
+
48
+ // Return success response
49
+ return RuntimeActionResponse.success({
50
+ message: 'Action completed successfully',
51
+ userId: userId,
52
+ });
53
+ }
54
+ );
55
+
56
+ // Export for Adobe I/O Runtime
57
+ exports.main = myAction;
58
+ ```
59
+
60
+ #### `EventConsumerAction`
61
+ Event-driven processing for Adobe I/O Events with automatic validation.
62
+
63
+ ```typescript
64
+ const {
65
+ EventConsumerAction,
66
+ RuntimeActionResponse,
67
+ HttpStatus
68
+ } = require('@adobe-commerce/aio-toolkit');
69
+
70
+ // Create a simple event consumer
71
+ const myEventConsumer = EventConsumerAction.execute(
72
+ 'commerce-event-handler',
73
+ ['eventType'], // Required parameters
74
+ ['x-adobe-signature'], // Required headers
75
+ async (params, ctx) => {
76
+ const { eventType } = params;
77
+ const { logger } = ctx;
78
+
79
+ logger.info(`Processing event: ${eventType}`);
80
+
81
+ // Process the event
82
+ await processCommerceEvent(eventType, params);
83
+
84
+ // Return success response
85
+ return RuntimeActionResponse.success({
86
+ message: 'Event processed successfully',
87
+ eventType: eventType,
88
+ processedAt: new Date().toISOString()
89
+ });
90
+ }
91
+ );
92
+
93
+ // Export for Adobe I/O Runtime
94
+ exports.main = myEventConsumer;
95
+ ```
96
+
97
+ #### `GraphQlAction`
98
+ GraphQL server implementation with schema validation and introspection control.
99
+
100
+ ```typescript
101
+ const { GraphQlAction } = require('@adobe-commerce/aio-toolkit');
102
+
103
+ const schema = `
104
+ type Query {
105
+ hello: String
106
+ user(id: ID!): User
107
+ }
108
+
109
+ type User {
110
+ id: ID!
111
+ name: String!
112
+ email: String!
113
+ }
114
+ `;
115
+
116
+ const resolvers = async ctx => {
117
+ const { logger } = ctx;
118
+
119
+ return {
120
+ hello: () => 'Hello, World!',
121
+ user: ({ id }) => {
122
+ logger.debug(`Fetching user: ${id}`);
123
+ return {
124
+ id,
125
+ name: 'John Doe',
126
+ email: 'john@example.com'
127
+ };
128
+ }
129
+ };
130
+ };
131
+
132
+ // Create and export GraphQL endpoint
133
+ exports.main = GraphQlAction.execute(schema, resolvers);
134
+ ```
135
+
136
+ #### `OpenWhisk` & `OpenWhiskAction`
137
+ OpenWhisk client for serverless action invocation and integration. OpenWhisk action wrapper with enhanced logging and error handling.
138
+
139
+ ```typescript
140
+ const { OpenWhisk, OpenwhiskAction, RuntimeActionResponse } = require('@adobe-commerce/aio-toolkit');
141
+
142
+ // Invoke another action
143
+ const invokeAction = async (params) => {
144
+ try {
145
+ // Initialize OpenWhisk client
146
+ const openwhisk = new Openwhisk(params.API_HOST, params.API_AUTH)
147
+
148
+ const result = await openwhisk.execute('hello-world', {
149
+ name: 'World'
150
+ });
151
+
152
+ console.log('Action result:', result);
153
+ return result;
154
+ } catch (error) {
155
+ console.error('Action invocation failed:', error);
156
+ throw error;
157
+ }
158
+ };
159
+
160
+ // Simple action with logging
161
+ const helloWorldAction = OpenwhiskAction.execute('hello-world', async (params, ctx) => {
162
+ const { logger } = ctx;
163
+ const { name = 'World' } = params;
164
+
165
+ logger.info(`Greeting request for: ${name}`);
166
+
167
+ const message = `Hello, ${name}!`;
168
+
169
+ logger.info(`Generated greeting: ${message}`);
170
+
171
+ return RuntimeActionResponse.success({
172
+ message,
173
+ timestamp: new Date().toISOString(),
174
+ action: 'hello-world'
175
+ });
176
+ });
177
+
178
+ // Export for Adobe I/O Runtime
179
+ exports.main = helloWorldAction;
180
+ ```
181
+
182
+ #### `FileRepository`
183
+ File-based storage with CRUD operations for Adobe I/O Runtime applications.
184
+
185
+ ```typescript
186
+ const { FileRepository } = require('@adobe-commerce/aio-toolkit');
187
+
188
+ // Initialize file repository
189
+ const orderRepository = new FileRepository('orders');
190
+ const customerRepository = new FileRepository('customers');
191
+
192
+ // Save order data
193
+ const saveOrder = async (orderData) => {
194
+ const orderId = orderData.orderId;
195
+
196
+ const success = await orderRepository.save(orderId, {
197
+ ...orderData,
198
+ createdAt: new Date().toISOString(),
199
+ status: 'pending'
200
+ });
201
+
202
+ if (success) {
203
+ console.log(`Order ${orderId} saved successfully`);
204
+ } else {
205
+ throw new Error(`Failed to save order ${orderId}`);
206
+ }
207
+ };
208
+
209
+ // Load order data
210
+ const getOrder = async (orderId) => {
211
+ const orderData = await orderRepository.load(orderId);
212
+
213
+ if (Object.keys(orderData).length === 0) {
214
+ throw new Error(`Order ${orderId} not found`);
215
+ }
216
+
217
+ return orderData;
218
+ };
219
+
220
+ // List all orders
221
+ const getAllOrders = async () => {
222
+ const orderFiles = await orderRepository.list();
223
+ const orders = [];
224
+
225
+ for (const fileName of orderFiles) {
226
+ const orderId = fileName.replace('.json', '');
227
+ const orderData = await orderRepository.load(orderId);
228
+ orders.push({ id: orderId, ...orderData });
229
+ }
230
+
231
+ return orders;
232
+ };
233
+
234
+ // Use in a runtime action
235
+ const orderManagerAction = RuntimeAction.execute(
236
+ 'order-manager',
237
+ ['action', 'orderId'],
238
+ [],
239
+ async (params, ctx) => {
240
+ const { action, orderId } = params;
241
+ const { logger } = ctx;
242
+
243
+ logger.info(`Order management action: ${action} for ${orderId}`);
244
+
245
+ switch (action) {
246
+ case 'get':
247
+ const order = await getOrder(orderId);
248
+ return RuntimeActionResponse.success({ order });
249
+
250
+ case 'save':
251
+ await saveOrder(params.orderData);
252
+ return RuntimeActionResponse.success({
253
+ message: `Order ${orderId} saved`
254
+ });
255
+
256
+ case 'list':
257
+ const orders = await getAllOrders();
258
+ return RuntimeActionResponse.success({ orders });
259
+
260
+ case 'delete':
261
+ const deletedOrders = await orderRepository.delete([orderId]);
262
+ return RuntimeActionResponse.success({
263
+ deleted: deletedOrders.length > 0
264
+ });
265
+
266
+ default:
267
+ return RuntimeActionResponse.error(
268
+ HttpStatus.BAD_REQUEST,
269
+ `Unknown action: ${action}`
270
+ );
271
+ }
272
+ }
273
+ );
274
+
275
+ exports.main = orderManagerAction;
276
+ ```
277
+
278
+ ### 🏪 Commerce Components
279
+
280
+ **Adobe Commerce API integration and authentication**
281
+
282
+ #### `AdobeAuth`
283
+ Adobe IMS authentication and token management.
284
+
285
+ ```typescript
286
+ const { AdobeAuth } = require('@adobe-commerce/aio-toolkit');
287
+
288
+ // Get authentication token
289
+ const token = await AdobeAuth.getToken(
290
+ 'your-client-id',
291
+ 'your-client-secret',
292
+ 'your-technical-account-id',
293
+ 'your-technical-account-email',
294
+ 'your-ims-org-id',
295
+ ['AdobeID', 'openid', 'adobeio_api'] // Scopes
296
+ );
297
+
298
+ console.log('Authentication token:', token);
299
+ ```
300
+
301
+ #### `AdobeCommerceClient`
302
+ HTTP client for Adobe Commerce API integration with multiple authentication methods.
303
+
304
+ ```typescript
305
+ const { AdobeCommerceClient } = require('@adobe-commerce/aio-toolkit');
306
+ const { Oauth1aConnection } = require('@adobe-commerce/aio-toolkit');
307
+
308
+ const connection = new Oauth1aConnection(
309
+ 'consumer-key',
310
+ 'consumer-secret',
311
+ 'access-token',
312
+ 'access-token-secret',
313
+ logger? // Optional custom logger
314
+ );
315
+
316
+ // Create client
317
+ const client = new AdobeCommerceClient('https://your-commerce-store.com', connection);
318
+
319
+ // Make API calls
320
+ const products = await client.get('rest/V1/products');
321
+ const newProduct = await client.post('rest/V1/products', {}, productData);
322
+ ```
323
+
324
+ ### 🔗 Integration Components
325
+
326
+ **External API integration and utility functions**
327
+
328
+ #### `RestClient`
329
+ HTTP client for external API integration.
330
+
331
+ ```typescript
332
+ const { RestClient } = require('@adobe-commerce/aio-toolkit');
333
+
334
+ const client = new RestClient();
335
+ const response = await client.get('https://api.example.com/data', {
336
+ 'Authorization': 'Bearer token',
337
+ 'Content-Type': 'application/json'
338
+ });
339
+ ```
340
+
341
+ #### `BearerToken`
342
+ Bearer token extraction and JWT analysis utility.
343
+
344
+ ```typescript
345
+ const { BearerToken } = require('@adobe-commerce/aio-toolkit');
346
+
347
+ const tokenInfo = BearerToken.extract(params);
348
+ if (tokenInfo?.token) {
349
+ console.log('Token expires at:', tokenInfo.expiresAt);
350
+ console.log('Is expired:', tokenInfo.isExpired);
351
+ }
352
+ ```
353
+
354
+ #### `InfiniteLoopBreaker`
355
+ Detect and prevent infinite loops in event-driven applications.
356
+
357
+ ```typescript
358
+ const { InfiniteLoopBreaker } = require('@adobe-commerce/aio-toolkit');
359
+
360
+ // Basic infinite loop detection
361
+ const isLoop = await InfiniteLoopBreaker.isInfiniteLoop({
362
+ keyFn: 'order-processing-key',
363
+ fingerprintFn: orderData,
364
+ eventTypes: ['order.created', 'order.updated'],
365
+ event: 'order.created'
366
+ });
367
+
368
+ if (isLoop) {
369
+ console.log('Infinite loop detected, skipping processing');
370
+ return;
371
+ }
372
+
373
+ // Process the event
374
+ await processOrderEvent(orderData);
375
+
376
+ // Store fingerprint to prevent future loops
377
+ await InfiniteLoopBreaker.storeFingerPrint(
378
+ 'order-processing-key',
379
+ orderData,
380
+ 300 // 5 minutes TTL
381
+ );
382
+ ```
383
+
384
+ #### `OnboardEvents`
385
+ Complete onboarding orchestration for Adobe I/O Events (providers, metadata, and registrations).
386
+
387
+ ```typescript
388
+ const { OnboardEvents } = require('@adobe-commerce/aio-toolkit');
389
+
390
+ const onboardEvents = new OnboardEvents(
391
+ 'My E-commerce Store',
392
+ process.env.ADOBE_CONSUMER_ID!,
393
+ process.env.ADOBE_PROJECT_ID!,
394
+ process.env.ADOBE_WORKSPACE_ID!,
395
+ process.env.ADOBE_API_KEY!,
396
+ process.env.ADOBE_ACCESS_TOKEN!
397
+ );
398
+
399
+ const basicOnboardingExample = async () => {
400
+ const input = {
401
+ providers: [
402
+ {
403
+ key: 'ecommerce-provider',
404
+ label: 'E-commerce Events Provider',
405
+ description: 'Provider for e-commerce platform events',
406
+ docsUrl: 'https://docs.mystore.com/events',
407
+ registrations: [
408
+ {
409
+ key: 'order-events',
410
+ label: 'Order Events Registration',
411
+ description: 'Registration for order-related events',
412
+ events: [
413
+ {
414
+ eventCode: 'com.mystore.order.placed',
415
+ runtimeAction: 'mystore/order-placed-handler',
416
+ deliveryType: 'webhook',
417
+ sampleEventTemplate: {
418
+ orderId: 'ord_123456',
419
+ customerId: 'cust_789',
420
+ totalAmount: 99.99,
421
+ currency: 'USD',
422
+ status: 'placed',
423
+ timestamp: '2023-01-01T12:00:00Z'
424
+ }
425
+ },
426
+ {
427
+ eventCode: 'com.mystore.order.shipped',
428
+ runtimeAction: 'mystore/order-shipped-handler',
429
+ deliveryType: 'journal',
430
+ sampleEventTemplate: {
431
+ orderId: 'ord_123456',
432
+ trackingNumber: 'TN123456789',
433
+ carrier: 'UPS',
434
+ shippedAt: '2023-01-02T10:00:00Z'
435
+ }
436
+ }
437
+ ]
438
+ }
439
+ ]
440
+ }
441
+ ]
442
+ };
443
+
444
+ try {
445
+ const result = await onboardEvents.process(input);
446
+
447
+ console.log('Onboarding completed successfully!');
448
+ console.log(`Providers: ${result.createdProviders.length} processed`);
449
+ console.log(`Events: ${result.createdEvents.length} processed`);
450
+ console.log(`Registrations: ${result.createdRegistrations.length} processed`);
451
+
452
+ // Check results
453
+ result.createdProviders.forEach(provider => {
454
+ if (provider.created) {
455
+ console.log(`✅ Created provider: ${provider.provider.label}`);
456
+ } else if (provider.skipped) {
457
+ console.log(`⏭️ Skipped existing provider: ${provider.provider.label}`);
458
+ } else {
459
+ console.log(`❌ Failed to create provider: ${provider.error}`);
460
+ }
461
+ });
462
+
463
+ return result;
464
+ } catch (error) {
465
+ console.error('Onboarding failed:', error.message);
466
+ return null;
467
+ }
468
+ };
469
+
470
+ basicOnboardingExample();
471
+ ```
472
+
473
+ ### 📡 I/O Events Components
474
+
475
+ **Adobe I/O Events management and orchestration**
476
+
477
+ #### `EventMetadataManager`
478
+ Manage event metadata for Adobe I/O Events providers.
479
+
480
+ ```typescript
481
+ const { EventMetadataManager } = require('@adobe-commerce/aio-toolkit');
482
+
483
+ const manager = new EventMetadataManager({
484
+ apiKey: 'your-api-key',
485
+ accessToken: 'your-access-token',
486
+ consumerId: 'your-consumer-id',
487
+ projectId: 'your-project-id',
488
+ workspaceId: 'your-workspace-id'
489
+ });
490
+
491
+ // Create event metadata
492
+ const eventMetadata = await manager.create({
493
+ providerId: 'your-provider-id',
494
+ eventCode: 'commerce.order.created',
495
+ label: 'Order Created',
496
+ description: 'Triggered when a new order is created in the commerce system',
497
+ sampleEventTemplate: {
498
+ orderId: 'ORD-123456',
499
+ customerId: 'CUST-789',
500
+ amount: 199.99,
501
+ currency: 'USD',
502
+ items: [
503
+ {
504
+ sku: 'PRODUCT-001',
505
+ name: 'Premium Widget',
506
+ quantity: 2,
507
+ price: 99.99
508
+ }
509
+ ],
510
+ timestamp: '2023-12-01T10:30:00Z'
511
+ }
512
+ });
513
+
514
+ // Get event metadata
515
+ const metadata = await manager.get('your-provider-id', 'commerce.order.created');
516
+
517
+ // List all event metadata for a provider
518
+ const allMetadata = await manager.list('your-provider-id');
519
+
520
+ // Delete specific event metadata
521
+ await manager.delete('your-provider-id', 'commerce.order.created');
522
+
523
+ // Delete all event metadata for a provider
524
+ await manager.delete('your-provider-id');
525
+ ```
526
+
527
+ #### `ProviderManager`
528
+ Manage event providers for Adobe I/O Events.
529
+
530
+ ```typescript
531
+ const { ProviderManager } = require('@adobe-commerce/aio-toolkit');
532
+
533
+ const manager = new ProviderManager({
534
+ apiKey: 'your-api-key',
535
+ accessToken: 'your-access-token',
536
+ consumerId: 'your-consumer-id',
537
+ projectId: 'your-project-id',
538
+ workspaceId: 'your-workspace-id'
539
+ });
540
+
541
+ // Create a new provider
542
+ const provider = await manager.create({
543
+ label: 'E-commerce Events Provider',
544
+ description: 'Provides events from our e-commerce platform including orders, customers, products, and inventory updates',
545
+ docsUrl: 'https://docs.mystore.com/events-api',
546
+ providerMetadata: {
547
+ region: 'us-east-1',
548
+ environment: 'production',
549
+ version: '2.1.0'
550
+ }
551
+ });
552
+
553
+ // Get provider details
554
+ const existingProvider = await manager.get('your-provider-id', {
555
+ eventmetadata: true // Include event metadata in response
556
+ });
557
+
558
+ // List all providers
559
+ const providers = await manager.list({
560
+ eventmetadata: true, // Include event metadata
561
+ providerMetadataIds: ['provider-1', 'provider-2'] // Filter by specific providers
562
+ });
563
+
564
+ // List providers with advanced filtering
565
+ const filteredProviders = await manager.list({
566
+ instanceId: 'your-instance-id',
567
+ providerMetadataId: 'specific-provider-metadata-id'
568
+ });
569
+
570
+ // Delete provider
571
+ await manager.delete('your-provider-id');
572
+ ```
573
+
574
+ #### `RegistrationManager`
575
+ Manage event registrations and subscriptions for Adobe I/O Events.
576
+
577
+ ```typescript
578
+ const { RegistrationManager } = require('@adobe-commerce/aio-toolkit');
579
+
580
+ const manager = new RegistrationManager({
581
+ apiKey: 'your-api-key',
582
+ accessToken: 'your-access-token',
583
+ consumerId: 'your-consumer-id',
584
+ projectId: 'your-project-id',
585
+ workspaceId: 'your-workspace-id'
586
+ });
587
+
588
+ // Create event registration
589
+ const registration = await manager.create({
590
+ name: 'Order Processing Registration',
591
+ description: 'Handles order-related events',
592
+ delivery: {
593
+ type: 'webhook',
594
+ url: 'https://your-app.com/webhook/orders'
595
+ },
596
+ eventsOfInterest: [
597
+ {
598
+ providerId: 'your-provider-id',
599
+ eventCode: 'commerce.order.created'
600
+ },
601
+ {
602
+ providerId: 'your-provider-id',
603
+ eventCode: 'commerce.order.updated'
604
+ }
605
+ ]
606
+ });
607
+
608
+ // Get registration details
609
+ const existingRegistration = await manager.get('your-registration-id');
610
+
611
+ // List all registrations
612
+ const registrations = await manager.list();
613
+
614
+ // Delete registration
615
+ await manager.delete('your-registration-id');
616
+ ```
617
+
618
+ ## Environment Variables
619
+
620
+ Common environment variables used across components:
621
+
622
+ ```bash
623
+ # Adobe commerce credentials
624
+ COMMERCE_BASE_URL=commerce-base-url
625
+ COMMERCE_CONSUMER_KEY=commerce-consumer-key
626
+ COMMERCE_CONSUMER_SECRET=commerce-consumer-secret
627
+ COMMERCE_ACCESS_TOKEN=commerce-access-token
628
+ COMMERCE_ACCESS_TOKEN_SECRET=commerce-access-token-secret
629
+ # Environment from DIST file
630
+ OAUTH_BASE_URL=https://ims-na1.adobelogin.com/ims/token/
631
+ IO_MANAGEMENT_BASE_URL=https://api.adobe.io/events/
632
+ # OAuth configs
633
+ # The following values can be copied from the Credential details page in AppBuilder under Organization > Project > Workspace > OAuth Server-to-Server
634
+ OAUTH_CLIENT_ID=client-id
635
+ OAUTH_CLIENT_SECRET=client-secret
636
+ OAUTH_TECHNICAL_ACCOUNT_ID=technical-account-id
637
+ OAUTH_TECHNICAL_ACCOUNT_EMAIL=technical-account-email
638
+ OAUTH_ORG_ID=org-id
639
+ OAUTH_SCOPES=scopes
640
+ # OAUTH_HOST=<string>
641
+ # Workspace configs
642
+ # The following values can be copied from the JSON downloadable in AppBuilder from Organization > Project > Workspace
643
+ # IO_CONSUMER corresponds to project.org.id
644
+ # IO_PROJECT_ID corresponds to project.id
645
+ # IO_WORKSPACE_ID corresponds to project.workspace.id
646
+ IO_CONSUMER_ID=consumer-id
647
+ IO_PROJECT_ID=project-id
648
+ IO_WORKSPACE_ID=workspace-id
649
+ ```
650
+
651
+ ## License
652
+
653
+ See the [LICENSE](https://github.com/adobe-commerce/aio-toolkit/blob/main/LICENSE) file for license rights and limitations.
654
+
655
+ ## Support
656
+
657
+ - **Documentation**: Full API documentation available in TypeScript definitions
658
+ - **Package**: [npm package page](https://www.npmjs.com/package/@adobe-commerce/aio-toolkit)