@aexol/axolotl 2.0.9 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,29 +1,856 @@
1
- # @aexol/axolotl (CLI)
1
+ # Axolotl Framework - LLM Integration Guide
2
2
 
3
- Command‑line interface for the Axolotl framework. It scaffolds starters, generates/inspects resolvers, runs chaos tests, and can spin up an MCP server for GraphQL.
3
+ ## Overview
4
+
5
+ Axolotl is a **type-safe, schema-first GraphQL framework** that generates TypeScript types from your GraphQL schema and provides full type safety for resolvers. This guide provides exact instructions for LLMs to work with Axolotl projects.
6
+
7
+ ## Core Concepts
8
+
9
+ ### 1. Schema-First Development
10
+
11
+ - Write GraphQL schema in `.graphql` files
12
+ - Axolotl CLI generates TypeScript types automatically
13
+ - Resolvers are fully typed based on the schema
14
+
15
+ ### 2. File Structure
16
+
17
+ ```
18
+ project/
19
+ ├── axolotl.json # Configuration file
20
+ ├── schema.graphql # GraphQL schema
21
+ ├── src/
22
+ │ ├── axolotl.ts # Framework initialization
23
+ │ ├── models.ts # Auto-generated types (DO NOT EDIT)
24
+ │ ├── resolvers.ts # Resolver implementations
25
+ │ └── index.ts # Server entry point
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Critical Rules for LLMs
31
+
32
+ **ALWAYS follow these rules when working with Axolotl:**
33
+
34
+ 1. **NEVER edit models.ts manually** - always regenerate with `axolotl build`
35
+ 2. **ALWAYS use .js extensions** in imports (ESM requirement)
36
+ 3. **ALWAYS run axolotl build** after schema changes
37
+ 4. **CRITICAL: Resolver signature is** `(input, args)` where `input = [source, args, context]`
38
+ 5. **CRITICAL: Access context as** `input[2]` or `([, , context])`
39
+ 6. **CRITICAL: Access parent/source as** `input[0]` or `([source])`
40
+ 7. **CRITICAL: Context type must** extend `YogaInitialContext` and spread `...initial`
41
+ 8. **Import from axolotl.ts** - never from @aexol/axolotl-core directly in resolver files
42
+ 9. **Use createResolvers()** for ALL resolver definitions
43
+ 10. **Use mergeAxolotls()** to combine multiple resolver sets
44
+ 11. **Return empty object `{}`** for nested resolver enablement
45
+ 12. **Context typing** requires `graphqlYogaWithContextAdapter<T>(contextFunction)`
46
+
47
+ ---
48
+
49
+ ## STEP 1: Understanding axolotl.json
50
+
51
+ The `axolotl.json` configuration file defines:
52
+
53
+ ```json
54
+ {
55
+ "schema": "schema.graphql", // Path to main schema
56
+ "models": "src/models.ts", // Where to generate types
57
+ "federation": [
58
+ // Optional: for micro-federation
59
+ {
60
+ "schema": "src/todos/schema.graphql",
61
+ "models": "src/todos/models.ts"
62
+ }
63
+ ],
64
+ "zeus": [
65
+ // Optional: GraphQL client generation
66
+ {
67
+ "generationPath": "src/"
68
+ }
69
+ ]
70
+ }
71
+ ```
72
+
73
+ **Instructions:**
74
+
75
+ - Read `axolotl.json` first to understand project structure
76
+ - NEVER edit `axolotl.json` unless explicitly asked
77
+ - Use paths from config to locate schema and models
78
+
79
+ ---
80
+
81
+ ## STEP 2: GraphQL Schema (schema.graphql)
82
+
83
+ **Example:**
84
+
85
+ ```graphql
86
+ scalar Secret
87
+
88
+ type User {
89
+ _id: String!
90
+ username: String!
91
+ }
92
+
93
+ type Query {
94
+ user: AuthorizedUserQuery @resolver
95
+ hello: String!
96
+ }
97
+
98
+ type Mutation {
99
+ login(username: String!, password: String!): String! @resolver
100
+ }
101
+
102
+ directive @resolver on FIELD_DEFINITION
103
+
104
+ schema {
105
+ query: Query
106
+ mutation: Mutation
107
+ }
108
+ ```
109
+
110
+ **Key Points:**
111
+
112
+ - This is the source of truth for your API
113
+ - The `@resolver` directive marks fields that need resolver implementations
114
+ - After modifying schema, ALWAYS run: `npx @aexol/axolotl build`
115
+
116
+ ---
117
+
118
+ ## STEP 3: Models Generation
119
+
120
+ **Command:**
121
+
122
+ ```bash
123
+ npx @aexol/axolotl build
124
+ # Or with custom directory:
125
+ npx @aexol/axolotl build --cwd path/to/project
126
+ ```
127
+
128
+ **What it does:**
129
+
130
+ - Reads `schema.graphql`
131
+ - Generates TypeScript types in `src/models.ts`
132
+ - Creates type definitions for Query, Mutation, Subscription, and all types
133
+
134
+ **Generated models.ts structure:**
135
+
136
+ ```typescript
137
+ // AUTO-GENERATED - DO NOT EDIT
138
+
139
+ export type Scalars = {
140
+ ['Secret']: unknown;
141
+ };
142
+
143
+ export type Models<S extends { [P in keyof Scalars]: any }> = {
144
+ ['User']: {
145
+ _id: { args: Record<string, never> };
146
+ username: { args: Record<string, never> };
147
+ };
148
+ ['Query']: {
149
+ hello: { args: Record<string, never> };
150
+ user: { args: Record<string, never> };
151
+ };
152
+ ['Mutation']: {
153
+ login: {
154
+ args: {
155
+ username: string;
156
+ password: string;
157
+ };
158
+ };
159
+ };
160
+ };
161
+ ```
162
+
163
+ ---
164
+
165
+ ## STEP 3.5: Generate Resolver Boilerplate (Optional but Recommended)
166
+
167
+ **Command:**
168
+
169
+ ```bash
170
+ npx @aexol/axolotl resolvers
171
+ ```
172
+
173
+ **What it does:**
174
+
175
+ - Reads your schema and finds all fields marked with `@resolver` directive
176
+ - Generates organized resolver file structure automatically
177
+ - Creates placeholder implementations for each resolver field
178
+ - Sets up proper import structure and type safety
179
+
180
+ **Generated structure example:**
181
+
182
+ Given a schema with `@resolver` directives:
183
+
184
+ ```graphql
185
+ type Query {
186
+ user: AuthorizedUserQuery @resolver
187
+ hello: String!
188
+ }
189
+
190
+ type Mutation {
191
+ login(username: String!, password: String!): String! @resolver
192
+ }
193
+ ```
194
+
195
+ The command generates:
196
+
197
+ ```
198
+ src/
199
+ ├── resolvers/
200
+ │ ├── Query/
201
+ │ │ ├── user.ts # Individual field resolver
202
+ │ │ └── resolvers.ts # Query type aggregator
203
+ │ ├── Mutation/
204
+ │ │ ├── login.ts # Individual field resolver
205
+ │ │ └── resolvers.ts # Mutation type aggregator
206
+ │ └── resolvers.ts # Root aggregator (export this)
207
+ ```
208
+
209
+ **Generated file example (Query/user.ts):**
210
+
211
+ ```typescript
212
+ import { createResolvers } from '../../axolotl.js';
213
+
214
+ export default createResolvers({
215
+ Query: {
216
+ user: async ([parent, details, ctx], args) => {
217
+ // TODO: implement resolver for Query.user
218
+ throw new Error('Not implemented: Query.user');
219
+ },
220
+ },
221
+ });
222
+ ```
223
+
224
+ **Generated aggregator (Query/resolvers.ts):**
225
+
226
+ ```typescript
227
+ import { createResolvers } from '../../axolotl.js';
228
+ import user from './user.js';
229
+
230
+ export default createResolvers({
231
+ Query: {
232
+ ...user.Query,
233
+ },
234
+ });
235
+ ```
236
+
237
+ **Root aggregator (resolvers/resolvers.ts):**
238
+
239
+ ```typescript
240
+ import { createResolvers } from '../axolotl.js';
241
+ import Query from './Query/resolvers.js';
242
+ import Mutation from './Mutation/resolvers.js';
243
+
244
+ export default createResolvers({
245
+ ...Query,
246
+ ...Mutation,
247
+ });
248
+ ```
249
+
250
+ **Key Benefits:**
251
+
252
+ - **Automatic scaffolding** - No manual file/folder creation needed
253
+ - **Organized structure** - Each resolver in its own file
254
+ - **Type safety** - All generated files use `createResolvers()` correctly
255
+ - **Non-destructive** - Only creates files that don't exist (won't overwrite your implementations)
256
+ - **Aggregator files always updated** - Type-level and root aggregators are regenerated to stay in sync
257
+
258
+ **When to use:**
259
+
260
+ - ✅ Starting a new project with many resolvers
261
+ - ✅ Adding new resolver fields to existing schema
262
+ - ✅ Want organized, maintainable resolver structure
263
+ - ✅ Working with federated schemas (generates for each module)
264
+
265
+ **Workflow:**
266
+
267
+ 1. Add `@resolver` directives to schema fields
268
+ 2. Run `npx @aexol/axolotl build` to update types
269
+ 3. Run `npx @aexol/axolotl resolvers` to scaffold structure
270
+ 4. Implement TODO sections in generated resolver files
271
+ 5. Import and use `resolvers/resolvers.ts` in your server
272
+
273
+ **Note for Federated Projects:**
274
+
275
+ The command automatically detects federation in `axolotl.json` and generates resolver structures for each federated schema in the appropriate directories.
276
+
277
+ ---
278
+
279
+ ## STEP 4: Creating axolotl.ts
280
+
281
+ **Purpose:** Initialize Axolotl framework with adapter and type definitions.
282
+
283
+ **File: src/axolotl.ts**
284
+
285
+ ### Without Custom Context (Basic)
286
+
287
+ ```typescript
288
+ import { Models, Scalars } from '@/src/models.js';
289
+ import { Axolotl } from '@aexol/axolotl-core';
290
+ import { graphqlYogaAdapter } from '@aexol/axolotl-graphql-yoga';
291
+
292
+ export const { applyMiddleware, createResolvers, createDirectives, adapter } = Axolotl(graphqlYogaAdapter)<
293
+ Models<{ Secret: number }>, // Models with scalar mappings
294
+ Scalars // Scalar type definitions
295
+ >();
296
+ ```
297
+
298
+ ### With Custom Context (Recommended)
299
+
300
+ ```typescript
301
+ import { Models, Scalars } from '@/src/models.js';
302
+ import { Axolotl } from '@aexol/axolotl-core';
303
+ import { graphqlYogaWithContextAdapter } from '@aexol/axolotl-graphql-yoga';
304
+ import { YogaInitialContext } from 'graphql-yoga';
305
+
306
+ // Define your context type - MUST extend YogaInitialContext
307
+ type AppContext = YogaInitialContext & {
308
+ userId: string | null;
309
+ isAuthenticated: boolean;
310
+ isAdmin: boolean;
311
+ requestId: string;
312
+ };
313
+
314
+ // Context builder function
315
+ async function buildContext(initial: YogaInitialContext): Promise<AppContext> {
316
+ const token = initial.request.headers.get('authorization')?.replace('Bearer ', '');
317
+ const user = token ? await verifyToken(token) : null;
318
+
319
+ return {
320
+ ...initial, // ✅ MUST spread initial context
321
+ userId: user?._id || null,
322
+ isAuthenticated: !!user,
323
+ isAdmin: user?.role === 'admin' || false,
324
+ requestId: crypto.randomUUID(),
325
+ };
326
+ }
327
+
328
+ export const { createResolvers, adapter } = Axolotl(graphqlYogaWithContextAdapter<AppContext>(buildContext))<
329
+ Models<{ Secret: number }>,
330
+ Scalars
331
+ >();
332
+ ```
333
+
334
+ **Key Components:**
335
+
336
+ 1. **Import Models & Scalars** from generated `models.ts`
337
+ 2. **Import Axolotl** from `@aexol/axolotl-core`
338
+ 3. **Import adapter** (GraphQL Yoga in this case)
339
+ 4. **Initialize with generics:**
340
+ - First generic: `Models<ScalarMap>` - your type definitions
341
+ - Second generic: `Scalars` - custom scalar types
342
+
343
+ **Exported functions:**
344
+
345
+ - `createResolvers()` - Create type-safe resolvers
346
+ - `createDirectives()` - Create custom directives
347
+ - `applyMiddleware()` - Apply middleware to resolvers
348
+ - `adapter()` - Configure and start server
349
+
350
+ **Context Type Safety:**
351
+
352
+ - `graphqlYogaWithContextAdapter<T>()` takes a **FUNCTION** (not an object)
353
+ - Your context type MUST extend `YogaInitialContext`
354
+ - The function MUST return an object that includes `...initial`
355
+ - Context is automatically typed in ALL resolvers
356
+
357
+ ---
358
+
359
+ ## STEP 5: Writing Resolvers
360
+
361
+ ### Resolver Signature
362
+
363
+ **The resolver signature is:**
364
+
365
+ ```typescript
366
+ (input, args) => ReturnType;
367
+ ```
368
+
369
+ Where:
370
+
371
+ - **`input`** is a tuple: `[source, args, context]`
372
+ - `input[0]` = **source** (parent value)
373
+ - `input[1]` = **args** (field arguments)
374
+ - `input[2]` = **context** (request context)
375
+ - **`args`** is also provided as second parameter for convenience
376
+
377
+ ### Simple Resolver Example
378
+
379
+ ```typescript
380
+ import { createResolvers } from '@/src/axolotl.js';
381
+
382
+ export default createResolvers({
383
+ Query: {
384
+ hello: async ([source, args, context]) => {
385
+ // ↑ ↑ ↑
386
+ // input[0] [1] [2]
387
+ return 'Hello, World!';
388
+ },
389
+ },
390
+ Mutation: {
391
+ login: async ([source, args, context], { username, password }) => {
392
+ // ↑ Destructure tuple ↑ Convenience args parameter
393
+ const token = await authenticateUser(username, password);
394
+ return token;
395
+ },
396
+ },
397
+ });
398
+ ```
399
+
400
+ ### Common Destructuring Patterns
401
+
402
+ ```typescript
403
+ // Pattern 1: Access context only
404
+ createResolvers({
405
+ Query: {
406
+ me: async ([, , context]) => {
407
+ return getUserById(context.userId);
408
+ },
409
+ },
410
+ });
411
+
412
+ // Pattern 2: Access source and context
413
+ createResolvers({
414
+ AuthorizedUserQuery: {
415
+ todos: async ([source, , context]) => {
416
+ const src = source as { _id: string };
417
+ return getTodosByUserId(src._id);
418
+ },
419
+ },
420
+ });
421
+
422
+ // Pattern 3: Use convenience args parameter
423
+ createResolvers({
424
+ Mutation: {
425
+ createTodo: async ([, , context], { content }) => {
426
+ return createTodo(content, context.userId);
427
+ },
428
+ },
429
+ });
430
+
431
+ // Pattern 4: Ignore unused with underscores
432
+ createResolvers({
433
+ Query: {
434
+ me: async ([_, __, context]) => {
435
+ return getUserById(context.userId);
436
+ },
437
+ },
438
+ });
439
+ ```
440
+
441
+ ### Accessing Parent (Source) in Nested Resolvers
442
+
443
+ In nested resolvers, the **parent** (also called **source**) is the value returned by the parent resolver.
444
+
445
+ ```typescript
446
+ // Schema
447
+ type Query {
448
+ user: AuthorizedUserQuery @resolver
449
+ }
450
+
451
+ type AuthorizedUserQuery {
452
+ me: User! @resolver
453
+ todos: [Todo!] @resolver
454
+ }
455
+
456
+ // Resolvers
457
+ createResolvers({
458
+ Query: {
459
+ user: async ([, , context]) => {
460
+ const token = context.request.headers.get('authorization');
461
+ const user = await verifyToken(token);
462
+
463
+ // This object becomes the SOURCE for AuthorizedUserQuery resolvers
464
+ return {
465
+ _id: user._id,
466
+ username: user.username,
467
+ };
468
+ },
469
+ },
470
+ AuthorizedUserQuery: {
471
+ me: ([source]) => {
472
+ // source is what Query.user returned
473
+ const src = source as { _id: string; username: string };
474
+ return src;
475
+ },
476
+ todos: async ([source]) => {
477
+ // Access parent data
478
+ const src = source as { _id: string };
479
+ return getTodosByUserId(src._id);
480
+ },
481
+ },
482
+ });
483
+ ```
484
+
485
+ ### Typing the Parent (Two Methods)
486
+
487
+ **Method 1: Type Assertion (Simple)**
488
+
489
+ ```typescript
490
+ type UserSource = {
491
+ _id: string;
492
+ username: string;
493
+ token?: string;
494
+ };
495
+
496
+ export default createResolvers({
497
+ AuthorizedUserQuery: {
498
+ me: ([source]) => {
499
+ const src = source as UserSource;
500
+ return {
501
+ _id: src._id,
502
+ username: src.username,
503
+ };
504
+ },
505
+ },
506
+ });
507
+ ```
508
+
509
+ **Method 2: Using setSourceTypeFromResolver (Advanced)**
510
+
511
+ ```typescript
512
+ import { createResolvers, setSourceTypeFromResolver } from '@aexol/axolotl-core';
513
+
514
+ const getUserResolver = async ([, , context]) => {
515
+ const user = await authenticateUser(context);
516
+ return {
517
+ _id: user._id,
518
+ username: user.username,
519
+ email: user.email,
520
+ };
521
+ };
522
+
523
+ const getUser = setSourceTypeFromResolver(getUserResolver);
524
+
525
+ export default createResolvers({
526
+ Query: {
527
+ user: getUserResolver,
528
+ },
529
+ AuthorizedUserQuery: {
530
+ me: ([source]) => {
531
+ const src = getUser(source); // src is now fully typed
532
+ return src;
533
+ },
534
+ },
535
+ });
536
+ ```
537
+
538
+ ### Organized Resolver Structure (Recommended)
539
+
540
+ ```typescript
541
+ // src/resolvers/Query/resolvers.ts
542
+ import { createResolvers } from '../axolotl.js';
543
+ import user from './user.js';
544
+
545
+ export default createResolvers({
546
+ Query: {
547
+ ...user.Query,
548
+ },
549
+ });
550
+
551
+ // src/resolvers/Query/user.ts
552
+ import { createResolvers } from '../axolotl.js';
553
+
554
+ export default createResolvers({
555
+ Query: {
556
+ user: async ([, , context]) => {
557
+ // Return object to enable nested resolvers
558
+ return {};
559
+ },
560
+ },
561
+ });
562
+
563
+ // Main resolvers.ts
564
+ import { mergeAxolotls } from '@aexol/axolotl-core';
565
+ import QueryResolvers from '@/src/resolvers/Query/resolvers.js';
566
+ import MutationResolvers from '@/src/resolvers/Mutation/resolvers.js';
567
+
568
+ export default mergeAxolotls(QueryResolvers, MutationResolvers);
569
+ ```
570
+
571
+ **Key Points:**
572
+
573
+ - Arguments are automatically typed from schema
574
+ - Return types must match schema definitions
575
+ - For nested resolvers, return an empty object `{}` in parent resolver
576
+ - Always use async functions (best practice)
577
+
578
+ ---
579
+
580
+ ## STEP 6: Server Configuration
581
+
582
+ **File: src/index.ts**
583
+
584
+ ### Basic Server
585
+
586
+ ```typescript
587
+ import { adapter } from '@/src/axolotl.js';
588
+ import resolvers from '@/src/resolvers.js';
589
+
590
+ const { server, yoga } = adapter(
591
+ { resolvers },
592
+ {
593
+ yoga: {
594
+ graphiql: true, // Enable GraphiQL UI
595
+ },
596
+ },
597
+ );
598
+
599
+ server.listen(4000, () => {
600
+ console.log('Server running on http://localhost:4000');
601
+ });
602
+ ```
603
+
604
+ ### With Custom Scalars
605
+
606
+ ```typescript
607
+ import { GraphQLScalarType, Kind } from 'graphql';
608
+ import { createScalars } from '@/src/axolotl.js';
609
+
610
+ const scalars = createScalars({
611
+ Secret: new GraphQLScalarType({
612
+ name: 'Secret',
613
+ serialize: (value) => String(value),
614
+ parseValue: (value) => Number(value),
615
+ parseLiteral: (ast) => {
616
+ if (ast.kind !== Kind.INT) return null;
617
+ return Number(ast.value);
618
+ },
619
+ }),
620
+ });
621
+
622
+ adapter({ resolvers, scalars });
623
+ ```
624
+
625
+ ### With Directives
626
+
627
+ ```typescript
628
+ import { createDirectives } from '@/src/axolotl.js';
629
+ import { MapperKind } from '@graphql-tools/utils';
630
+ import { defaultFieldResolver } from 'graphql';
631
+
632
+ const directives = createDirectives({
633
+ auth: (schema, getDirective) => ({
634
+ [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
635
+ const hasDirective = getDirective(schema, fieldConfig, 'auth');
636
+ if (!hasDirective) return fieldConfig;
637
+
638
+ const { resolve = defaultFieldResolver } = fieldConfig as any;
639
+ return {
640
+ ...fieldConfig,
641
+ resolve: async (source, args, context, info) => {
642
+ if (!context.userId) {
643
+ throw new Error('Not authenticated');
644
+ }
645
+ return resolve(source, args, context, info);
646
+ },
647
+ } as any;
648
+ },
649
+ }),
650
+ });
651
+
652
+ adapter({ resolvers, directives });
653
+ ```
654
+
655
+ ---
656
+
657
+ ## STEP 7: Micro-Federation (Optional)
658
+
659
+ **Purpose:** Merge multiple GraphQL schemas and resolvers into one API.
660
+
661
+ **Configuration in axolotl.json:**
662
+
663
+ ```json
664
+ {
665
+ "schema": "schema.graphql",
666
+ "models": "src/models.ts",
667
+ "federation": [
668
+ {
669
+ "schema": "src/todos/schema.graphql",
670
+ "models": "src/todos/models.ts"
671
+ },
672
+ {
673
+ "schema": "src/users/schema.graphql",
674
+ "models": "src/users/models.ts"
675
+ }
676
+ ]
677
+ }
678
+ ```
679
+
680
+ **Each module has its own:**
681
+
682
+ - `schema.graphql`
683
+ - `models.ts` (generated)
684
+ - `axolotl.ts` (module-specific initialization)
685
+ - Resolvers
686
+
687
+ **Module axolotl.ts:**
688
+
689
+ ```typescript
690
+ // src/todos/axolotl.ts
691
+ import { Models } from '@/src/todos/models.js';
692
+ import { Axolotl } from '@aexol/axolotl-core';
693
+ import { graphqlYogaAdapter } from '@aexol/axolotl-graphql-yoga';
694
+
695
+ export const { createResolvers } = Axolotl(graphqlYogaAdapter)<Models>();
696
+ ```
697
+
698
+ **Main resolvers (merge):**
699
+
700
+ ```typescript
701
+ // src/resolvers.ts
702
+ import { mergeAxolotls } from '@aexol/axolotl-core';
703
+ import todosResolvers from '@/src/todos/resolvers/resolvers.js';
704
+ import usersResolvers from '@/src/users/resolvers/resolvers.js';
705
+
706
+ export default mergeAxolotls(todosResolvers, usersResolvers);
707
+ ```
708
+
709
+ **Key Points:**
710
+
711
+ - Run `axolotl build` to generate ALL models (main + federated)
712
+ - Each module has its own axolotl.ts using its own models
713
+ - Merge all resolvers using `mergeAxolotls()`
714
+ - Schema files are merged automatically by CLI
715
+
716
+ ---
4
717
 
5
718
  ## Common Commands
6
719
 
7
- - Create starters: `axolotl create-<starter> [dir]` (see `packages/cli/create/consts.ts` for list)
8
- - Create Dockerfile: `axolotl create-dockerfile`
9
- - Build models from schema: `axolotl build`
10
- - Inspect resolvers vs schema: `axolotl inspect -s schema.graphql -r ./lib/resolvers.js`
11
- - Chaos testing against endpoint: `axolotl chaos -s schema.graphql -u http://localhost:4000/graphql`
12
- - AI resolver codegen: `axolotl ai <schemaPath> <type> <field> <prompt> [existing_resolver_path]`
13
- - Frontend/GraphQL AI helpers: `axolotl frontend-ai …`, `axolotl graphql-ai …`
14
- - MCP server for GraphQL: `axolotl mcp <schema> [--endpoint <url>] [-H "Key: Value"]`
720
+ ```bash
721
+ # Create new Axolotl project with Yoga
722
+ npx @aexol/axolotl create-yoga my-project
723
+
724
+ # Generate models from schema
725
+ npx @aexol/axolotl build
726
+
727
+ # Generate models with custom directory
728
+ npx @aexol/axolotl build --cwd path/to/project
729
+
730
+ # Generate resolver boilerplate from @resolver directives
731
+ npx @aexol/axolotl resolvers
732
+
733
+ # Inspect resolvers (find missing/extra resolvers)
734
+ npx @aexol/axolotl inspect -s schema.graphql -r src/resolvers.ts
735
+ ```
736
+
737
+ ---
738
+
739
+ ## LLM Workflow Checklist
740
+
741
+ When working with an Axolotl project:
742
+
743
+ 1. ✅ **Read axolotl.json** to understand structure
744
+ 2. ✅ **Check schema.graphql** for current schema
745
+ 3. ✅ **Verify models.ts is up-to-date** (regenerate if needed)
746
+ 4. ✅ **Locate axolotl.ts** to understand initialization
747
+ 5. ✅ **Find resolver files** and understand structure
748
+ 6. ✅ **Make schema changes** if requested
749
+ 7. ✅ **Run `axolotl build`** after schema changes
750
+ 8. ✅ **Optionally run `axolotl resolvers`** to scaffold new resolver files
751
+ 9. ✅ **Update resolvers** to match new types
752
+ 10. ✅ **Test** that server starts without type errors
753
+
754
+ ---
755
+
756
+ ## Common Patterns Cheat Sheet
757
+
758
+ ### Context Type Safety
759
+
760
+ ```typescript
761
+ // ✅ CORRECT
762
+ type AppContext = YogaInitialContext & { userId: string };
763
+
764
+ graphqlYogaWithContextAdapter<AppContext>(async (initial) => ({
765
+ ...initial,
766
+ userId: '123',
767
+ }));
768
+
769
+ // ❌ WRONG - Not extending YogaInitialContext
770
+ type AppContext = { userId: string };
771
+
772
+ // ❌ WRONG - Not spreading initial
773
+ graphqlYogaWithContextAdapter<AppContext>(async (initial) => ({
774
+ userId: '123', // Missing ...initial
775
+ }));
776
+
777
+ // ❌ WRONG - Passing object instead of function
778
+ graphqlYogaWithContextAdapter<AppContext>({ userId: '123' });
779
+ ```
780
+
781
+ ### Resolver Patterns
782
+
783
+ ```typescript
784
+ // Type-safe arguments (auto-typed from schema)
785
+ createResolvers({
786
+ Query: {
787
+ user: async ([, , context], { id, includeEmail }) => {
788
+ // id: string, includeEmail: boolean | undefined
789
+ return getUserById(id, includeEmail);
790
+ },
791
+ },
792
+ });
793
+
794
+ // Nested resolvers
795
+ createResolvers({
796
+ Query: {
797
+ user: async ([, , context]) => {
798
+ return {}; // Enable nested resolvers
799
+ },
800
+ },
801
+ UserQuery: {
802
+ me: async ([, , context]) => {
803
+ return getUserById(context.userId);
804
+ },
805
+ },
806
+ });
807
+ ```
808
+
809
+ ---
810
+
811
+ ## Troubleshooting
812
+
813
+ ### Type errors in resolvers
814
+
815
+ **Solution:** Run `npx @aexol/axolotl build` to regenerate models
816
+
817
+ ### Scalar types showing as 'unknown'
818
+
819
+ **Solution:** Map scalars in axolotl.ts:
820
+
821
+ ```typescript
822
+ Axolotl(adapter)<Models<{ MyScalar: string }>, Scalars>();
823
+ ```
824
+
825
+ ### Context type not recognized
826
+
827
+ **Solution:** Use `graphqlYogaWithContextAdapter<YourContextType>(contextFunction)`
828
+
829
+ ### Context properties undefined
15
830
 
16
- Environment for AI commands is read via `@aexol/axolotl-config` (see `packages/config`).
831
+ **Solution:** Make sure you spread `...initial` when building context
17
832
 
18
- ## Usage
833
+ ---
19
834
 
20
- - Global: `npm i -g @aexol/axolotl`
21
- - Local via npx: `npx @aexol/axolotl <command>`
835
+ ## Quick Reference
22
836
 
23
- Examples use these scripts in the workspaces under `examples/`.
837
+ | Task | Command/Code |
838
+ | -------------------- | ----------------------------------------------------------- |
839
+ | Initialize project | `npx @aexol/axolotl create-yoga <name>` |
840
+ | Generate types | `npx @aexol/axolotl build` |
841
+ | Scaffold resolvers | `npx @aexol/axolotl resolvers` |
842
+ | Create resolvers | `createResolvers({ Query: {...} })` |
843
+ | Access context | `([, , context])` - third in tuple |
844
+ | Access parent | `([source])` - first in tuple |
845
+ | Merge resolvers | `mergeAxolotls(resolvers1, resolvers2)` |
846
+ | Start server | `adapter({ resolvers }).server.listen(4000)` |
847
+ | Add custom context | `graphqlYogaWithContextAdapter<Ctx>(contextFn)` |
848
+ | Context must extend | `YogaInitialContext & { custom }` |
849
+ | Context must include | `{ ...initial, ...custom }` |
850
+ | Define scalars | `createScalars({ ScalarName: GraphQLScalarType })` |
851
+ | Define directives | `createDirectives({ directiveName: mapper })` |
852
+ | Inspect resolvers | `npx @aexol/axolotl inspect -s schema.graphql -r resolvers` |
24
853
 
25
- ## Develop
854
+ ---
26
855
 
27
- - Build: `npm run build --ws --if-present`
28
- - Test: `npm test`
29
- - Lint: `npx eslint packages/cli`
856
+ This guide provides everything an LLM needs to work effectively with Axolotl projects, from understanding the structure to implementing resolvers with full type safety.