@adobe-commerce/aio-toolkit 1.2.5 → 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 +134 -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,610 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Adding App Builder Data (ABDB) collection schema to Adobe I/O Runtime actions using @adobe-commerce/aio-toolkit
|
|
3
|
+
globs: '**/{actions,lib}/**/*.{ts,js}'
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Using AbdbCollection
|
|
8
|
+
|
|
9
|
+
## Trigger Conditions
|
|
10
|
+
|
|
11
|
+
This rule applies when the user requests to add MongoDB-backed database storage to an action using any of these phrases:
|
|
12
|
+
|
|
13
|
+
- "Use App Builder Data"
|
|
14
|
+
- "Use ABDB"
|
|
15
|
+
- "MongoDB-backed storage"
|
|
16
|
+
- "Define collection schema"
|
|
17
|
+
- "Database in App Builder"
|
|
18
|
+
- "Store data in MongoDB"
|
|
19
|
+
- "Create a collection for [entity]"
|
|
20
|
+
- "Persist data with ABDB"
|
|
21
|
+
- "App Builder Data Services"
|
|
22
|
+
- "Custom database query"
|
|
23
|
+
- "Define database schema for [entity]"
|
|
24
|
+
|
|
25
|
+
**Important**: This rule covers **collection schema creation** and direct `run()` usage for custom queries. For full CRUD operations (insert, find, update, delete) via `AbdbRepository`, use the "Using AbdbRepository" rule after this one.
|
|
26
|
+
|
|
27
|
+
**Integration with Other Action Creation Rules:**
|
|
28
|
+
|
|
29
|
+
When using action creation rules (RuntimeAction, WebhookAction, EventConsumerAction, GraphQlAction, OpenwhiskAction) and the developer mentions persisting data in a managed database:
|
|
30
|
+
|
|
31
|
+
- **Automatically trigger this rule** after the action is created
|
|
32
|
+
- Examples: "save records to the database", "query MongoDB", "use App Builder Data", "persist with ABDB"
|
|
33
|
+
- This rule handles creating the collection class, wiring IMS credentials, configuring the YAML, and integrating `run()` into the action
|
|
34
|
+
|
|
35
|
+
**If you need to create a new action first:**
|
|
36
|
+
|
|
37
|
+
- Use the appropriate action creation command first
|
|
38
|
+
- Then this rule will add the ABDB collection to that action
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Step 1: Verify Prerequisites
|
|
43
|
+
|
|
44
|
+
Before proceeding, verify:
|
|
45
|
+
|
|
46
|
+
1. **Toolkit installed**: Check `@adobe-commerce/aio-toolkit` in `package.json` dependencies
|
|
47
|
+
- If NOT installed: `npm install @adobe-commerce/aio-toolkit`
|
|
48
|
+
|
|
49
|
+
2. **Peer dependency — aio-lib-db**: Check `@adobe/aio-lib-db` in `package.json` dependencies
|
|
50
|
+
- **Required**: `AbdbCollection` uses this internally to connect to the App Builder Data database
|
|
51
|
+
- If NOT installed: `npm install @adobe/aio-lib-db`
|
|
52
|
+
- Minimum version: `>= 1.0.0`
|
|
53
|
+
|
|
54
|
+
3. **AIO SDK installed**: Check `@adobe/aio-sdk` in `package.json` dependencies
|
|
55
|
+
- Required for `Core.AuthClient.generateAccessToken(params)` to obtain an IMS access token
|
|
56
|
+
- If NOT installed: `npm install @adobe/aio-sdk`
|
|
57
|
+
|
|
58
|
+
4. **Detect Language (TypeScript vs JavaScript)**:
|
|
59
|
+
|
|
60
|
+
**Check for TypeScript indicators (check ALL of these)**:
|
|
61
|
+
1. **Package Dependencies**: Look for `typescript` in `package.json` dependencies or devDependencies
|
|
62
|
+
2. **Config File**: Check if `tsconfig.json` exists in project root
|
|
63
|
+
3. **TypeScript Loaders**: Look for `ts-loader`, `ts-node`, or `@types/*` packages in `package.json`
|
|
64
|
+
4. **Existing Files**: Check for `.ts` or `.tsx` files in key directories (`actions/`, `src/`, `lib/`)
|
|
65
|
+
|
|
66
|
+
**Detection Logic**:
|
|
67
|
+
- **If ANY of the following are true → TypeScript project**:
|
|
68
|
+
- `typescript` package installed AND `tsconfig.json` exists
|
|
69
|
+
- Multiple TypeScript indicators (2 or more from above list)
|
|
70
|
+
- Existing `.ts` files found in `actions/` or `lib/` directories
|
|
71
|
+
- **Otherwise → JavaScript project**
|
|
72
|
+
|
|
73
|
+
5. **Detect Project Structure**:
|
|
74
|
+
- Check `app.config.yaml` for `application:` section (root actions)
|
|
75
|
+
- Check `app.config.yaml` for `extensions:` section (extension point actions)
|
|
76
|
+
- If extensions exist, parse the `$include` path for the extension directory
|
|
77
|
+
|
|
78
|
+
6. **Check Existing Actions**: List all existing actions and their file paths
|
|
79
|
+
- From root `actions/` directory
|
|
80
|
+
- From `[extension-path]/actions/` directory
|
|
81
|
+
- If NO actions exist: Stop and recommend creating an action first
|
|
82
|
+
|
|
83
|
+
7. **Check Existing Collections**: Look for `lib/database/collection/` directory
|
|
84
|
+
- List any existing collection classes already defined
|
|
85
|
+
- Inform the user if a collection for the same entity already exists
|
|
86
|
+
|
|
87
|
+
8. 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 the following questions:
|
|
94
|
+
|
|
95
|
+
**Important**: Do not make assumptions — keep asking until all details are clear.
|
|
96
|
+
|
|
97
|
+
1. **Entity Name**: What entity will this collection store?
|
|
98
|
+
- Examples: `Order`, `User`, `QueueMessage`, `Product`, `Session`
|
|
99
|
+
- Used to name the collection class (e.g. `OrdersCollection`) and derive the collection name
|
|
100
|
+
|
|
101
|
+
2. **Collection Name**: What is the MongoDB collection name?
|
|
102
|
+
- Rules: **only alphanumeric characters and underscores** (`[a-zA-Z0-9_]`) — no hyphens, no spaces
|
|
103
|
+
- Convention: snake_case plural — e.g. `orders`, `queue_messages`, `user_sessions`
|
|
104
|
+
- **Default suggestion**: derive from entity name, e.g. `Order` → `orders`
|
|
105
|
+
|
|
106
|
+
3. **Columns / Schema**: What fields does this collection need?
|
|
107
|
+
- For each column, ask:
|
|
108
|
+
- **Name**: snake_case, alphanumeric + underscore only
|
|
109
|
+
- **Type**: one of `STRING`, `NUMBER`, `BOOLEAN`, `DATETIME`
|
|
110
|
+
- **Required?**: is this field mandatory in every record?
|
|
111
|
+
- **Description** (optional): human-readable label
|
|
112
|
+
- Minimum: at least one column
|
|
113
|
+
- Tip: guide the user with examples — e.g. `status` (STRING, required), `total` (NUMBER, required), `created_at` (DATETIME, optional)
|
|
114
|
+
|
|
115
|
+
4. **Data Region**: Which region should the database connect to?
|
|
116
|
+
- Options: `amer` (default), `emea`, `apac`
|
|
117
|
+
- **Default**: `amer` — suggest this unless the user knows they need another region
|
|
118
|
+
|
|
119
|
+
5. **Target Action(s)**: Which existing action(s) will use this collection?
|
|
120
|
+
- List all discovered actions for the user to choose from
|
|
121
|
+
- The action will use `collection.run()` directly for custom queries
|
|
122
|
+
|
|
123
|
+
6. **Query / Operation**: What database operation(s) will the action perform?
|
|
124
|
+
- Examples: find all, find by filter, insert one, update, delete, count
|
|
125
|
+
- This determines what goes inside the `run()` callback
|
|
126
|
+
- Note: for standard CRUD (insert/find/update/delete), suggest "Using AbdbRepository" rule after this setup
|
|
127
|
+
|
|
128
|
+
7. **Validation Strategy**: Should the action validate incoming params before writing?
|
|
129
|
+
- **`validate()`** — fail fast on the first bad field (good for single inserts or tight control)
|
|
130
|
+
- **`validateAll()`** — collect all errors at once (better UX at API boundaries)
|
|
131
|
+
- **None** — skip validation (read-only or trusted data only)
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Step 3: Confirm Implementation Details
|
|
136
|
+
|
|
137
|
+
After receiving answers to ALL questions, present a comprehensive summary and **wait for user confirmation** before proceeding to Step 4:
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### 📋 AbdbCollection Integration Summary
|
|
142
|
+
|
|
143
|
+
**Entity:** `[EntityName]`
|
|
144
|
+
**Collection Class:** `[EntityName]Collection`
|
|
145
|
+
**Collection Name (DB):** `[collection_name]`
|
|
146
|
+
**Language:** [TypeScript/JavaScript] (auto-detected)
|
|
147
|
+
**Region:** `[amer/emea/apac]`
|
|
148
|
+
|
|
149
|
+
**Schema:**
|
|
150
|
+
|
|
151
|
+
| Column | Type | Required | Description |
|
|
152
|
+
|---|---|---|---|
|
|
153
|
+
| `[column_name]` | `[TYPE]` | Yes/No | `[description or —]` |
|
|
154
|
+
|
|
155
|
+
**Target Action(s):**
|
|
156
|
+
- `[action-name]` → `[path/to/action/index.js]`
|
|
157
|
+
- **Operation**: `[find all / find by filter / insert / update / delete / custom]`
|
|
158
|
+
- **Validation**: `validate()` / `validateAll()` / None
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### ✅ What Will Be Changed
|
|
163
|
+
|
|
164
|
+
**1. New Collection Class**
|
|
165
|
+
- ➕ `lib/database/collection/[entity-name]/index.[js/ts]`
|
|
166
|
+
- Extends `AbdbCollection` with fixed collection name `[collection_name]`
|
|
167
|
+
- Defines all columns via `addColumn()`
|
|
168
|
+
- Reusable across all actions in the project
|
|
169
|
+
|
|
170
|
+
**2. Action Implementation**
|
|
171
|
+
- ✏️ `[action-path]/index.[js/ts]`
|
|
172
|
+
- Import `[EntityName]Collection` from `lib/database/collection/[entity-name]`
|
|
173
|
+
- Import `Core` from `@adobe/aio-sdk`
|
|
174
|
+
- Generate IMS access token via `Core.AuthClient.generateAccessToken(params)`
|
|
175
|
+
- Call `collection.run(callback, accessToken, region)` for the database operation
|
|
176
|
+
- Add validation with `validate()` / `validateAll()` if selected
|
|
177
|
+
|
|
178
|
+
**3. Action Configuration**
|
|
179
|
+
- ✏️ `app.config.yaml` or `ext.config.yaml` (or `actions.config.yaml` if packaged)
|
|
180
|
+
- Add `include-ims-credentials: true` annotation
|
|
181
|
+
- Add database auto-provision block (if not already present)
|
|
182
|
+
|
|
183
|
+
**4. No Additional `.env` Variables Required**
|
|
184
|
+
- `AbdbCollection` uses standard IMS credentials already injected via `include-ims-credentials: true`
|
|
185
|
+
- No new secrets are needed
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
**Should I proceed with this implementation?**
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Step 4: Generate Implementation
|
|
194
|
+
|
|
195
|
+
### Step 4.1: Create the Collection Class
|
|
196
|
+
|
|
197
|
+
Create a dedicated collection class under `lib/database/collection/`. Always extend `AbdbCollection` — never instantiate it directly with ad hoc column definitions in action code.
|
|
198
|
+
|
|
199
|
+
#### TypeScript
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// lib/database/collection/[entity-name]/index.ts
|
|
203
|
+
|
|
204
|
+
import { AbdbCollection, AbdbColumnType } from '@adobe-commerce/aio-toolkit';
|
|
205
|
+
|
|
206
|
+
export class [EntityName]Collection extends AbdbCollection {
|
|
207
|
+
constructor() {
|
|
208
|
+
super('[collection_name]', collection => {
|
|
209
|
+
collection
|
|
210
|
+
.addColumn('[column_1]', AbdbColumnType.[TYPE], '[description]', true) // required
|
|
211
|
+
.addColumn('[column_2]', AbdbColumnType.[TYPE], '[description]', false) // optional
|
|
212
|
+
.addColumn('[column_3]', AbdbColumnType.[TYPE], { isRequired: true }); // options-object form
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### JavaScript
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
// lib/database/collection/[entity-name]/index.js
|
|
222
|
+
|
|
223
|
+
const { AbdbCollection, AbdbColumnType } = require('@adobe-commerce/aio-toolkit');
|
|
224
|
+
|
|
225
|
+
class [EntityName]Collection extends AbdbCollection {
|
|
226
|
+
constructor() {
|
|
227
|
+
super('[collection_name]', collection => {
|
|
228
|
+
collection
|
|
229
|
+
.addColumn('[column_1]', AbdbColumnType.[TYPE], '[description]', true)
|
|
230
|
+
.addColumn('[column_2]', AbdbColumnType.[TYPE], '[description]', false);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = { [EntityName]Collection };
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
> **Column naming rules**: Only `[a-zA-Z0-9_]` characters are allowed — use snake_case. Hyphens and spaces will throw during construction.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
### Step 4.2: Integrate into the Target Action
|
|
243
|
+
|
|
244
|
+
Add the collection to the existing action. Generate an IMS token, then call `run()` with the database logic.
|
|
245
|
+
|
|
246
|
+
#### Find All Records — TypeScript
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { HttpMethod, RuntimeAction, RuntimeActionResponse } from '@adobe-commerce/aio-toolkit';
|
|
250
|
+
import { Core } from '@adobe/aio-sdk';
|
|
251
|
+
import { [EntityName]Collection } from '../../lib/database/collection/[entity-name]';
|
|
252
|
+
|
|
253
|
+
const name = '[action-name]';
|
|
254
|
+
|
|
255
|
+
export const main = RuntimeAction.execute(
|
|
256
|
+
name,
|
|
257
|
+
[HttpMethod.POST],
|
|
258
|
+
[],
|
|
259
|
+
['Authorization'],
|
|
260
|
+
async (params, ctx) => {
|
|
261
|
+
const { logger } = ctx;
|
|
262
|
+
|
|
263
|
+
const tokenResponse = await Core.AuthClient.generateAccessToken(params);
|
|
264
|
+
const accessToken = tokenResponse && tokenResponse.access_token;
|
|
265
|
+
|
|
266
|
+
const collection = new [EntityName]Collection();
|
|
267
|
+
|
|
268
|
+
logger.info({ message: `${name}-fetching` });
|
|
269
|
+
|
|
270
|
+
const results = await collection.run(async (col) => {
|
|
271
|
+
return await col.find({}).toArray();
|
|
272
|
+
}, accessToken, '[amer/emea/apac]');
|
|
273
|
+
|
|
274
|
+
logger.info({ message: `${name}-fetched`, count: results.length });
|
|
275
|
+
|
|
276
|
+
return RuntimeActionResponse.success(results);
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### Find with Filter — TypeScript
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const results = await collection.run(async (col) => {
|
|
285
|
+
return await col
|
|
286
|
+
.find({ status: 'active' })
|
|
287
|
+
.sort({ _created_at: -1 })
|
|
288
|
+
.limit(50)
|
|
289
|
+
.toArray();
|
|
290
|
+
}, accessToken, '[region]');
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### Insert with Validation — TypeScript
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// Option A: validate() — fail fast on first error
|
|
297
|
+
collection.validateAll(params); // use validateAll to surface all problems
|
|
298
|
+
const errors = collection.validateAll(params);
|
|
299
|
+
if (errors.length > 0) {
|
|
300
|
+
return RuntimeActionResponse.error(HttpStatus.BAD_REQUEST, errors.join(', '));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const result = await collection.run(async (col) => {
|
|
304
|
+
return await col.insertOne({
|
|
305
|
+
[column_1]: params.[column_1],
|
|
306
|
+
[column_2]: params.[column_2],
|
|
307
|
+
});
|
|
308
|
+
}, accessToken, '[region]');
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
#### JavaScript (equivalent patterns with `require`)
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
const { HttpMethod, RuntimeAction, RuntimeActionResponse, HttpStatus } = require('@adobe-commerce/aio-toolkit');
|
|
315
|
+
const { Core } = require('@adobe/aio-sdk');
|
|
316
|
+
const { [EntityName]Collection } = require('../../lib/database/collection/[entity-name]');
|
|
317
|
+
|
|
318
|
+
const name = '[action-name]';
|
|
319
|
+
|
|
320
|
+
exports.main = RuntimeAction.execute(
|
|
321
|
+
name,
|
|
322
|
+
[HttpMethod.POST],
|
|
323
|
+
[],
|
|
324
|
+
['Authorization'],
|
|
325
|
+
async (params, ctx) => {
|
|
326
|
+
const { logger } = ctx;
|
|
327
|
+
|
|
328
|
+
const tokenResponse = await Core.AuthClient.generateAccessToken(params);
|
|
329
|
+
const accessToken = tokenResponse && tokenResponse.access_token;
|
|
330
|
+
|
|
331
|
+
const collection = new [EntityName]Collection();
|
|
332
|
+
|
|
333
|
+
const results = await collection.run(async (col) => {
|
|
334
|
+
return await col.find({}).toArray();
|
|
335
|
+
}, accessToken, 'amer');
|
|
336
|
+
|
|
337
|
+
return RuntimeActionResponse.success(results);
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
### Step 4.3: Update Action Configuration
|
|
345
|
+
|
|
346
|
+
#### `app.config.yaml` / `ext.config.yaml`
|
|
347
|
+
|
|
348
|
+
```yaml
|
|
349
|
+
[action-name]:
|
|
350
|
+
function: [action-path]/index.[js/ts]
|
|
351
|
+
web: 'yes'
|
|
352
|
+
runtime: nodejs:22
|
|
353
|
+
annotations:
|
|
354
|
+
require-adobe-auth: true
|
|
355
|
+
final: true
|
|
356
|
+
include-ims-credentials: true # REQUIRED: injects IMS credentials for token generation
|
|
357
|
+
inputs:
|
|
358
|
+
LOG_LEVEL: debug
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
> **Why `include-ims-credentials: true`?** `AbdbCollection.run()` requires an IMS access token obtained via `Core.AuthClient.generateAccessToken(params)`. Without this annotation, `generateAccessToken` will fail and `accessToken` will be blank.
|
|
362
|
+
|
|
363
|
+
#### Database Provisioning (add once per project)
|
|
364
|
+
|
|
365
|
+
Add the database auto-provision block to `app.config.yaml` if not already present:
|
|
366
|
+
|
|
367
|
+
```yaml
|
|
368
|
+
application:
|
|
369
|
+
runtimeManifest:
|
|
370
|
+
database:
|
|
371
|
+
auto-provision: true
|
|
372
|
+
region: [amer/emea/apac] # match the region used in collection.run()
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Or provision manually for local development:
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
aio app db provision --region amer
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
> **App Builder Data Services API must be enabled** in your Adobe Developer Console project workspace before provisioning.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Step 5: Recommendations
|
|
386
|
+
|
|
387
|
+
### A. Always Use `validateAll()` at API Boundaries
|
|
388
|
+
|
|
389
|
+
Prefer `validateAll()` over `validate()` when the action receives data from an external caller — it surfaces all schema errors at once rather than forcing the caller to fix one field at a time:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
const errors = collection.validateAll(params);
|
|
393
|
+
if (errors.length > 0) {
|
|
394
|
+
return RuntimeActionResponse.error(HttpStatus.BAD_REQUEST, errors.join(', '));
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Use `validate()` (fail-fast) only when you need to short-circuit immediately and the first error is sufficient context.
|
|
399
|
+
|
|
400
|
+
### B. Collection and Column Naming Rules
|
|
401
|
+
|
|
402
|
+
Both collection names and column names must match `[a-zA-Z0-9_]`:
|
|
403
|
+
|
|
404
|
+
| Input | Valid? | Note |
|
|
405
|
+
|---|---|---|
|
|
406
|
+
| `queue_messages` | ✅ | snake_case — preferred |
|
|
407
|
+
| `orders` | ✅ | simple lowercase |
|
|
408
|
+
| `QueueMessages` | ✅ | PascalCase — allowed but not conventional |
|
|
409
|
+
| `queue-messages` | ❌ | hyphens are not allowed |
|
|
410
|
+
| `order items` | ❌ | spaces are not allowed |
|
|
411
|
+
| `order.items` | ❌ | dots are not allowed |
|
|
412
|
+
|
|
413
|
+
The collection name throws at construction time — catch naming errors during development, not at runtime.
|
|
414
|
+
|
|
415
|
+
### C. Missing Value Rules — `0` and `false` Are Not Missing
|
|
416
|
+
|
|
417
|
+
For **required** columns, a value is considered **missing** (and will fail) only if it is `undefined`, `null`, or an empty/whitespace-only string:
|
|
418
|
+
|
|
419
|
+
| Value | Required column | Optional column |
|
|
420
|
+
|---|---|---|
|
|
421
|
+
| `undefined` | ❌ Fails — `"field" is required` | ✅ Passes |
|
|
422
|
+
| `null` | ❌ Fails | ✅ Passes |
|
|
423
|
+
| `''` / `' '` | ❌ Fails | ✅ Passes |
|
|
424
|
+
| `0` | ✅ Passes (not missing) | ✅ Passes |
|
|
425
|
+
| `false` | ✅ Passes (not missing) | ✅ Passes |
|
|
426
|
+
|
|
427
|
+
> **Common mistake**: Assuming `0` or `false` will fail a required check — they won't. Only truly absent/empty values fail.
|
|
428
|
+
|
|
429
|
+
### D. Column Type Accepted Values
|
|
430
|
+
|
|
431
|
+
| Type | Accepted values |
|
|
432
|
+
|---|---|
|
|
433
|
+
| `STRING` | Primitive `string` only |
|
|
434
|
+
| `NUMBER` | Finite `number`, or a string that parses to a finite number via `Number()` (e.g. `'3.14'`) |
|
|
435
|
+
| `BOOLEAN` | Primitive `boolean`, or trimmed string `'true'` / `'false'` (case-insensitive) |
|
|
436
|
+
| `DATETIME` | Valid `Date` instance; finite `number` (ms timestamp); ISO 8601 string (e.g. `'2024-01-15'`, `'2024-01-15T10:30:00.000Z'`) |
|
|
437
|
+
|
|
438
|
+
### E. Connection Lifecycle — `run()` Always Closes
|
|
439
|
+
|
|
440
|
+
`run()` opens a database connection, executes the callback, then **always closes the connection** in a `finally` block — whether the callback succeeds or throws. You never need to manually close the client.
|
|
441
|
+
|
|
442
|
+
### F. Error Wrapping in `run()`
|
|
443
|
+
|
|
444
|
+
Errors from the database are wrapped and rethrown with a prefix:
|
|
445
|
+
- `DbError` → `AbdbCollection: database error: <message>`
|
|
446
|
+
- Any other error → `AbdbCollection: unexpected error: <message>`
|
|
447
|
+
|
|
448
|
+
Wrap `run()` in a try/catch at the action level to handle these uniformly:
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
try {
|
|
452
|
+
const results = await collection.run(async (col) => col.find({}).toArray(), accessToken);
|
|
453
|
+
return RuntimeActionResponse.success(results);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
logger.error({ message: `${name}-db-error`, error: error.message });
|
|
456
|
+
return RuntimeActionResponse.error(HttpStatus.INTERNAL_ERROR, error.message);
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### G. Prefer `AbdbRepository` Over `run()` Directly
|
|
461
|
+
|
|
462
|
+
**Recommendation: always prefer `AbdbRepository`** over calling `run()` directly in action files. `AbdbRepository` provides all standard CRUD operations built-in and exposes `getCollection().run()` for custom queries inside a repository subclass — keeping all database access centralized in one place.
|
|
463
|
+
|
|
464
|
+
| | `run()` in action file | `AbdbRepository` (recommended) |
|
|
465
|
+
|---|---|---|
|
|
466
|
+
| Use case | Avoid — only when explicitly bypassing the repository | All database operations — insert, find, update, delete, count, pagination |
|
|
467
|
+
| Boilerplate | Raw MongoDB query logic | Built-in methods cover all common patterns |
|
|
468
|
+
| Custom queries | Direct in action (discouraged) | Override in repository subclass via `getCollection().run()` |
|
|
469
|
+
| Recommendation | Rare edge cases only | **Default choice** — use "Using AbdbRepository" rule |
|
|
470
|
+
|
|
471
|
+
> **When you need a custom query**: extend `AbdbRepository` and add a method that calls `this.getCollection().run()` — do not call `collection.run()` directly in the action file. This keeps all DB access in the repository layer.
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Key Components from @adobe-commerce/aio-toolkit
|
|
476
|
+
|
|
477
|
+
### AbdbCollection Constructor
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
new AbdbCollection(
|
|
481
|
+
name: string, // collection name — [a-zA-Z0-9_] only
|
|
482
|
+
callback?: (collection: AbdbCollection) => void // optional fluent setup function
|
|
483
|
+
)
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### AbdbCollection.addColumn()
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
// Positional form
|
|
490
|
+
addColumn(name: string, type: AbdbColumnType, description?: string, isRequired?: boolean): this
|
|
491
|
+
|
|
492
|
+
// Options object form
|
|
493
|
+
addColumn(name: string, type: AbdbColumnType, options?: { description?: string; isRequired?: boolean }): this
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
Returns `this` — chainable.
|
|
497
|
+
|
|
498
|
+
### AbdbCollection.run()
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
async run<T>(
|
|
502
|
+
callback: (collection: DbCollection, client: DbClient) => Promise<T>,
|
|
503
|
+
token: string, // IMS access token — from Core.AuthClient.generateAccessToken(params)
|
|
504
|
+
region?: string // default: 'amer' — options: 'amer', 'emea', 'apac'
|
|
505
|
+
): Promise<T>
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Connection is always closed in `finally` — never call `client.close()` manually.
|
|
509
|
+
|
|
510
|
+
**`_id` filters inside `run()`:** MongoDB stores document identifiers as `ObjectId` instances, not plain strings. If your callback filters by `_id`, you must wrap the string with `new ObjectId()` from `bson`:
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
import { ObjectId } from 'bson';
|
|
514
|
+
|
|
515
|
+
const result = await collection.run(async (col) => {
|
|
516
|
+
return await col.findOne({ _id: new ObjectId(params.id) });
|
|
517
|
+
}, accessToken);
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
> `bson` is a transitive dependency of `@adobe/aio-lib-db` — it is already present in your project and does not need to be installed separately. Plain string comparisons against `_id` will return no results.
|
|
521
|
+
|
|
522
|
+
### AbdbCollection.validate() / validateAll()
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
validate(record: Record<string, unknown>): void // throws on first error
|
|
526
|
+
validateAll(record: Record<string, unknown>): string[] // returns all errors ([] if valid)
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### AbdbColumnType Enum
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
enum AbdbColumnType {
|
|
533
|
+
STRING = 'STRING',
|
|
534
|
+
NUMBER = 'NUMBER',
|
|
535
|
+
BOOLEAN = 'BOOLEAN',
|
|
536
|
+
DATETIME = 'DATETIME',
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Exports
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
import {
|
|
544
|
+
AbdbCollection,
|
|
545
|
+
AbdbColumn,
|
|
546
|
+
AbdbColumnType,
|
|
547
|
+
} from '@adobe-commerce/aio-toolkit';
|
|
548
|
+
|
|
549
|
+
// Types (TypeScript only)
|
|
550
|
+
import type {
|
|
551
|
+
AbdbCollectionCallback,
|
|
552
|
+
AbdbRunCallback,
|
|
553
|
+
AddColumnOptions,
|
|
554
|
+
AbdbColumnOptions,
|
|
555
|
+
AbdbColumnJson,
|
|
556
|
+
} from '@adobe-commerce/aio-toolkit';
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## Important Notes
|
|
562
|
+
|
|
563
|
+
1. **Peer dependency is mandatory**: `@adobe/aio-lib-db >= 1.0.0` must be installed — `AbdbCollection` will not function without it
|
|
564
|
+
2. **`include-ims-credentials: true` is required**: `Core.AuthClient.generateAccessToken(params)` needs injected IMS credentials to generate a token for `run()`
|
|
565
|
+
3. **Always extend `AbdbCollection`**: Never use `AbdbCollection` directly in action code — always create a dedicated subclass per entity in `lib/database/collection/`
|
|
566
|
+
4. **Collection and column names are validated at construction**: Naming errors throw immediately — not at query time
|
|
567
|
+
5. **`run()` always closes the connection**: No manual cleanup needed — the `finally` block handles it regardless of success or failure
|
|
568
|
+
6. **Database must be provisioned first**: Use `aio app db provision --region amer` or `auto-provision: true` in `app.config.yaml` before the action can connect
|
|
569
|
+
7. **App Builder Data Services API must be enabled**: Enabled in Adobe Developer Console → Your project → Workspace → Services
|
|
570
|
+
8. **`0` and `false` are not missing**: Only `undefined`, `null`, and empty/whitespace strings are treated as missing for required column validation
|
|
571
|
+
9. **`validate()` throws; `validateAll()` returns errors**: Use `validateAll()` at API boundaries for better error messages; use `validate()` for internal guard checks
|
|
572
|
+
10. **Prefer `AbdbRepository` over direct `run()`**: Do not call `collection.run()` in action files — use `AbdbRepository` for all standard operations and `getCollection().run()` inside a repository subclass for custom queries
|
|
573
|
+
11. **`_id` filters require `ObjectId`**: Wrap string IDs with `new ObjectId(id)` from `bson` when filtering by `_id` inside a `run()` callback — plain strings will return no results
|
|
574
|
+
|
|
575
|
+
## Integration with Action Creation Rules
|
|
576
|
+
|
|
577
|
+
**This rule should be referenced in other action creation rules when database storage is mentioned:**
|
|
578
|
+
|
|
579
|
+
### Trigger Patterns in Other Rules
|
|
580
|
+
|
|
581
|
+
When developers mention these phrases during action creation in the "Business Logic" question:
|
|
582
|
+
|
|
583
|
+
- "Store data in App Builder Data"
|
|
584
|
+
- "Use ABDB / MongoDB"
|
|
585
|
+
- "Persist records to the database"
|
|
586
|
+
- "Define a collection schema"
|
|
587
|
+
- "Query the database"
|
|
588
|
+
- Any phrase containing "ABDB", "App Builder Data", "aio-lib-db", or "MongoDB"
|
|
589
|
+
|
|
590
|
+
**Action to take:**
|
|
591
|
+
|
|
592
|
+
1. Complete the action creation first
|
|
593
|
+
2. Then inform the user: "I see you need App Builder Data storage. Let me set up the AbdbCollection."
|
|
594
|
+
3. Trigger/reference the "Using AbdbCollection" rule
|
|
595
|
+
4. This rule handles the peer dependency, collection class, token generation, `run()` integration, and YAML config
|
|
596
|
+
|
|
597
|
+
**Example Flow:**
|
|
598
|
+
|
|
599
|
+
```
|
|
600
|
+
User: "Create a runtime action that saves queue messages to the App Builder database"
|
|
601
|
+
AI: ✓ Detected TypeScript project
|
|
602
|
+
AI: Creating runtime action... [generates index.ts]
|
|
603
|
+
AI: I see you need App Builder Data storage. Let me set up the AbdbCollection.
|
|
604
|
+
AI: [Creates lib/database/collection/queue-messages/index.ts]
|
|
605
|
+
AI: The collection schema is ready. For full CRUD (insert, find, update, delete), use the "Using AbdbRepository" rule next.
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## Related Rules
|
|
609
|
+
|
|
610
|
+
- **Using AbdbRepository**: Add full CRUD operations (insert, find, update, delete, count, pagination) on top of this collection using `AbdbRepository`
|