@adobe-commerce/aio-toolkit 1.0.14 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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
+