@adobe-commerce/aio-toolkit 1.0.14 → 1.0.16
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 +90 -0
- package/README.md +218 -13
- package/dist/aio-toolkit-cursor-context/bin/cli.js +1168 -0
- package/dist/aio-toolkit-cursor-context/bin/cli.js.map +1 -0
- package/dist/aio-toolkit-onboard-events/bin/cli.js +14611 -0
- package/dist/aio-toolkit-onboard-events/bin/cli.js.map +1 -0
- package/files/cursor-context/commands/aio-toolkit-create-event-consumer-action.md +562 -0
- package/files/cursor-context/commands/aio-toolkit-create-graphql-action.md +531 -0
- package/files/cursor-context/commands/aio-toolkit-create-runtime-action.md +293 -0
- package/files/cursor-context/commands/aio-toolkit-create-webhook-action.md +439 -0
- package/files/cursor-context/rules/aio-toolkit-create-adobe-commerce-client.mdc +1321 -0
- package/files/cursor-context/rules/aio-toolkit-oop-best-practices.mdc +331 -0
- package/files/cursor-context/rules/aio-toolkit-setup-new-relic-telemetry.mdc +354 -0
- package/onboard.config.default.yaml +31 -0
- package/package.json +8 -1
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
# AIO Toolkit: Create GraphQL Action
|
|
2
|
+
|
|
3
|
+
**Command Name:** `aio-toolkit-create-graphql-action`
|
|
4
|
+
|
|
5
|
+
**Description:** Creates or extends a GraphQL action using @adobe-commerce/aio-toolkit with queries, mutations, and API Gateway
|
|
6
|
+
|
|
7
|
+
## Workflow
|
|
8
|
+
|
|
9
|
+
This command creates a new GraphQL action or adds queries/mutations to an existing GraphQL implementation.
|
|
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. **Check for existing GraphQL action**
|
|
16
|
+
- Search for `GraphQlAction.execute` in the project
|
|
17
|
+
- If found: Proceed to add queries/mutations/fields to existing action
|
|
18
|
+
- If not found: Create new GraphQL action
|
|
19
|
+
3. Detect project language (TypeScript or JavaScript)
|
|
20
|
+
- Check for `typescript` in dependencies + `tsconfig.json`
|
|
21
|
+
- Check for `.ts` files in `actions/` or `lib/`
|
|
22
|
+
- Default to JavaScript if ambiguous
|
|
23
|
+
4. Detect project structure
|
|
24
|
+
- Check for `application:` in `app.config.yaml` (root actions)
|
|
25
|
+
- Check for `extensions:` in `app.config.yaml` (extension point actions)
|
|
26
|
+
|
|
27
|
+
### Step 2: Collect Configuration
|
|
28
|
+
|
|
29
|
+
**Important**: GraphQL actions are always in the `actions/application/` directory.
|
|
30
|
+
|
|
31
|
+
#### If Creating NEW GraphQL Action:
|
|
32
|
+
|
|
33
|
+
Ask the user:
|
|
34
|
+
|
|
35
|
+
1. **Action Location** (auto-detect or ask)
|
|
36
|
+
- Root application (`actions/application/`)
|
|
37
|
+
- Extension point (`[extension-path]/actions/application/`)
|
|
38
|
+
|
|
39
|
+
2. **Initial Operation Type**
|
|
40
|
+
- Query (fetch data)
|
|
41
|
+
- Mutation (modify data)
|
|
42
|
+
|
|
43
|
+
3. **Operation Name** (required)
|
|
44
|
+
- For Query: `getUser`, `listProducts`, `helloWorld`
|
|
45
|
+
- For Mutation: `createUser`, `updateProduct`, `deleteOrder`
|
|
46
|
+
- Used for resolver class name (PascalCase) and operation name (camelCase)
|
|
47
|
+
|
|
48
|
+
4. **Return Type** (required)
|
|
49
|
+
- Scalar types: `String`, `Int`, `Boolean`, `Float`, `ID`
|
|
50
|
+
- Custom types: `User`, `Product`, `Order`
|
|
51
|
+
- Arrays: `[Product]`, `[String]`
|
|
52
|
+
- If custom type, ask: What fields does this type have?
|
|
53
|
+
|
|
54
|
+
5. **Type Definitions** (if custom type)
|
|
55
|
+
- Field definitions with types
|
|
56
|
+
- Example: `User` type with `id: ID!, name: String!, email: String`
|
|
57
|
+
- Format: `fieldName: Type` (use `!` for required fields)
|
|
58
|
+
- Ask for description for the type and each field
|
|
59
|
+
|
|
60
|
+
6. **Mutation Input Parameters** (if Mutation)
|
|
61
|
+
- Parameter names and types
|
|
62
|
+
- Example: `userId: ID!, name: String, email: String`
|
|
63
|
+
- Format: `paramName: Type`
|
|
64
|
+
|
|
65
|
+
7. **Description** (required)
|
|
66
|
+
- Short (one line): Use `"` format
|
|
67
|
+
- Long (multi-line): Use `"""` format
|
|
68
|
+
- Appears in introspection and GraphiQL documentation
|
|
69
|
+
|
|
70
|
+
8. **Business Logic Description**
|
|
71
|
+
- Brief description of what the operation should do
|
|
72
|
+
|
|
73
|
+
9. **Introspection**
|
|
74
|
+
- Enable (default, recommended for development)
|
|
75
|
+
- Disable (secure for production, prevents schema exploration)
|
|
76
|
+
- Note: When enabled, tools like GraphiQL can explore the schema
|
|
77
|
+
|
|
78
|
+
#### If Adding to EXISTING GraphQL Action:
|
|
79
|
+
|
|
80
|
+
Ask the user:
|
|
81
|
+
|
|
82
|
+
1. **What to Add?**
|
|
83
|
+
- New Query
|
|
84
|
+
- New Mutation
|
|
85
|
+
- New Field to existing Query/Mutation
|
|
86
|
+
- New Field to custom Type
|
|
87
|
+
|
|
88
|
+
2. **For New Query/Mutation**:
|
|
89
|
+
- Ask questions 3-8 from "Creating NEW" section above
|
|
90
|
+
|
|
91
|
+
3. **For New Field to Query/Mutation**:
|
|
92
|
+
- **Parent Operation**: Which operation? (list existing from schema)
|
|
93
|
+
- **Field Name**: Name of the new field
|
|
94
|
+
- **Field Type**: Type of the field
|
|
95
|
+
- **Description**: Field description
|
|
96
|
+
- **Business Logic**: What should this field resolver do?
|
|
97
|
+
|
|
98
|
+
4. **For New Field to Custom Type**:
|
|
99
|
+
- **Type Name**: Which custom type? (list existing from schema)
|
|
100
|
+
- **Field Name**: Name of the new field
|
|
101
|
+
- **Field Type**: Type of the field
|
|
102
|
+
- **Description**: Field description
|
|
103
|
+
- **Business Logic**: What should this field resolver do?
|
|
104
|
+
|
|
105
|
+
### Step 3: Confirm Configuration
|
|
106
|
+
|
|
107
|
+
Display summary:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
📋 GraphQL Action Configuration
|
|
111
|
+
|
|
112
|
+
Action Type: [New GraphQL Action / Add to Existing]
|
|
113
|
+
Language: [JavaScript/TypeScript] (auto-detected)
|
|
114
|
+
Location: [Root Application / Extension Point]
|
|
115
|
+
|
|
116
|
+
[If New Action]
|
|
117
|
+
Operation Type: [Query/Mutation]
|
|
118
|
+
Operation Name: [operationName]
|
|
119
|
+
Return Type: [returnType]
|
|
120
|
+
[If custom type]
|
|
121
|
+
Custom Type: [TypeName]
|
|
122
|
+
Fields: [field definitions]
|
|
123
|
+
[If mutation]
|
|
124
|
+
Input Parameters: [params]
|
|
125
|
+
Description: [description]
|
|
126
|
+
Introspection: [Enabled/Disabled]
|
|
127
|
+
|
|
128
|
+
[If Adding to Existing]
|
|
129
|
+
Adding: [New Query/Mutation/Field]
|
|
130
|
+
To: [existing operation or type]
|
|
131
|
+
Details: [relevant details]
|
|
132
|
+
|
|
133
|
+
API Gateway:
|
|
134
|
+
- Endpoint: GET/POST /apis/[namespace]/v1/application/graphql
|
|
135
|
+
- Methods: GET, POST
|
|
136
|
+
- Response Type: http
|
|
137
|
+
- Provisioning: 5-10 minutes
|
|
138
|
+
|
|
139
|
+
✅ Files to Create/Update:
|
|
140
|
+
[If new action]
|
|
141
|
+
- actions/application/graphql/index.[js/ts]
|
|
142
|
+
- actions/application/resolvers/[OperationName].[js/ts]
|
|
143
|
+
- actions/application/actions.config.yaml
|
|
144
|
+
- actions/application/apis.config.yaml
|
|
145
|
+
- Update app.config.yaml or ext.config.yaml
|
|
146
|
+
|
|
147
|
+
[If adding to existing]
|
|
148
|
+
- Update: actions/application/graphql/index.[js/ts] (schema)
|
|
149
|
+
- Create: actions/application/resolvers/[OperationName].[js/ts]
|
|
150
|
+
|
|
151
|
+
Should I proceed?
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Step 4: Generate GraphQL Action
|
|
155
|
+
|
|
156
|
+
**Directory Structure:**
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
actions/
|
|
160
|
+
└── application/
|
|
161
|
+
├── actions.config.yaml
|
|
162
|
+
├── apis.config.yaml
|
|
163
|
+
├── graphql/
|
|
164
|
+
│ └── index.[js/ts]
|
|
165
|
+
└── resolvers/
|
|
166
|
+
├── HelloWorld.[js/ts]
|
|
167
|
+
└── [OperationName].[js/ts]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### GraphQL Action Template
|
|
171
|
+
|
|
172
|
+
**JavaScript** (`actions/application/graphql/index.js`):
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
const { GraphQlAction } = require('@adobe-commerce/aio-toolkit');
|
|
176
|
+
const HelloWorld = require('../resolvers/HelloWorld');
|
|
177
|
+
|
|
178
|
+
exports.main = GraphQlAction.execute(
|
|
179
|
+
`type Query {
|
|
180
|
+
"Just a sample query."
|
|
181
|
+
helloWorld: String
|
|
182
|
+
}`,
|
|
183
|
+
async ctx => {
|
|
184
|
+
const helloWorld = new HelloWorld(ctx);
|
|
185
|
+
return {
|
|
186
|
+
helloWorld: await helloWorld.execute(),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// Optional 3rd parameter: name (default: 'main')
|
|
190
|
+
// Optional 4th parameter: disableIntrospection (default: false)
|
|
191
|
+
// Add 4th param as true to disable introspection for production
|
|
192
|
+
);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**For Mutations**, use `type Mutation { ... }` instead of `type Query`.
|
|
196
|
+
|
|
197
|
+
**For Custom Types**, add type definitions:
|
|
198
|
+
```javascript
|
|
199
|
+
exports.main = GraphQlAction.execute(
|
|
200
|
+
`type Query {
|
|
201
|
+
"Returns user information by ID."
|
|
202
|
+
getUser(id: ID!): User
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
"Contains details about a user."
|
|
206
|
+
type User {
|
|
207
|
+
"The user identifier."
|
|
208
|
+
id: ID!
|
|
209
|
+
"The name of the user."
|
|
210
|
+
name: String!
|
|
211
|
+
"The email address of the user."
|
|
212
|
+
email: String
|
|
213
|
+
}`,
|
|
214
|
+
async ctx => {
|
|
215
|
+
const getUser = new GetUser(ctx);
|
|
216
|
+
return { getUser: await getUser.execute() };
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**TypeScript:** Same with type annotations and `import` syntax
|
|
222
|
+
|
|
223
|
+
#### Resolver Template
|
|
224
|
+
|
|
225
|
+
**JavaScript** (`actions/application/resolvers/[OperationName].js`):
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
class HelloWorld {
|
|
229
|
+
constructor(ctx) {
|
|
230
|
+
this.ctx = ctx;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async execute() {
|
|
234
|
+
return async (args) => {
|
|
235
|
+
const { logger } = this.ctx;
|
|
236
|
+
logger.info({ message: 'helloWorld-execution', args: JSON.stringify(args) });
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
// TODO: Implement resolver logic
|
|
240
|
+
const result = 'Hello World!';
|
|
241
|
+
|
|
242
|
+
logger.info({ message: 'helloWorld-success' });
|
|
243
|
+
return result;
|
|
244
|
+
} catch (error) {
|
|
245
|
+
logger.error({
|
|
246
|
+
message: 'helloWorld-error',
|
|
247
|
+
error: error.message,
|
|
248
|
+
stack: error.stack
|
|
249
|
+
});
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = HelloWorld;
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**For mutations with arguments:**
|
|
260
|
+
```javascript
|
|
261
|
+
async execute() {
|
|
262
|
+
return async (args) => {
|
|
263
|
+
const { userId, name, email } = args;
|
|
264
|
+
// Use arguments in business logic
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**TypeScript:** Same with type annotations, use `export default`
|
|
270
|
+
|
|
271
|
+
### Step 5: Update Configuration Files
|
|
272
|
+
|
|
273
|
+
#### Actions Configuration
|
|
274
|
+
|
|
275
|
+
Create `actions/application/actions.config.yaml`:
|
|
276
|
+
|
|
277
|
+
```yaml
|
|
278
|
+
graphql:
|
|
279
|
+
function: graphql/index.[js/ts]
|
|
280
|
+
web: 'yes'
|
|
281
|
+
runtime: nodejs:22
|
|
282
|
+
inputs:
|
|
283
|
+
LOG_LEVEL: debug
|
|
284
|
+
annotations:
|
|
285
|
+
require-adobe-auth: false
|
|
286
|
+
final: true
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### API Gateway Configuration
|
|
290
|
+
|
|
291
|
+
Create `actions/application/apis.config.yaml`:
|
|
292
|
+
|
|
293
|
+
```yaml
|
|
294
|
+
graphql:
|
|
295
|
+
v1:
|
|
296
|
+
application/graphql:
|
|
297
|
+
graphql:
|
|
298
|
+
method: get,post
|
|
299
|
+
response: http
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
#### Main Configuration
|
|
303
|
+
|
|
304
|
+
Update `app.config.yaml` (or `ext.config.yaml` for extensions):
|
|
305
|
+
|
|
306
|
+
```yaml
|
|
307
|
+
application:
|
|
308
|
+
actions: actions
|
|
309
|
+
web: web-src
|
|
310
|
+
runtimeManifest:
|
|
311
|
+
packages:
|
|
312
|
+
application:
|
|
313
|
+
license: Apache-2.0
|
|
314
|
+
actions:
|
|
315
|
+
$include: ./actions/application/actions.config.yaml
|
|
316
|
+
apis:
|
|
317
|
+
$include: ./actions/application/apis.config.yaml
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**For Extension Points:**
|
|
321
|
+
- Actions in: `[extension-path]/actions/application/`
|
|
322
|
+
- If TypeScript: Config references `../../../../build/actions/application/graphql/index.js`
|
|
323
|
+
- Update `[extension-path]/ext.config.yaml` with same structure
|
|
324
|
+
|
|
325
|
+
### Step 6: Adding to Existing GraphQL Action
|
|
326
|
+
|
|
327
|
+
When adding to an existing GraphQL action:
|
|
328
|
+
|
|
329
|
+
1. **Update Schema**: Add new query/mutation/field to schema string
|
|
330
|
+
2. **Import Resolver**: Add resolver import at top of file
|
|
331
|
+
3. **Instantiate**: Create resolver instance in async function
|
|
332
|
+
4. **Return**: Add to return object
|
|
333
|
+
5. **Create Resolver File**: Create new resolver class file
|
|
334
|
+
|
|
335
|
+
**Example:**
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
// Before
|
|
339
|
+
const HelloWorld = require('../resolvers/HelloWorld');
|
|
340
|
+
|
|
341
|
+
exports.main = GraphQlAction.execute(
|
|
342
|
+
`type Query {
|
|
343
|
+
"Just a sample query."
|
|
344
|
+
helloWorld: String
|
|
345
|
+
}`,
|
|
346
|
+
async ctx => {
|
|
347
|
+
const helloWorld = new HelloWorld(ctx);
|
|
348
|
+
return { helloWorld: await helloWorld.execute() };
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
// After - Adding getUser query
|
|
353
|
+
const HelloWorld = require('../resolvers/HelloWorld');
|
|
354
|
+
const GetUser = require('../resolvers/GetUser'); // New import
|
|
355
|
+
|
|
356
|
+
exports.main = GraphQlAction.execute(
|
|
357
|
+
`type Query {
|
|
358
|
+
"Just a sample query."
|
|
359
|
+
helloWorld: String
|
|
360
|
+
|
|
361
|
+
"Returns user information by ID."
|
|
362
|
+
getUser(id: ID!): User
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
"Contains details about a user."
|
|
366
|
+
type User {
|
|
367
|
+
"The user identifier."
|
|
368
|
+
id: ID!
|
|
369
|
+
"The name of the user."
|
|
370
|
+
name: String!
|
|
371
|
+
"The email address of the user."
|
|
372
|
+
email: String
|
|
373
|
+
}`,
|
|
374
|
+
async ctx => {
|
|
375
|
+
const helloWorld = new HelloWorld(ctx);
|
|
376
|
+
const getUser = new GetUser(ctx); // New instantiation
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
helloWorld: await helloWorld.execute(),
|
|
380
|
+
getUser: await getUser.execute(), // New resolver
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Step 7: Completion
|
|
387
|
+
|
|
388
|
+
Display:
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
✅ GraphQL Action Created Successfully!
|
|
392
|
+
|
|
393
|
+
📁 Files Created/Updated:
|
|
394
|
+
[If new]
|
|
395
|
+
- actions/application/graphql/index.[js/ts]
|
|
396
|
+
- actions/application/resolvers/[OperationName].[js/ts]
|
|
397
|
+
- actions/application/actions.config.yaml
|
|
398
|
+
- actions/application/apis.config.yaml
|
|
399
|
+
- Updated: app.config.yaml or ext.config.yaml
|
|
400
|
+
|
|
401
|
+
[If adding to existing]
|
|
402
|
+
- Updated: actions/application/graphql/index.[js/ts]
|
|
403
|
+
- Created: actions/application/resolvers/[OperationName].[js/ts]
|
|
404
|
+
|
|
405
|
+
🚀 Next Steps:
|
|
406
|
+
1. Implement resolver business logic
|
|
407
|
+
2. Test locally: aio app dev
|
|
408
|
+
3. Test GraphQL endpoint (see below)
|
|
409
|
+
4. Deploy: aio app deploy
|
|
410
|
+
5. Wait 5-10 minutes for API Gateway provisioning
|
|
411
|
+
|
|
412
|
+
🌐 GraphQL Endpoints:
|
|
413
|
+
- Local: http://localhost:9080/api/v1/web/[namespace]/application/graphql
|
|
414
|
+
- API Gateway: https://[runtime-host]/apis/[namespace]/v1/application/graphql
|
|
415
|
+
|
|
416
|
+
📝 Testing GraphQL:
|
|
417
|
+
- Use Postman, Insomnia, or GraphiQL
|
|
418
|
+
- Body: {"query": "{ helloWorld }"}
|
|
419
|
+
- Body: {"query": "{ getUser(id: \"123\") { id name email } }"}
|
|
420
|
+
- Introspection query: {"query": "{ __schema { types { name } } }"}
|
|
421
|
+
|
|
422
|
+
📖 Documentation:
|
|
423
|
+
- GraphQlAction: @adobe-commerce/aio-toolkit
|
|
424
|
+
- GraphQL: https://graphql.org/learn/
|
|
425
|
+
|
|
426
|
+
💡 GraphQL Features:
|
|
427
|
+
- Introspection: [Enabled/Disabled]
|
|
428
|
+
- Supports GET and POST methods
|
|
429
|
+
- Variable support for complex queries
|
|
430
|
+
- Named operations
|
|
431
|
+
- Schema validation
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Key Features
|
|
435
|
+
|
|
436
|
+
- **Auto-detection**: Language (TS/JS) and project structure
|
|
437
|
+
- **Flexible**: Create new or extend existing GraphQL actions
|
|
438
|
+
- **One GraphQL Action Per Project**: All queries, mutations, types in single schema
|
|
439
|
+
- **Class-based Resolvers**: Constructor with context, execute method returning async function
|
|
440
|
+
- **Schema Descriptions**: Single-line (`"`) or multi-line (`"""`) for documentation
|
|
441
|
+
- **Introspection**: Enabled by default (disable for production)
|
|
442
|
+
- **API Gateway**: Automatic endpoint creation at `/apis/[namespace]/v1/application/graphql`
|
|
443
|
+
- **Best Practices**: Structured logging, error handling, telemetry-ready
|
|
444
|
+
|
|
445
|
+
### GraphQL Schema Types
|
|
446
|
+
|
|
447
|
+
**Scalar Types:**
|
|
448
|
+
- `String` - Text data
|
|
449
|
+
- `Int` - Integer numbers
|
|
450
|
+
- `Float` - Floating-point numbers
|
|
451
|
+
- `Boolean` - true/false
|
|
452
|
+
- `ID` - Unique identifier
|
|
453
|
+
|
|
454
|
+
**Type Modifiers:**
|
|
455
|
+
- `Type!` - Required (non-nullable)
|
|
456
|
+
- `[Type]` - Array of Type
|
|
457
|
+
- `[Type]!` - Required array
|
|
458
|
+
- `[Type!]!` - Required array of required items
|
|
459
|
+
|
|
460
|
+
**Example Schema:**
|
|
461
|
+
|
|
462
|
+
```graphql
|
|
463
|
+
type Query {
|
|
464
|
+
"Get a single user by ID"
|
|
465
|
+
getUser(id: ID!): User
|
|
466
|
+
|
|
467
|
+
"Search for users"
|
|
468
|
+
searchUsers(name: String): [User]
|
|
469
|
+
|
|
470
|
+
"""
|
|
471
|
+
Get all products with pagination
|
|
472
|
+
Returns a list of products
|
|
473
|
+
"""
|
|
474
|
+
listProducts(page: Int, limit: Int): [Product]!
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
type Mutation {
|
|
478
|
+
"Create a new user"
|
|
479
|
+
createUser(name: String!, email: String!): User
|
|
480
|
+
|
|
481
|
+
"Update user information"
|
|
482
|
+
updateUser(id: ID!, name: String, email: String): User
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
"User entity with personal information"
|
|
486
|
+
type User {
|
|
487
|
+
"Unique user identifier"
|
|
488
|
+
id: ID!
|
|
489
|
+
"User's full name"
|
|
490
|
+
name: String!
|
|
491
|
+
"User's email address"
|
|
492
|
+
email: String
|
|
493
|
+
"List of user's orders"
|
|
494
|
+
orders: [Order]
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Resolver Pattern
|
|
499
|
+
|
|
500
|
+
Resolvers follow this class-based pattern:
|
|
501
|
+
|
|
502
|
+
```javascript
|
|
503
|
+
class ResolverName {
|
|
504
|
+
constructor(ctx) {
|
|
505
|
+
this.ctx = ctx; // Context with logger, headers, params
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async execute() {
|
|
509
|
+
return async (args) => {
|
|
510
|
+
// args = GraphQL operation arguments
|
|
511
|
+
const { logger } = this.ctx;
|
|
512
|
+
|
|
513
|
+
// Business logic here
|
|
514
|
+
// Can access: logger, this.ctx.headers, this.ctx.params
|
|
515
|
+
|
|
516
|
+
return result; // Return data matching GraphQL return type
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**Key Points:**
|
|
523
|
+
- Constructor receives context from GraphQlAction
|
|
524
|
+
- execute() returns async function that receives GraphQL arguments
|
|
525
|
+
- Throw errors for GraphQL error handling
|
|
526
|
+
- Use structured logging
|
|
527
|
+
|
|
528
|
+
### Related Rules
|
|
529
|
+
|
|
530
|
+
- **Setting up New Relic Telemetry**: Add observability to your GraphQL action
|
|
531
|
+
|