@adobe-commerce/aio-toolkit 1.2.5 → 1.2.7

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/README.md +450 -1
  3. package/dist/aio-toolkit-cli-workflow/bin/cli.js +2048 -0
  4. package/dist/aio-toolkit-cli-workflow/bin/cli.js.map +1 -0
  5. package/dist/aio-toolkit-cursor-context/bin/cli.js +16 -0
  6. package/dist/aio-toolkit-cursor-context/bin/cli.js.map +1 -1
  7. package/dist/index.d.mts +61 -6
  8. package/dist/index.d.ts +61 -6
  9. package/dist/index.js +600 -42
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +610 -38
  12. package/dist/index.mjs.map +1 -1
  13. package/files/cursor-context/commands/aio-toolkit-analyze-adobe-commerce-module.md +612 -0
  14. package/files/cursor-context/commands/aio-toolkit-create-amazon-sqs-consumer.md +445 -0
  15. package/files/cursor-context/commands/aio-toolkit-create-event-consumer-action.md +6 -0
  16. package/files/cursor-context/commands/aio-toolkit-create-graphql-action.md +21 -7
  17. package/files/cursor-context/commands/aio-toolkit-create-openwhisk-action.md +326 -0
  18. package/files/cursor-context/commands/aio-toolkit-create-runtime-action.md +15 -5
  19. package/files/cursor-context/commands/aio-toolkit-create-shipping-carrier.md +681 -0
  20. package/files/cursor-context/commands/aio-toolkit-create-webhook-action.md +22 -9
  21. package/files/cursor-context/rules/aio-toolkit-create-adobe-commerce-client.mdc +252 -116
  22. package/files/cursor-context/rules/aio-toolkit-oop-best-practices.mdc +10 -4
  23. package/files/cursor-context/rules/aio-toolkit-setup-new-relic-telemetry.mdc +167 -2
  24. package/files/cursor-context/rules/aio-toolkit-use-abdb-collection.mdc +610 -0
  25. package/files/cursor-context/rules/aio-toolkit-use-abdb-repository.mdc +705 -0
  26. package/files/cursor-context/rules/aio-toolkit-use-adobe-auth.mdc +442 -0
  27. package/files/cursor-context/rules/aio-toolkit-use-amazon-sqs-publish.mdc +397 -0
  28. package/files/cursor-context/rules/aio-toolkit-use-file-repository.mdc +502 -0
  29. package/files/cursor-context/rules/aio-toolkit-use-publish-event.mdc +510 -0
  30. package/files/cursor-context/rules/aio-toolkit-use-runtime-api-gateway-service.mdc +542 -0
  31. package/package.json +4 -2
@@ -0,0 +1,502 @@
1
+ ---
2
+ description: Adding FileRepository to Adobe I/O Runtime actions using @adobe-commerce/aio-toolkit
3
+ globs: '**/{actions,lib}/**/*.{ts,js}'
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Using FileRepository
8
+
9
+ ## Trigger Conditions
10
+
11
+ This rule applies when the user requests to add persistent file storage to an action using any of these phrases:
12
+
13
+ - "Store data in App Builder"
14
+ - "Save records to file storage"
15
+ - "Persist data across invocations"
16
+ - "Use FileRepository"
17
+ - "Add file storage to [action-name]"
18
+ - "CRUD file operations in App Builder"
19
+ - "Save to Adobe I/O Files"
20
+ - "Create a repository for [entity]"
21
+ - "Store [entity] data"
22
+ - "List / load / save / delete records"
23
+
24
+ **Important**: This rule does NOT create new actions. It adds persistent file storage to 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 storing or persisting data:
29
+
30
+ - **Automatically trigger this rule** after the action is created
31
+ - Examples: "save the result to storage", "persist records", "CRUD operations on [entity]", "store data between invocations"
32
+ - This rule handles creating the repository class, integrating it into the action, and documenting ID/overwrite behaviour
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 FileRepository 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. **App Builder Files Services API**: `FileRepository` uses `@adobe/aio-sdk`'s Files client, which is included with standard App Builder credentials. No additional configuration is required — the Files Services API is automatically available in App Builder workspaces.
49
+
50
+ 3. **Detect Language (TypeScript vs JavaScript)**:
51
+
52
+ **Check for TypeScript indicators (check ALL of these)**:
53
+ 1. **Package Dependencies**: Look for `typescript` in `package.json` dependencies or devDependencies
54
+ 2. **Config File**: Check if `tsconfig.json` exists in project root
55
+ 3. **TypeScript Loaders**: Look for `ts-loader`, `ts-node`, or `@types/*` packages in `package.json`
56
+ 4. **Existing Files**: Check for `.ts` or `.tsx` files in key directories (`actions/`, `src/`, `lib/`)
57
+
58
+ **Detection Logic**:
59
+ - **If ANY of the following are true → TypeScript project**:
60
+ - `typescript` package installed AND `tsconfig.json` exists
61
+ - Multiple TypeScript indicators (2 or more from above list)
62
+ - Existing `.ts` files found in `actions/` or `lib/` directories
63
+ - **Otherwise → JavaScript project**
64
+
65
+ 4. **Detect Project Structure**:
66
+ - Check `app.config.yaml` for `application:` section (root actions)
67
+ - Check `app.config.yaml` for `extensions:` section (extension point actions)
68
+ - If extensions exist, parse the `$include` path for the extension directory
69
+
70
+ 5. **Check Existing Actions**: List all existing actions and their file paths
71
+ - From root `actions/` directory
72
+ - From `[extension-path]/actions/` directory
73
+ - If NO actions exist: Stop and recommend creating an action first
74
+
75
+ 6. Only proceed to Step 2 after confirming all prerequisites
76
+
77
+ ---
78
+
79
+ ## Step 2: Ask Required Questions
80
+
81
+ Before generating any code, ask the user the following questions:
82
+
83
+ **Important**: Do not make assumptions — keep asking until all details are clear.
84
+
85
+ 1. **Entity Name**: What entity will this repository store?
86
+ - Examples: `Order`, `Product`, `Customer`, `Session`, `Config`
87
+ - Used to name the repository class (e.g. `OrderRepository`) and derive a default storage path
88
+
89
+ 2. **Base Filepath**: What base directory path should records be stored under?
90
+ - Format: `/[app-name]/[entity-plural]` — e.g. `/my-app/orders`
91
+ - All records are stored as `{filepath}/{id}.json`
92
+ - **Default suggestion**: derive from entity name, e.g. `Order` → `/my-app/orders`
93
+
94
+ 3. **Target Action(s)**: Which existing action(s) will use this repository?
95
+ - List all discovered actions for the user to choose from
96
+ - Multiple actions can share the same repository class
97
+
98
+ 4. **CRUD Operations**: Which operations are needed?
99
+ - **`list()`** — retrieve all records (reads every file; for large datasets use `metadata()` instead)
100
+ - **`metadata()`** — retrieve file metadata without reading content (fast; timestamps, sizes, existence checks)
101
+ - **`load(id)`** — retrieve a single record by ID
102
+ - **`save(payload, id?, overwrite?)`** — create or update a record
103
+ - **`delete(ids[])`** — delete one or more records
104
+ - Ask: which of these does the action need?
105
+
106
+ 5. **ID Strategy** (for `save()` operations): How should record IDs be assigned?
107
+ - **Auto-generated** (default): millisecond timestamp (`new Date().getTime()`)
108
+ - **From payload**: use `payload.id` when the record already carries its own ID
109
+ - **Explicit parameter**: pass a specific ID from `params` (e.g. `params.orderId`) — will be sanitized (`order-123` → `order_123`)
110
+
111
+ 6. **Overwrite vs Merge** (for `save()` operations):
112
+ - **Merge** (default, `overwrite: false`): shallow-merges new payload into existing record — existing fields not in the payload are preserved
113
+ - **Overwrite** (`overwrite: true`): replaces the entire file — existing fields are lost
114
+ - Ask: which behaviour is needed for each save operation?
115
+
116
+ ---
117
+
118
+ ## Step 3: Confirm Implementation Details
119
+
120
+ After receiving answers to ALL questions, present a comprehensive summary and **wait for user confirmation** before proceeding to Step 4:
121
+
122
+ ---
123
+
124
+ ### 📋 FileRepository Integration Summary
125
+
126
+ **Entity:** `[EntityName]`
127
+ **Repository Class:** `[EntityName]Repository`
128
+ **Storage Path:** `[/app-name/entity-plural]`
129
+ **Language:** [TypeScript/JavaScript] (auto-detected)
130
+
131
+ **Target Action(s):**
132
+ - `[action-name]` → `[path/to/action/index.js]`
133
+
134
+ **Operations to Integrate:**
135
+
136
+ | Operation | Details |
137
+ |---|---|
138
+ | `list()` | Retrieve all records |
139
+ | `metadata()` | Retrieve file metadata without content |
140
+ | `load(id)` | Load single record by `[params.field]` |
141
+ | `save(payload, id?, overwrite?)` | ID: [auto / payload.id / params.field]; Overwrite: [true/false] |
142
+ | `delete(ids[])` | Delete records by `[params.field]` |
143
+
144
+ ---
145
+
146
+ ### ✅ What Will Be Changed
147
+
148
+ **1. New Repository Class**
149
+ - ➕ `lib/[entityName]Repository.[js/ts]`
150
+ - Extends `FileRepository` with fixed filepath `[/app-name/entity-plural]`
151
+ - Reusable across all actions in the project
152
+
153
+ **2. Action Implementation**
154
+ - ✏️ `[action-path]/index.[js/ts]`
155
+ - Import `[EntityName]Repository` from `lib/`
156
+ - Construct `new [EntityName]Repository()` inside the handler
157
+ - Add selected CRUD operations with error handling
158
+
159
+ **3. No Configuration Changes Required**
160
+ - `FileRepository` requires no extra inputs in `app.config.yaml` — standard App Builder credentials are sufficient
161
+
162
+ ---
163
+
164
+ **Should I proceed with this implementation?**
165
+
166
+ ---
167
+
168
+ ## Step 4: Generate Implementation
169
+
170
+ ### Step 4.1: Create the Repository Class
171
+
172
+ Create a dedicated repository class in `lib/`. Always extend `FileRepository` rather than using it directly — this encapsulates the storage path and makes the class reusable.
173
+
174
+ #### JavaScript
175
+
176
+ ```javascript
177
+ // lib/[entityName]Repository.js
178
+ const { FileRepository } = require('@adobe-commerce/aio-toolkit');
179
+
180
+ class [EntityName]Repository extends FileRepository {
181
+ constructor() {
182
+ super('[/app-name/entity-plural]');
183
+ }
184
+ }
185
+
186
+ module.exports = [EntityName]Repository;
187
+ ```
188
+
189
+ #### TypeScript
190
+
191
+ ```typescript
192
+ // lib/[entityName]Repository.ts
193
+ import { FileRepository } from '@adobe-commerce/aio-toolkit';
194
+
195
+ export class [EntityName]Repository extends FileRepository {
196
+ constructor() {
197
+ super('[/app-name/entity-plural]');
198
+ }
199
+ }
200
+ ```
201
+
202
+ > Place this file in a shared `lib/` directory at the same level as `actions/` so it can be imported from any action.
203
+
204
+ ---
205
+
206
+ ### Step 4.2: Integrate into the Target Action
207
+
208
+ Add the repository to the existing action. Include only the CRUD operations selected in Step 2.
209
+
210
+ #### List All Records — JavaScript
211
+
212
+ ```javascript
213
+ const { HttpMethod, RuntimeAction, RuntimeActionResponse } = require('@adobe-commerce/aio-toolkit');
214
+ const [EntityName]Repository = require('../../lib/[entityName]Repository');
215
+
216
+ exports.main = RuntimeAction.execute(
217
+ '[action-name]',
218
+ [HttpMethod.POST],
219
+ [],
220
+ ['Authorization'],
221
+ async (params, ctx) => {
222
+ const repo = new [EntityName]Repository();
223
+ const records = await repo.list();
224
+ return RuntimeActionResponse.success(records);
225
+ }
226
+ );
227
+ ```
228
+
229
+ #### Load a Single Record — JavaScript
230
+
231
+ ```javascript
232
+ // Check for empty record — load() returns {} when the file does not exist
233
+ const record = await repo.load(params.id);
234
+ if (!record.id) {
235
+ return RuntimeActionResponse.error(HttpStatus.NOT_FOUND, `[EntityName] '${params.id}' not found`);
236
+ }
237
+ return RuntimeActionResponse.success(record);
238
+ ```
239
+
240
+ #### Save a Record (auto-generated ID) — JavaScript
241
+
242
+ ```javascript
243
+ const savedId = await repo.save({
244
+ // payload fields
245
+ status: 'active',
246
+ ...params,
247
+ });
248
+
249
+ // save() returns null on failure — always check
250
+ if (!savedId) {
251
+ return RuntimeActionResponse.error(HttpStatus.INTERNAL_ERROR, 'Failed to save [entityName]');
252
+ }
253
+
254
+ return RuntimeActionResponse.success({ id: savedId });
255
+ ```
256
+
257
+ #### Save with Explicit ID (merge, default) — JavaScript
258
+
259
+ ```javascript
260
+ // ID from params is sanitized: 'order-123' → 'order_123'
261
+ const savedId = await repo.save(
262
+ { status: params.status },
263
+ params.[entityId] // explicit ID takes precedence over payload.id
264
+ );
265
+
266
+ if (!savedId) {
267
+ return RuntimeActionResponse.error(HttpStatus.INTERNAL_ERROR, 'Failed to update [entityName]');
268
+ }
269
+
270
+ return RuntimeActionResponse.success({ id: savedId });
271
+ ```
272
+
273
+ #### Save with Overwrite (replace entire file) — JavaScript
274
+
275
+ ```javascript
276
+ // overwrite: true replaces the entire file — no merging with existing content
277
+ const savedId = await repo.save(
278
+ {
279
+ // provide ALL fields — partial updates are not possible with overwrite
280
+ status: params.status,
281
+ customerId: params.customerId,
282
+ },
283
+ params.[entityId],
284
+ true // overwrite: true
285
+ );
286
+ ```
287
+
288
+ #### Delete Records — JavaScript
289
+
290
+ ```javascript
291
+ // Pass exact IDs as returned by save(), list(), or load()
292
+ // IDs are NOT sanitized by delete() — do not transform them before passing
293
+ const remaining = await repo.delete(params.ids);
294
+ return RuntimeActionResponse.success(remaining);
295
+ ```
296
+
297
+ #### Get Metadata (without reading content) — JavaScript
298
+
299
+ ```javascript
300
+ // All records — no JSON reads, fast for large datasets
301
+ const allMeta = await repo.metadata();
302
+
303
+ // Single record metadata
304
+ const singleMeta = params.id ? await repo.metadata(params.id) : null;
305
+
306
+ return RuntimeActionResponse.success({ all: allMeta, single: singleMeta });
307
+ ```
308
+
309
+ **TypeScript:** Same patterns with `import` syntax and optional type imports:
310
+
311
+ ```typescript
312
+ import { FileRepository } from '@adobe-commerce/aio-toolkit';
313
+ import type { FileRecord, FileMetadata } from '@adobe-commerce/aio-toolkit';
314
+ ```
315
+
316
+ ---
317
+
318
+ ## Step 5: Recommendations
319
+
320
+ ### A. Always Check save() Return Value
321
+
322
+ `save()` is the only method that catches errors internally — it returns `null` on failure rather than throwing. All other methods propagate errors from the Files SDK.
323
+
324
+ ```javascript
325
+ const savedId = await repo.save(payload, id);
326
+ if (!savedId) {
327
+ // Write failed — log and return an appropriate error response
328
+ logger.error({ message: '[action-name]-save-failed' });
329
+ return RuntimeActionResponse.error(HttpStatus.INTERNAL_ERROR, 'Storage write failed');
330
+ }
331
+ ```
332
+
333
+ ### B. Checking for a Missing Record
334
+
335
+ `load()` returns `{}` (empty object cast to `FileRecord`) when no file exists — it does not throw. Always check for record existence before accessing fields:
336
+
337
+ ```javascript
338
+ const record = await repo.load(params.id);
339
+ if (!record.id) {
340
+ // Record does not exist
341
+ return RuntimeActionResponse.error(HttpStatus.NOT_FOUND, `[EntityName] '${params.id}' not found`);
342
+ }
343
+ // record is valid — use record.id, record.status, etc.
344
+ ```
345
+
346
+ ### C. ID Sanitization Rules
347
+
348
+ When an explicit `id` is passed to `save()`, it is sanitized:
349
+
350
+ | Input | Result |
351
+ |---|---|
352
+ | `'order-123'` | `'order_123'` |
353
+ | `'my.record'` | `'my_record'` |
354
+ | `'valid_id_123'` | `'valid_id_123'` (unchanged) |
355
+ | `'---'` | `'1714234567890'` (timestamp fallback) |
356
+
357
+ - Characters outside `[a-zA-Z0-9_]` are replaced with `_`
358
+ - If the result is empty or all underscores, falls back to millisecond timestamp
359
+ - `payload.id` is **not** sanitized — only explicit `id` parameter is
360
+ - IDs passed to `delete()` are **never** sanitized — pass exact IDs from `save()`/`list()`/`load()`
361
+
362
+ ### D. Timestamps Are From the File System
363
+
364
+ `createdAt` and `updatedAt` fields on `FileRecord` come from file system metadata (creation time and last modified time). They are **not stored inside the JSON file**. This means:
365
+ - They are automatically updated on every `save()` call — no manual timestamp management needed
366
+ - You cannot set or override them via payload — they are stripped from `payload` before writing
367
+
368
+ ### E. Performance: list() vs metadata()
369
+
370
+ | Situation | Use |
371
+ |---|---|
372
+ | Need full record content | `list()` — reads every JSON file |
373
+ | Only need sizes, timestamps, or existence | `metadata()` — no file content reads, much faster for large datasets |
374
+ | Check if a specific record exists | `metadata(id)` — single metadata fetch |
375
+
376
+ ### F. Overwrite vs Merge — Choose Carefully
377
+
378
+ | Scenario | Overwrite Setting |
379
+ |---|---|
380
+ | Partial update (e.g. update status only) | `overwrite: false` (default) — merges with existing |
381
+ | Full replacement (e.g. PUT semantics) | `overwrite: true` — replaces entire file |
382
+ | New record | Both behave the same — file does not exist yet |
383
+
384
+ > **Warning**: With `overwrite: true`, any fields not in the new payload are permanently lost. Use it only when you intend full replacement (PUT), not partial update (PATCH).
385
+
386
+ ---
387
+
388
+ ## Key Components from @adobe-commerce/aio-toolkit
389
+
390
+ ### FileRepository Constructor
391
+
392
+ ```typescript
393
+ constructor(filepath: string)
394
+ ```
395
+
396
+ - `filepath` — base directory for all file operations; all records stored as `{filepath}/{id}.json`
397
+ - Files SDK client is initialized lazily on first operation and cached for the lifetime of the instance
398
+
399
+ ### FileRepository Methods
400
+
401
+ ```typescript
402
+ // List all records — reads every file in the directory
403
+ async list(): Promise<FileRecord[]>
404
+
405
+ // Get metadata only — no file content reads
406
+ async metadata(id?: string): Promise<FileMetadata | FileMetadata[]>
407
+
408
+ // Load a single record by ID — returns {} if not found
409
+ async load(id: string): Promise<FileRecord>
410
+
411
+ // Save a record — returns ID on success, null on failure
412
+ async save(
413
+ payload: Partial<FileRecord>,
414
+ id?: string | null, // explicit ID — sanitized; overrides payload.id
415
+ overwrite?: boolean // default: false (merge)
416
+ ): Promise<string | null>
417
+
418
+ // Delete records — returns updated list() after deletion
419
+ async delete(ids: string[]): Promise<FileRecord[]>
420
+ ```
421
+
422
+ ### FileRecord Type
423
+
424
+ ```typescript
425
+ interface FileRecord {
426
+ id: string;
427
+ createdAt: string; // ISO 8601 UTC — from file system metadata, NOT stored in JSON
428
+ updatedAt: string; // ISO 8601 UTC — from file system metadata, NOT stored in JSON
429
+ [key: string]: any; // your payload fields
430
+ }
431
+ ```
432
+
433
+ ### FileMetadata Type
434
+
435
+ ```typescript
436
+ interface FileMetadata {
437
+ name: string;
438
+ creationTime: Date;
439
+ lastModified: Date;
440
+ etag: string;
441
+ contentLength: number;
442
+ contentType: string;
443
+ isDirectory: boolean;
444
+ isPublic: boolean;
445
+ url: string;
446
+ }
447
+ ```
448
+
449
+ ### Exports
450
+
451
+ ```typescript
452
+ import { FileRepository } from '@adobe-commerce/aio-toolkit';
453
+ import type { FileRecord, FileMetadata } from '@adobe-commerce/aio-toolkit';
454
+ ```
455
+
456
+ ---
457
+
458
+ ## Important Notes
459
+
460
+ 1. **Always extend FileRepository**: Create a dedicated subclass per entity — do not use `FileRepository` directly with a hardcoded path in action code
461
+ 2. **save() returns null on failure**: It is the only method that silently catches errors — always check the return value
462
+ 3. **load() returns {} on missing file**: Never throws when the file doesn't exist — check `!record.id` before using the record
463
+ 4. **Timestamps are file system metadata**: `createdAt`/`updatedAt` are never stored in the JSON file — they come from the Files SDK metadata on every `load()` or `list()` call
464
+ 5. **IDs passed to delete() are NOT sanitized**: Pass the exact ID strings returned by `save()`, `list()`, or `load()`
465
+ 6. **No extra credentials needed**: FileRepository uses standard App Builder credentials — no additional inputs are required in `app.config.yaml`
466
+ 7. **Files Services API must be enabled**: The App Builder workspace must have the Files Services API enabled in Adobe Developer Console (enabled by default in new App Builder projects)
467
+ 8. **Filepath convention**: Use `/[app-name]/[entity-plural]` for the base path — e.g. `/my-app/orders`, `/my-app/products`
468
+ 9. **Lazy SDK initialization**: The Files SDK client is created on the first operation, not in the constructor — no setup overhead if the repository is instantiated but not used
469
+ 10. **Works with any action type**: RuntimeAction, WebhookAction, EventConsumerAction, GraphQlAction, OpenwhiskAction
470
+
471
+ ## Integration with Action Creation Rules
472
+
473
+ **This rule should be referenced in other action creation rules when data storage is mentioned:**
474
+
475
+ ### Trigger Patterns in Other Rules
476
+
477
+ When developers mention these phrases during action creation in the "Business Logic" question:
478
+
479
+ - "Store data"
480
+ - "Persist records"
481
+ - "Save to file storage"
482
+ - "CRUD operations on [entity]"
483
+ - "Store data between invocations"
484
+ - "Create a repository"
485
+ - Any phrase containing "FileRepository" or "file storage" + "save"/"list"/"load"/"delete"
486
+
487
+ **Action to take:**
488
+
489
+ 1. Complete the action creation first
490
+ 2. Then inform the user: "I see you need to persist data. Let me help you integrate FileRepository."
491
+ 3. Trigger/reference the "Using FileRepository" rule
492
+ 4. This rule handles creating the repository class, integrating it into the action, and all ID/overwrite guidance
493
+
494
+ **Example Flow:**
495
+
496
+ ```
497
+ User: "Create a runtime action that saves order records to persistent storage"
498
+ AI: ✓ Detected JavaScript project
499
+ AI: Creating runtime action... [generates index.js]
500
+ AI: I see you need persistent storage. Let me integrate FileRepository.
501
+ AI: [Creates lib/OrderRepository.js and integrates it into the action]
502
+ ```