@magek/mcp-server 0.0.10 → 0.0.11

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.
@@ -94,10 +94,13 @@ npx magek new:entity <EntityName> --fields <field1:type1> <field2:type2> --reduc
94
94
  **Best Practices:**
95
95
  - One entity per aggregate root
96
96
  - Entities should only contain state derived from events
97
- - Use the \`@Reduces\` decorator to specify which events affect this entity
97
+ - Use the \`@reduces\` decorator to specify which events affect this entity
98
+ - **Always use \`evolve()\` for state updates** - it ensures immutability
98
99
 
99
100
  **Example:**
100
101
  \`\`\`typescript
102
+ import { evolve } from '@magek/common'
103
+
101
104
  @Entity
102
105
  export class <EntityName> {
103
106
  public constructor(
@@ -106,20 +109,22 @@ export class <EntityName> {
106
109
  readonly field2: number,
107
110
  ) {}
108
111
 
109
- @Reduces(<EventName>)
112
+ @reduces(<EventName>)
110
113
  public static reduce<EventName>(
111
114
  event: <EventName>,
112
115
  currentEntity?: <EntityName>
113
116
  ): <EntityName> {
114
- return new <EntityName>(
115
- event.entityId,
116
- event.field1,
117
- event.field2,
118
- )
117
+ return evolve(currentEntity, {
118
+ id: event.entityId,
119
+ field1: event.field1,
120
+ field2: event.field2,
121
+ })
119
122
  }
120
123
  }
121
124
  \`\`\`
122
125
 
126
+ > **Important:** Always use \`evolve()\` from \`@magek/common\` for state updates. It ensures immutability and handles both new entities (when \`currentEntity\` is undefined) and updates.
127
+
123
128
  ## Step 4: Define the Read Model
124
129
 
125
130
  Read models provide optimized query access to your data.
@@ -132,9 +137,12 @@ npx magek new:read-model <ReadModelName> --fields <field1:type1> <field2:type2>
132
137
  - Design read models for specific query use cases
133
138
  - Include only the fields needed for queries
134
139
  - You can have multiple read models projecting the same entity
140
+ - **Use \`evolve()\` for projection updates** - same pattern as entities
135
141
 
136
142
  **Example:**
137
143
  \`\`\`typescript
144
+ import { evolve } from '@magek/common'
145
+
138
146
  @ReadModel({
139
147
  authorize: 'all'
140
148
  })
@@ -145,16 +153,16 @@ export class <ReadModelName> {
145
153
  readonly field2: number,
146
154
  ) {}
147
155
 
148
- @Projects(<EntityName>, 'id')
156
+ @projects(<EntityName>, 'id')
149
157
  public static project<EntityName>(
150
158
  entity: <EntityName>,
151
159
  currentReadModel?: <ReadModelName>
152
160
  ): ProjectionResult<<ReadModelName>> {
153
- return new <ReadModelName>(
154
- entity.id,
155
- entity.field1,
156
- entity.field2,
157
- )
161
+ return evolve(currentReadModel, {
162
+ id: entity.id,
163
+ field1: entity.field1,
164
+ field2: entity.field2,
165
+ })
158
166
  }
159
167
  }
160
168
  \`\`\`
@@ -43,13 +43,18 @@ export * from './read-models/ProductReadModel'
43
43
  3. Ensure the reduce method returns a new entity instance (not mutating)
44
44
 
45
45
  \`\`\`typescript
46
+ import { evolve } from '@magek/common'
47
+
46
48
  @Entity
47
49
  export class Product {
48
50
  // Make sure decorator references the actual event class
49
51
  @Reduces(ProductCreated) // ✅ Correct
50
52
  @Reduces('ProductCreated') // ❌ Won't work
51
- public static reduceProductCreated(event: ProductCreated): Product {
52
- return new Product(event.entityId, event.name)
53
+ public static reduceProductCreated(event: ProductCreated, current?: Product): Product {
54
+ return evolve(current, {
55
+ id: event.entityId,
56
+ name: event.name,
57
+ })
53
58
  }
54
59
  }
55
60
  \`\`\`
@@ -78,8 +83,9 @@ export class Product {
78
83
  1. Check the \`authorize\` option in decorators:
79
84
  \`\`\`typescript
80
85
  @Command({ authorize: 'all' }) // Public access
81
- @Command({ authorize: 'authenticated' }) // Requires login
82
- @Command({ authorize: ['admin'] }) // Requires admin role
86
+ @Command({ authorize: [User] }) // Requires User role
87
+ @Command({ authorize: [Admin] }) // Requires Admin role
88
+ @Command({ authorize: [User, Admin] }) // Requires User OR Admin role
83
89
  \`\`\`
84
90
 
85
91
  2. If using authentication, ensure JWT token is valid
@@ -138,6 +144,39 @@ export class Product {
138
144
  }
139
145
  \`\`\`
140
146
 
147
+ ### Reducer returns incorrect state
148
+
149
+ **Symptoms:**
150
+ - Entity state not updating as expected
151
+ - State appears corrupted or missing fields
152
+ - Immutability violations
153
+
154
+ **Solutions:**
155
+ 1. **Always use \`evolve()\` for state updates** - never mutate directly:
156
+ \`\`\`typescript
157
+ // ✅ Correct - use evolve()
158
+ return evolve(current, { name: event.newName })
159
+
160
+ // ❌ Incorrect - mutates state directly
161
+ current.name = event.newName
162
+ return current
163
+
164
+ // ❌ Incorrect - manual construction doesn't handle undefined
165
+ return new Product(event.id, event.name)
166
+ \`\`\`
167
+
168
+ 2. Handle the case when \`current\` is undefined (new entity):
169
+ \`\`\`typescript
170
+ // evolve() handles this automatically
171
+ return evolve(current, { id: event.id, name: event.name })
172
+ \`\`\`
173
+
174
+ 3. For update-only reducers, skip if entity doesn't exist:
175
+ \`\`\`typescript
176
+ if (!current) return ReducerAction.Skip
177
+ return evolve(current, { name: event.newName })
178
+ \`\`\`
179
+
141
180
  ---
142
181
 
143
182
  ## Development Server Issues
@@ -0,0 +1,371 @@
1
+ # Custom Adapters
2
+
3
+ Magek uses an adapter pattern to abstract away database and storage implementation details. This allows you to use different storage backends without modifying your application code. This guide explains how to create custom adapters for the Magek framework.
4
+
5
+ ## Overview
6
+
7
+ Magek has three types of adapters:
8
+
9
+ 1. **Event Store Adapter** - Stores events and entity snapshots for event sourcing
10
+ 2. **Read Model Store Adapter** - Stores and queries read model projections
11
+ 3. **Session Store Adapter** - Manages WebSocket connections and GraphQL subscriptions
12
+
13
+ Each adapter type has a well-defined interface that you must implement. The framework provides built-in adapters for NeDB (file-based) and in-memory storage.
14
+
15
+ ## Adapter Interfaces
16
+
17
+ ### Event Store Adapter
18
+
19
+ The `EventStoreAdapter` interface (defined in `@magek/common`) handles event sourcing operations:
20
+
21
+ ```typescript
22
+ interface EventStoreAdapter {
23
+ // Convert raw data to EventEnvelope objects
24
+ rawToEnvelopes(rawEvents: unknown): Array<EventEnvelope>
25
+
26
+ // Streaming methods (optional - can throw "not implemented")
27
+ rawStreamToEnvelopes(config: MagekConfig, context: unknown, dedupEventStream: EventStream): Array<EventEnvelope>
28
+ dedupEventStream(config: MagekConfig, rawEvents: unknown): Promise<EventStream>
29
+ produce(entityName: string, entityID: UUID, eventEnvelopes: Array<EventEnvelope>, config: MagekConfig): Promise<void>
30
+
31
+ // Core event operations
32
+ forEntitySince(config: MagekConfig, entityTypeName: string, entityID: UUID, since?: string): Promise<Array<EventEnvelope>>
33
+ latestEntitySnapshot(config: MagekConfig, entityTypeName: string, entityID: UUID): Promise<EntitySnapshotEnvelope | undefined>
34
+ store(eventEnvelopes: Array<NonPersistedEventEnvelope>, config: MagekConfig): Promise<Array<EventEnvelope>>
35
+ storeSnapshot(snapshotEnvelope: NonPersistedEntitySnapshotEnvelope, config: MagekConfig): Promise<EntitySnapshotEnvelope>
36
+
37
+ // Search and pagination
38
+ search(config: MagekConfig, parameters: EventSearchParameters): Promise<Array<EventSearchResponse>>
39
+ searchEntitiesIDs(config: MagekConfig, limit: number, afterCursor: Record<string, string> | undefined, entityTypeName: string): Promise<PaginatedEntitiesIdsResult>
40
+
41
+ // Dispatch tracking
42
+ storeDispatched(eventEnvelope: EventEnvelope, config: MagekConfig): Promise<boolean>
43
+
44
+ // Deletion operations
45
+ findDeletableEvent(config: MagekConfig, parameters: EventDeleteParameters): Promise<Array<EventEnvelopeFromDatabase>>
46
+ findDeletableSnapshot(config: MagekConfig, parameters: SnapshotDeleteParameters): Promise<Array<EntitySnapshotEnvelopeFromDatabase>>
47
+ deleteEvent(config: MagekConfig, events: Array<EventEnvelopeFromDatabase>): Promise<void>
48
+ deleteSnapshot(config: MagekConfig, snapshots: Array<EntitySnapshotEnvelopeFromDatabase>): Promise<void>
49
+
50
+ // Health check (optional)
51
+ healthCheck?: {
52
+ isUp(config: MagekConfig): Promise<boolean>
53
+ details(config: MagekConfig): Promise<unknown>
54
+ urls(config: MagekConfig): Promise<Array<string>>
55
+ }
56
+
57
+ // Async event processing (optional)
58
+ fetchUnprocessedEvents?(config: MagekConfig): Promise<Array<EventEnvelope>>
59
+ markEventProcessed?(config: MagekConfig, eventId: UUID): Promise<void>
60
+ }
61
+ ```
62
+
63
+ ### Read Model Store Adapter
64
+
65
+ The `ReadModelStoreAdapter` interface handles read model projections:
66
+
67
+ ```typescript
68
+ interface ReadModelStoreAdapter {
69
+ // Fetch a specific read model by ID
70
+ fetch<TReadModel extends ReadModelInterface>(
71
+ config: MagekConfig,
72
+ readModelName: string,
73
+ readModelID: UUID,
74
+ sequenceKey?: SequenceKey
75
+ ): Promise<ReadOnlyNonEmptyArray<TReadModel> | undefined>
76
+
77
+ // Search read models with filters, sorting, and pagination
78
+ search<TReadModel extends ReadModelInterface>(
79
+ config: MagekConfig,
80
+ readModelName: string,
81
+ filters: FilterFor<unknown>,
82
+ sortBy?: SortFor<unknown>,
83
+ limit?: number,
84
+ afterCursor?: unknown,
85
+ paginatedVersion?: boolean,
86
+ select?: ProjectionFor<TReadModel>
87
+ ): Promise<Array<TReadModel> | ReadModelListResult<TReadModel>>
88
+
89
+ // Store or update a read model
90
+ store<TReadModel extends ReadModelInterface>(
91
+ config: MagekConfig,
92
+ readModelName: string,
93
+ readModel: ReadModelStoreEnvelope<TReadModel>
94
+ ): Promise<ReadModelStoreEnvelope<TReadModel>>
95
+
96
+ // Delete a read model
97
+ delete(config: MagekConfig, readModelName: string, readModelID: UUID): Promise<void>
98
+
99
+ // Convert raw data to envelopes
100
+ rawToEnvelopes<TReadModel extends ReadModelInterface>(
101
+ config: MagekConfig,
102
+ rawReadModels: unknown
103
+ ): Promise<Array<ReadModelStoreEnvelope<TReadModel>>>
104
+
105
+ // Health check (optional)
106
+ healthCheck?: {
107
+ isUp(config: MagekConfig): Promise<boolean>
108
+ details(config: MagekConfig): Promise<unknown>
109
+ urls(config: MagekConfig): Promise<Array<string>>
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### Session Store Adapter
115
+
116
+ The `SessionStoreAdapter` interface manages real-time connections:
117
+
118
+ ```typescript
119
+ interface SessionStoreAdapter {
120
+ // Connection management
121
+ storeConnection(config: MagekConfig, connectionId: UUID, connectionData: Record<string, any>): Promise<void>
122
+ fetchConnection(config: MagekConfig, connectionId: UUID): Promise<Record<string, any> | undefined>
123
+ deleteConnection(config: MagekConfig, connectionId: UUID): Promise<void>
124
+
125
+ // Subscription management
126
+ storeSubscription(config: MagekConfig, connectionId: UUID, subscriptionId: UUID, subscriptionData: Record<string, any>): Promise<void>
127
+ fetchSubscription(config: MagekConfig, subscriptionId: UUID): Promise<Record<string, any> | undefined>
128
+ deleteSubscription(config: MagekConfig, connectionId: UUID, subscriptionId: UUID): Promise<void>
129
+
130
+ // Bulk operations
131
+ fetchSubscriptionsForConnection(config: MagekConfig, connectionId: UUID): Promise<Array<Record<string, any>>>
132
+ deleteSubscriptionsForConnection(config: MagekConfig, connectionId: UUID): Promise<void>
133
+ fetchSubscriptionsByClassName(config: MagekConfig, className: string): Promise<Array<SubscriptionEnvelope>>
134
+
135
+ // Health check (optional)
136
+ healthCheck?: {
137
+ isUp(config: MagekConfig): Promise<boolean>
138
+ details(config: MagekConfig): Promise<unknown>
139
+ urls(config: MagekConfig): Promise<Array<string>>
140
+ }
141
+ }
142
+ ```
143
+
144
+ ## Implementation Guide
145
+
146
+ ### Package Structure
147
+
148
+ Create your adapter package with this structure:
149
+
150
+ ```
151
+ adapters/<database>/<adapter-type>/
152
+ ├── src/
153
+ │ ├── index.ts # Export adapter singleton
154
+ │ └── <database>-<type>.ts # Implementation
155
+ ├── test/
156
+ │ ├── expect.ts # Test utilities
157
+ │ └── <adapter>.test.ts # Tests
158
+ ├── package.json
159
+ ├── tsconfig.json
160
+ ├── tsconfig.test.json
161
+ ├── tsconfig.eslint.json
162
+ ├── eslint.config.mjs
163
+ └── .mocharc.yml
164
+ ```
165
+
166
+ ### Package Naming Convention
167
+
168
+ Use this naming pattern: `@magek/adapter-{type}-{database}`
169
+
170
+ Examples:
171
+ - `@magek/adapter-event-store-nedb`
172
+ - `@magek/adapter-read-model-store-memory`
173
+ - `@magek/adapter-session-store-redis`
174
+
175
+ ### Implementing the Event Store
176
+
177
+ Key implementation considerations:
178
+
179
+ 1. **Event Storage**: Store events with `kind: 'event'` and snapshots with `kind: 'snapshot'`
180
+ 2. **Timestamps**: Generate `createdAt` when storing events
181
+ 3. **Soft Deletes**: Events should be soft-deleted by setting `deletedAt` and clearing `value`
182
+ 4. **Snapshots**: Hard delete snapshots when requested
183
+ 5. **Dispatch Tracking**: Track which events have been dispatched to prevent duplicate processing
184
+ 6. **Async Processing** (optional): Implement `fetchUnprocessedEvents` and `markEventProcessed` for polling-based event dispatch
185
+
186
+ Example implementation pattern:
187
+
188
+ ```typescript
189
+ import { EventStoreAdapter } from '@magek/common'
190
+
191
+ const eventRegistry = new YourEventRegistry()
192
+
193
+ export const eventStore: EventStoreAdapter = {
194
+ rawToEnvelopes: (rawEvents) => rawEvents as Array<EventEnvelope>,
195
+
196
+ forEntitySince: async (config, entityTypeName, entityID, since) => {
197
+ const query = {
198
+ entityTypeName,
199
+ entityID,
200
+ kind: 'event',
201
+ createdAt: { $gt: since || new Date(0).toISOString() },
202
+ deletedAt: { $exists: false },
203
+ }
204
+ return eventRegistry.query(query)
205
+ },
206
+
207
+ store: async (eventEnvelopes, config) => {
208
+ const persisted = []
209
+ for (const envelope of eventEnvelopes) {
210
+ const withTimestamp = { ...envelope, createdAt: new Date().toISOString() }
211
+ await eventRegistry.store(withTimestamp)
212
+ persisted.push(withTimestamp)
213
+ }
214
+ return persisted
215
+ },
216
+
217
+ // ... implement other methods
218
+ }
219
+ ```
220
+
221
+ ### Implementing the Read Model Store
222
+
223
+ Key implementation considerations:
224
+
225
+ 1. **Optimistic Concurrency**: Check version before updates, throw `OptimisticConcurrencyUnexpectedVersionError` on conflicts
226
+ 2. **Filter Support**: Implement filtering operations (eq, ne, lt, gt, lte, gte, in, contains, etc.)
227
+ 3. **Sorting**: Support multi-field sorting with ASC/DESC
228
+ 4. **Pagination**: Implement cursor-based pagination
229
+ 5. **Field Projection**: Support selecting specific fields
230
+
231
+ Filter operations to support:
232
+
233
+ | Operation | Description | Example |
234
+ |-----------|-------------|---------|
235
+ | `eq` | Equals | `{ name: { eq: 'John' } }` |
236
+ | `ne` | Not equals | `{ status: { ne: 'deleted' } }` |
237
+ | `lt` | Less than | `{ age: { lt: 18 } }` |
238
+ | `gt` | Greater than | `{ price: { gt: 100 } }` |
239
+ | `lte` | Less than or equal | `{ age: { lte: 65 } }` |
240
+ | `gte` | Greater than or equal | `{ rating: { gte: 4 } }` |
241
+ | `in` | In array | `{ status: { in: ['active', 'pending'] } }` |
242
+ | `isDefined` | Field exists | `{ email: { isDefined: true } }` |
243
+ | `contains` | String contains | `{ name: { contains: 'smith' } }` |
244
+ | `beginsWith` | String starts with | `{ name: { beginsWith: 'Dr.' } }` |
245
+ | `regex` | Regular expression | `{ email: { regex: '@example.com$' } }` |
246
+ | `iRegex` | Case-insensitive regex | `{ name: { iRegex: 'john' } }` |
247
+ | `includes` | Array includes | `{ tags: { includes: 'featured' } }` |
248
+ | `and` | Logical AND | `{ and: [filter1, filter2] }` |
249
+ | `or` | Logical OR | `{ or: [filter1, filter2] }` |
250
+ | `not` | Logical NOT | `{ not: { status: { eq: 'deleted' } } }` |
251
+
252
+ ### Implementing the Session Store
253
+
254
+ Key implementation considerations:
255
+
256
+ 1. **Connection Indexing**: Index connections by ID for fast lookup
257
+ 2. **Subscription Indexing**: Index subscriptions by connection ID and class name
258
+ 3. **Cleanup**: Ensure deleting a connection also cleans up its subscriptions
259
+ 4. **Class Name Queries**: Support fetching subscriptions by read model class name
260
+
261
+ ### Health Checks
262
+
263
+ Implement health checks to support monitoring:
264
+
265
+ ```typescript
266
+ healthCheck: {
267
+ isUp: async (config) => {
268
+ try {
269
+ // Test database connectivity
270
+ await database.ping()
271
+ return true
272
+ } catch {
273
+ return false
274
+ }
275
+ },
276
+
277
+ details: async (config) => ({
278
+ type: 'your-database',
279
+ status: 'healthy',
280
+ eventsCount: await database.count('events'),
281
+ }),
282
+
283
+ urls: async (config) => ['your-database://connection-string'],
284
+ }
285
+ ```
286
+
287
+ ### Async Event Processing (Optional)
288
+
289
+ Magek supports async event processing through polling. When implemented, the server polls for unprocessed events and dispatches them in batches, preventing duplicate processing.
290
+
291
+ #### Configuration Options
292
+
293
+ | Option | Default | Description |
294
+ |--------|---------|-------------|
295
+ | `eventPollingIntervalMs` | 1000 | Milliseconds between polling cycles |
296
+ | `eventProcessingBatchSize` | 100 | Maximum events per batch |
297
+
298
+ #### Implementation Pattern
299
+
300
+ ```typescript
301
+ // Track cursor position
302
+ let processingCursor: string = new Date(0).toISOString()
303
+
304
+ async function fetchUnprocessedEvents(config: MagekConfig): Promise<Array<EventEnvelope>> {
305
+ const query = {
306
+ kind: 'event',
307
+ deletedAt: { $exists: false },
308
+ processedAt: { $exists: false },
309
+ createdAt: { $gt: processingCursor },
310
+ }
311
+ return database.query(query, { sort: 'createdAt', limit: config.eventProcessingBatchSize })
312
+ }
313
+
314
+ async function markEventProcessed(config: MagekConfig, eventId: UUID): Promise<void> {
315
+ const event = await database.findById(eventId)
316
+ if (!event) return
317
+ await database.update(eventId, { processedAt: new Date().toISOString() })
318
+ processingCursor = event.createdAt
319
+ }
320
+ ```
321
+
322
+ #### Important Considerations
323
+
324
+ 1. **Remove Synchronous Dispatch**: When implementing async processing, remove any `eventDispatcher()` call from your `store()` method
325
+ 2. **Cursor Persistence**: Persistent adapters should persist the cursor; in-memory can use a variable
326
+ 3. **Event Ordering**: Process events in `createdAt` order for consistency
327
+ 4. **Optional Implementation**: If not implemented, the server uses synchronous dispatch
328
+
329
+ ## Configuration
330
+
331
+ To use your custom adapter, configure it in your Magek application:
332
+
333
+ ```typescript
334
+ import { MagekConfig } from '@magek/core'
335
+ import { eventStore } from '@magek/adapter-event-store-your-db'
336
+ import { readModelStore } from '@magek/adapter-read-model-store-your-db'
337
+ import { sessionStore } from '@magek/adapter-session-store-your-db'
338
+
339
+ const config = new MagekConfig('production')
340
+
341
+ // Set custom adapters
342
+ ;(config as any).eventStoreAdapter = eventStore
343
+ ;(config as any).readModelStoreAdapter = readModelStore
344
+ ;(config as any).sessionStoreAdapter = sessionStore
345
+ ```
346
+
347
+ ## Best Practices
348
+
349
+ 1. **Export Singleton Instances**: Export pre-configured adapter instances for easy usage:
350
+ ```typescript
351
+ export const eventStore: EventStoreAdapter = { /* ... */ }
352
+ ```
353
+
354
+ 2. **Use Logging**: Use `getLogger(config, 'AdapterName#methodName')` for consistent logging
355
+
356
+ 3. **Handle Errors Gracefully**: Wrap database errors in appropriate Magek error types
357
+
358
+ 4. **Support TypeScript**: Export types and use generics where appropriate
359
+
360
+ 5. **Write Comprehensive Tests**: Test all interface methods including edge cases
361
+
362
+ 6. **Document Connection Requirements**: Clearly document any environment variables or configuration needed
363
+
364
+ ## Example Adapters
365
+
366
+ For reference implementations, see:
367
+
368
+ - **NeDB Adapters** (file-based): `adapters/nedb/`
369
+ - **Memory Adapters** (in-memory): `adapters/memory/`
370
+
371
+ These implementations demonstrate all required patterns and can serve as templates for your custom adapters.
@@ -3,15 +3,108 @@ title: "Framework Packages"
3
3
  group: "Advanced"
4
4
  ---
5
5
 
6
- # Framework packages
7
- The framework is already splitted into different packages:
6
+ # Framework Packages
8
7
 
9
- ## Framework Core
8
+ Magek is organized as a monorepo with these packages:
10
9
 
11
- The `framework-core` package includes the most important components of the framework abstraction. It can be seen as skeleton or the main architecture of the framework.
10
+ ## Core Packages
12
11
 
13
- The package defines the specification of how should a Magek application work without taking into account the specific providers that could be used. Every Magek provider package is based on the components that the framework core needs in order to work on the platform.
12
+ ### @magek/core
14
13
 
15
- ## Common
14
+ The event sourcing engine with CQRS patterns, GraphQL generation, and decorators. This is the main package that powers Magek applications.
16
15
 
17
- The `common` package includes the types that define the domain of the Magek framework. It defines domain concepts like an `Event`, a `Command` or a `Role`.
16
+ ```bash
17
+ npm install @magek/core
18
+ ```
19
+
20
+ ### @magek/common
21
+
22
+ Shared types, utilities, and helper functions used across the framework. Includes essential helpers like:
23
+
24
+ - `evolve()` - Immutable state updates for entities and read models
25
+ - `UUID` - Unique identifier generation
26
+ - `createInstance()` - Instantiate classes from raw objects
27
+
28
+ ```bash
29
+ npm install @magek/common
30
+ ```
31
+
32
+ ## Development Tools
33
+
34
+ ### @magek/cli
35
+
36
+ Command-line tool for scaffolding projects, generating components, and running the development server.
37
+
38
+ ```bash
39
+ # Generate new components
40
+ npx magek new:command CreateProduct
41
+ npx magek new:event ProductCreated
42
+ npx magek new:entity Product
43
+ npx magek new:read-model ProductReadModel
44
+
45
+ # Start development server
46
+ npx magek start
47
+ ```
48
+
49
+ ### @magek/server
50
+
51
+ Fastify-based runtime for local development. Provides:
52
+
53
+ - Hot reloading
54
+ - GraphQL playground
55
+ - Local event store and read model storage
56
+
57
+ ### create-magek
58
+
59
+ Project scaffolding tool for creating new Magek applications with a single command.
60
+
61
+ ```bash
62
+ npx create-magek my-app
63
+ cd my-app
64
+ npm install
65
+ npx magek start
66
+ ```
67
+
68
+ ## Storage Adapters
69
+
70
+ Adapters provide pluggable storage backends for Magek applications. The NeDB adapters are used for local development.
71
+
72
+ ### @magek/adapter-event-store-nedb
73
+
74
+ NeDB-based event store adapter. Stores events in a local file-based database, ideal for development and testing.
75
+
76
+ ```bash
77
+ npm install @magek/adapter-event-store-nedb
78
+ ```
79
+
80
+ ### @magek/adapter-read-model-store-nedb
81
+
82
+ NeDB-based read model store adapter. Stores read model projections locally.
83
+
84
+ ```bash
85
+ npm install @magek/adapter-read-model-store-nedb
86
+ ```
87
+
88
+ ### @magek/adapter-session-store-nedb
89
+
90
+ NeDB-based session store adapter. Manages WebSocket connections and subscriptions for real-time features.
91
+
92
+ ```bash
93
+ npm install @magek/adapter-session-store-nedb
94
+ ```
95
+
96
+ ## AI Integration
97
+
98
+ ### @magek/mcp-server
99
+
100
+ Model Context Protocol (MCP) server that provides documentation, CLI reference, and guided prompts for AI coding assistants. Use this to integrate Magek knowledge into your AI-powered development workflow.
101
+
102
+ ```bash
103
+ npx @magek/mcp-server
104
+ ```
105
+
106
+ Features:
107
+ - Documentation resources accessible via MCP
108
+ - CQRS implementation guide prompt
109
+ - Troubleshooting assistance prompt
110
+ - CLI command reference
@@ -3,8 +3,117 @@ title: "Sensors"
3
3
  group: "Advanced"
4
4
  ---
5
5
 
6
- # Sensor
6
+ # Sensors
7
+
8
+ Sensors are monitoring components in Magek that track and report the status of your application. They provide visibility into application health and performance through HTTP endpoints.
9
+
10
+ ## Overview
11
+
12
+ Magek's sensor system allows you to:
13
+
14
+ - Monitor the health status of application components
15
+ - Expose status information via REST endpoints
16
+ - Create custom monitoring for your own services
17
+ - Integrate with external monitoring tools and dashboards
18
+
19
+ ## Health Sensors
20
+
21
+ The primary sensor type in Magek is the **Health Sensor**, which monitors application health and exposes status via the `/sensor/health/` endpoint.
22
+
23
+ ### Built-in Health Indicators
24
+
25
+ Magek provides these built-in health indicators:
26
+
27
+ | Indicator | Endpoint | Description |
28
+ |-----------|----------|-------------|
29
+ | `magek` | `/sensor/health/magek` | Overall application health |
30
+ | `magek/function` | `/sensor/health/magek/function` | GraphQL function status, CPU, and memory |
31
+ | `magek/database` | `/sensor/health/magek/database` | Database availability |
32
+ | `magek/database/events` | `/sensor/health/magek/database/events` | Event store health |
33
+ | `magek/database/readmodels` | `/sensor/health/magek/database/readmodels` | Read model store health |
34
+
35
+ ### Quick Start
36
+
37
+ Enable health sensors in your configuration:
38
+
39
+ ```typescript
40
+ Magek.configure('local', (config: MagekConfig): void => {
41
+ config.appName = 'my-app'
42
+ config.runtime = ServerRuntime
43
+
44
+ // Enable all health indicators
45
+ Object.values(config.sensorConfiguration.health.magek).forEach((indicator) => {
46
+ indicator.enabled = true
47
+ })
48
+ })
49
+ ```
50
+
51
+ Then access health status at `http://localhost:3000/sensor/health/`.
52
+
53
+ ### Creating Custom Health Sensors
54
+
55
+ Use the `@HealthSensor` decorator to create custom health indicators:
56
+
57
+ ```typescript
58
+ import {
59
+ MagekConfig,
60
+ HealthIndicatorResult,
61
+ HealthIndicatorMetadata,
62
+ HealthStatus,
63
+ } from '@magek/common'
64
+ import { HealthSensor } from '@magek/core'
65
+
66
+ @HealthSensor({
67
+ id: 'external-api',
68
+ name: 'External API Health',
69
+ enabled: true,
70
+ details: true,
71
+ })
72
+ export class ExternalApiHealthIndicator {
73
+ public async health(
74
+ config: MagekConfig,
75
+ metadata: HealthIndicatorMetadata
76
+ ): Promise<HealthIndicatorResult> {
77
+ // Check your external service
78
+ const isHealthy = await checkExternalApi()
79
+
80
+ return {
81
+ status: isHealthy ? HealthStatus.UP : HealthStatus.DOWN,
82
+ details: {
83
+ lastCheck: new Date().toISOString(),
84
+ },
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ ### Health Statuses
91
+
92
+ | Status | Description |
93
+ |--------|-------------|
94
+ | `UP` | Component is working as expected |
95
+ | `PARTIALLY_UP` | Component has reduced functionality |
96
+ | `DOWN` | Component is not working |
97
+ | `OUT_OF_SERVICE` | Component is temporarily unavailable |
98
+ | `UNKNOWN` | Component state cannot be determined |
99
+
100
+ ### HTTP Response Codes
101
+
102
+ - **200 OK**: All components are healthy (`UP`)
103
+ - **503 Service Unavailable**: One or more components are unhealthy
104
+
105
+ ## Configuration Options
106
+
107
+ Each health indicator supports these options:
108
+
109
+ | Option | Type | Default | Description |
110
+ |--------|------|---------|-------------|
111
+ | `id` | `string` | required | Unique identifier (used in URL path) |
112
+ | `name` | `string` | required | Display name in responses |
113
+ | `enabled` | `boolean` | `false` | Enable/disable the indicator |
114
+ | `details` | `boolean` | `true` | Include detailed information |
115
+ | `showChildren` | `boolean` | `true` | Include child components |
7
116
 
8
117
  ## Related Topics
9
118
 
10
- - [Sensor Health](health/sensor-health.md) - Health monitoring and indicators
119
+ - [Health Sensor Details](health/sensor-health.md) - Complete health monitoring guide with all configuration options, response formats, and examples
@@ -0,0 +1,133 @@
1
+ ---
2
+ title: "Best Practices"
3
+ group: "Architecture"
4
+ ---
5
+
6
+ # Best Practices
7
+
8
+ Quick reference for writing idiomatic Magek code.
9
+
10
+ ## State Updates with `evolve()`
11
+
12
+ **Always use `evolve()` for entity and read model state updates.**
13
+
14
+ The `evolve()` helper from `@magek/common`:
15
+ - Creates immutable copies (never mutates)
16
+ - Handles undefined state (new entities)
17
+ - Provides type safety
18
+
19
+ ```typescript
20
+ import { evolve } from '@magek/common'
21
+ ```
22
+
23
+ ### Entity Reducers
24
+
25
+ ```typescript
26
+ // DO: Use evolve()
27
+ @reduces(ProductCreated)
28
+ public static reduceProductCreated(event: ProductCreated, current?: Product): Product {
29
+ return evolve(current, {
30
+ id: event.entityID(),
31
+ name: event.name,
32
+ price: event.price,
33
+ })
34
+ }
35
+
36
+ // DON'T: Manual construction
37
+ public static reduceProductCreated(event: ProductCreated): Product {
38
+ return new Product(event.entityID(), event.name, event.price) // Missing immutability!
39
+ }
40
+ ```
41
+
42
+ ### Read Model Projections
43
+
44
+ ```typescript
45
+ // DO: Use evolve()
46
+ @projects(Product, 'id')
47
+ public static projectProduct(entity: Product, current?: ProductReadModel): ProjectionResult<ProductReadModel> {
48
+ return evolve(current, {
49
+ id: entity.id,
50
+ displayName: entity.name,
51
+ })
52
+ }
53
+ ```
54
+
55
+ ## Common Patterns
56
+
57
+ ### Creating vs Updating Entities
58
+
59
+ `evolve()` handles both cases automatically:
60
+ - When `current` is `undefined` → creates a new entity
61
+ - When `current` exists → updates with the provided changes
62
+
63
+ ```typescript
64
+ @reduces(ProductCreated)
65
+ public static reduceProductCreated(event: ProductCreated, current?: Product): Product {
66
+ // Works for both new entities and updates
67
+ return evolve(current, {
68
+ id: event.entityID(),
69
+ name: event.name,
70
+ })
71
+ }
72
+ ```
73
+
74
+ ### Providing Defaults for New Entities
75
+
76
+ When creating entities, you can provide default values:
77
+
78
+ ```typescript
79
+ return evolve(undefined, { id: event.id, name: event.name }, { status: 'active', createdAt: new Date() })
80
+ ```
81
+
82
+ The third parameter provides defaults that are applied when `current` is `undefined`.
83
+
84
+ ### Partial Updates
85
+
86
+ For update events, only include the fields that change:
87
+
88
+ ```typescript
89
+ @reduces(ProductRenamed)
90
+ public static reduceProductRenamed(event: ProductRenamed, current?: Product): Product {
91
+ if (!current) return ReducerAction.Skip
92
+ return evolve(current, { name: event.newName })
93
+ }
94
+ ```
95
+
96
+ ## Anti-Patterns
97
+
98
+ ### Mutating State Directly
99
+
100
+ ```typescript
101
+ // NEVER do this - breaks immutability
102
+ current.balance += amount
103
+ return current
104
+ ```
105
+
106
+ ### Using Spread Operator Instead of evolve()
107
+
108
+ ```typescript
109
+ // Avoid - evolve() is clearer and handles edge cases
110
+ return { ...current, balance: current.balance + amount }
111
+ ```
112
+
113
+ ### Constructing Entities Manually
114
+
115
+ ```typescript
116
+ // Avoid - doesn't handle undefined current state properly
117
+ return new Product(event.id, event.name, event.price)
118
+ ```
119
+
120
+ ## Utility Helpers Reference
121
+
122
+ | Helper | Import | Purpose |
123
+ |--------|--------|---------|
124
+ | `evolve(current, changes)` | `@magek/common` | Immutable state updates |
125
+ | `evolve(undefined, changes, defaults)` | `@magek/common` | Create with defaults |
126
+ | `UUID.generate()` | `@magek/common` | Generate unique IDs |
127
+ | `createInstance(Class, raw)` | `@magek/common` | Instantiate class from raw object |
128
+
129
+ ## Further Reading
130
+
131
+ - [Entity](./entity.md) - Learn about entity reducers
132
+ - [Read Model](./read-model.md) - Learn about projections
133
+ - [Event](./event.md) - Learn about events and the `entityID()` method
@@ -5,7 +5,7 @@ group: "Architecture"
5
5
 
6
6
  # Command
7
7
 
8
- Commands are any action a user performs on your application. For example, `RemoveItemFromCart`, `RatePhoto` or `AddCommentToPost`. They express the intention of an user, and they are the main interaction mechanism of your application. They are a similar to the concept of a **request on a REST API**. Command issuers can also send data on a command as parameters.
8
+ Commands are any action a user performs on your application. For example, `RemoveItemFromCart`, `RatePhoto` or `AddCommentToPost`. They express the intention of an user, and they are the main interaction mechanism of your application. They are similar to the concept of a **request on a REST API**. Command issuers can also send data on a command as parameters.
9
9
 
10
10
  ## Creating a command
11
11
 
@@ -82,7 +82,7 @@ export class CreateProduct {
82
82
 
83
83
  public static async handle(command: CreateProduct, register: Register): Promise<void> {
84
84
  // highlight-next-line
85
- register.event(new ProductCreated(/*...*/))
85
+ register.events(new ProductCreated(/*...*/))
86
86
  }
87
87
  }
88
88
  ```
@@ -91,7 +91,7 @@ For more details about events and the register parameter, see the [`Events`](/ar
91
91
 
92
92
  ### Returning a value
93
93
 
94
- The command handler function can return a value. This value will be the response of the GraphQL mutation. By default, the command handler function expects you to return a `void` as a return type. Since GrahpQL does not have a `void` type, the command handler function returns `true` when called through the GraphQL. This is because the GraphQL specification requires a response, and `true` is the most appropriate value to represent a successful execution with no return value.
94
+ The command handler function can return a value. This value will be the response of the GraphQL mutation. By default, the command handler function expects you to return a `void` as a return type. Since GraphQL does not have a `void` type, the command handler function returns `true` when called through the GraphQL. This is because the GraphQL specification requires a response, and `true` is the most appropriate value to represent a successful execution with no return value.
95
95
 
96
96
  If you want to return a value, you need to:
97
97
  1. Change the return type of the handler function
@@ -115,7 +115,7 @@ export class CreateProduct {
115
115
  // highlight-next-line
116
116
  @returns(type => String)
117
117
  public static async handle(command: CreateProduct, register: Register): Promise<string> {
118
- register.event(new ProductCreated(/*...*/))
118
+ register.events(new ProductCreated(/*...*/))
119
119
  // highlight-next-line
120
120
  return 'Product created!'
121
121
  }
@@ -296,7 +296,7 @@ You can read more about this on the [Authorization section](/security/authorizat
296
296
 
297
297
  ## Submitting a command
298
298
 
299
- Magek commands are accessible to the outside world as GraphQL mutations. GrahpQL fits very well with Magek's CQRS approach because it has two kinds of operations: Mutations and Queries. Mutations are actions that modify the server-side data, just like commands.
299
+ Magek commands are accessible to the outside world as GraphQL mutations. GraphQL fits very well with Magek's CQRS approach because it has two kinds of operations: Mutations and Queries. Mutations are actions that modify the server-side data, just like commands.
300
300
 
301
301
  Magek automatically creates one mutation per command. The framework infers the mutation input type from the command fields. Given this `CreateProduct` command:
302
302
 
@@ -365,3 +365,10 @@ Despite you can place commands, and other Magek files, in any directory, we stro
365
365
  │ ├── index.ts
366
366
  │ └── read-models
367
367
  ```
368
+
369
+ ## Related Topics
370
+
371
+ - [Events](./event.md) - Events registered by command handlers
372
+ - [Event Handlers](./event-handler.md) - Side effects triggered by events
373
+ - [Authorization](../security/authorization.md) - Securing commands with roles
374
+ - [GraphQL API](../graphql.md) - How commands become mutations
@@ -39,9 +39,25 @@ export class EntityName {
39
39
  }
40
40
  ```
41
41
 
42
+ ## Working with Entity State
43
+
44
+ Magek entities use immutable state updates. Always use the `evolve()` helper from `@magek/common`:
45
+
46
+ ```typescript
47
+ import { evolve } from '@magek/common'
48
+
49
+ // In your reducer:
50
+ return evolve(currentEntityState, {
51
+ fieldA: newValue,
52
+ fieldB: anotherValue,
53
+ })
54
+ ```
55
+
56
+ > **Why `evolve()`?** It ensures immutability, handles undefined state for new entities, and provides clear, consistent patterns across your codebase. See [Best Practices](./best-practices.md) for more details.
57
+
42
58
  ## The reduce function
43
59
 
44
- In order to tell Magek how to reduce the events, you must define a static method decorated with the `@reduces` decorator. This method will be called by the framework every time an event of the specified type is emitted. The reducer method must return a new entity instance with the current state of the entity.
60
+ In order to tell Magek how to reduce the events, you must define a static method decorated with the `@reduces` decorator. This method will be called by the framework every time an event of the specified type is emitted. The reducer method must return a new entity instance with the current state of the entity using `evolve()`.
45
61
 
46
62
  ```typescript title="src/entities/entity-name.ts"
47
63
  @Entity
@@ -212,3 +228,9 @@ Entities live within the entities directory of the project source: `<project-roo
212
228
  │ ├── index.ts
213
229
  │ └── read-models
214
230
  ```
231
+
232
+ ## Related Topics
233
+
234
+ - [Best Practices](./best-practices.md) - Recommended patterns for `evolve()` and state management
235
+ - [Events](./event.md) - Events that trigger entity reducers
236
+ - [Read Models](./read-model.md) - Project entities into query-optimized views
@@ -13,7 +13,7 @@ Two patterns influence the Magek's event-driven architecture: Command-Query Resp
13
13
 
14
14
  As you can see in the diagram, Magek applications consist of four main building blocks: `Commands`, `Events`, `Entities`, and `Read Models`. `Commands` and `Read Models` are the public interface of the application, while `Events` and `Entities` are private implementation details. With Magek, clients submit `Commands`, query the `Read Models`, or subscribe to them for receiving real-time updates thanks to the out of the box [GraphQL API](/graphql)
15
15
 
16
- Magek applications are event-driven and event-sourced so, **the source of truth is the whole history of events**. When a client submits a command, Magek _wakes up_ and handles it throght `Command Handlers`. As part of the process, some `Events` may be _registered_ as needed.
16
+ Magek applications are event-driven and event-sourced so, **the source of truth is the whole history of events**. When a client submits a command, Magek _wakes up_ and handles it through `Command Handlers`. As part of the process, some `Events` may be _registered_ as needed.
17
17
 
18
18
  On the other side, the framework caches the current state by automatically _reducing_ all the registered events into `Entities`. You can also _react_ to events via `Event Handlers`, triggering side effect actions to certain events. Finally, `Entities` are not directly exposed, they are transformed or _projected_ into `ReadModels`, which are exposed to the public.
19
19
 
@@ -5,7 +5,7 @@ group: "Architecture"
5
5
 
6
6
  # Read model
7
7
 
8
- A read model contains the data of your application that is exposed to the client through the GraphQL API. It's a _projection_ of one or more entities, so you dont have to directly expose them to the client. Magek generates the GraphQL queries that allow you to fetch your read models.
8
+ A read model contains the data of your application that is exposed to the client through the GraphQL API. It's a _projection_ of one or more entities, so you don't have to directly expose them to the client. Magek generates the GraphQL queries that allow you to fetch your read models.
9
9
 
10
10
  In other words, Read Models are cached data optimized for read operations. They're updated reactively when [Entities](./entity.md) are updated after reducing [events](./event.md).
11
11
 
@@ -43,7 +43,7 @@ export class ReadModelName {
43
43
 
44
44
  ## The projection function
45
45
 
46
- The projection function is a static method decorated with the `@projects` decorator. It is used to define how the read model is updated when an entity is modified. he projection function must return a new instance of the read model, it receives two arguments:
46
+ The projection function is a static method decorated with the `@projects` decorator. It is used to define how the read model is updated when an entity is modified. The projection function must return a new instance of the read model, it receives two arguments:
47
47
 
48
48
  - `entity`: The entity that has been modified
49
49
  - `current?`: The current read model instance. If it's the first time the read model is created, this argument will be `undefined`
@@ -233,7 +233,7 @@ export class UserReadModel {
233
233
  @projects(User, 'id')
234
234
  public static projectUser(entity: User, current?: UserReadModel): ProjectionResult<UserReadModel> {
235
235
  if (entity.deleted) {
236
- return ReadModelAction.Delete
236
+ return ProjectionAction.Delete
237
237
  }
238
238
  return evolve(current, { id: entity.id, username: entity.username })
239
239
  }
@@ -259,7 +259,7 @@ export class UserReadModel {
259
259
  @projects(User, 'id')
260
260
  public static projectUser(entity: User, current?: UserReadModel): ProjectionResult<UserReadModel> {
261
261
  if (!entity.modified) {
262
- return ReadModelAction.Nothing
262
+ return ProjectionAction.Skip
263
263
  }
264
264
  return evolve(current, { id: entity.id, username: entity.username })
265
265
  }
@@ -357,7 +357,7 @@ And here is an example of the corresponding JSON response when this query is exe
357
357
  }
358
358
  ```
359
359
 
360
- Notice that getters are not cached in the read models database, so the getters will be executed every time you include these fields in the queries. If access to nested queries is frequent or the size of the responses are big, you could improe your API response performance by querying the read models separately and joining the results in the client application.
360
+ Notice that getters are not cached in the read models database, so the getters will be executed every time you include these fields in the queries. If access to nested queries is frequent or the size of the responses are big, you could improve your API response performance by querying the read models separately and joining the results in the client application.
361
361
 
362
362
  ## Authorizing a read model
363
363
 
@@ -396,7 +396,7 @@ You can read more about this on the [Authorization section](/security/authorizat
396
396
 
397
397
  ## Querying a read model
398
398
 
399
- Magek read models are accessible to the outside world through GraphQL queries. GrahpQL fits very well with Magek's CQRS approach because it has two kinds of reading operations: Queries and Subscriptions. They are read-only operations that do not modify the state of the application. Magek uses them to fetch data from the read models.
399
+ Magek read models are accessible to the outside world through GraphQL queries. GraphQL fits very well with Magek's CQRS approach because it has two kinds of reading operations: Queries and Subscriptions. They are read-only operations that do not modify the state of the application. Magek uses them to fetch data from the read models.
400
400
 
401
401
  Magek automatically creates the queries and subscriptions for each read model. You can use them to fetch the data from the read models. For example, given the following read model:
402
402
 
@@ -505,3 +505,10 @@ Despite you can place your read models in any directory, we strongly recommend y
505
505
  │ ├── index.ts
506
506
  │ └── read-models
507
507
  ```
508
+
509
+ ## Related Topics
510
+
511
+ - [Best Practices](./best-practices.md) - Recommended patterns for `evolve()` and projections
512
+ - [Entities](./entity.md) - Source data for read model projections
513
+ - [Queries](./queries.md) - Alternative approach for complex query logic
514
+ - [GraphQL API](../graphql.md) - How read models are exposed via GraphQL
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "uri": "magek://docs/advanced/custom-adapters",
4
+ "path": "advanced/custom-adapters.md",
5
+ "title": "Custom Adapters",
6
+ "description": "Magek uses an adapter pattern to abstract away database and storage implementation details. This allows you to use different storage backends without modifying your application code. This guide exp..."
7
+ },
2
8
  {
3
9
  "uri": "magek://docs/advanced/custom-templates",
4
10
  "path": "advanced/custom-templates.md",
@@ -20,8 +26,8 @@
20
26
  {
21
27
  "uri": "magek://docs/advanced/framework-packages",
22
28
  "path": "advanced/framework-packages.md",
23
- "title": "Framework packages",
24
- "description": "The framework is already splitted into different packages:"
29
+ "title": "Framework Packages",
30
+ "description": "Magek is organized as a monorepo with these packages:"
25
31
  },
26
32
  {
27
33
  "uri": "magek://docs/advanced/health/sensor-health",
@@ -44,8 +50,8 @@
44
50
  {
45
51
  "uri": "magek://docs/advanced/sensor",
46
52
  "path": "advanced/sensor.md",
47
- "title": "Sensor",
48
- "description": "- [Sensor Health](health/sensor-health.md) - Health monitoring and indicators"
53
+ "title": "Sensors",
54
+ "description": "Sensors are monitoring components in Magek that track and report the status of your application. They provide visibility into application health and performance through HTTP endpoints."
49
55
  },
50
56
  {
51
57
  "uri": "magek://docs/advanced/testing",
@@ -59,6 +65,12 @@
59
65
  "title": "TouchEntities",
60
66
  "description": "Magek provides a way to refresh the value of an entity and update the corresponding ReadModels that depend on it."
61
67
  },
68
+ {
69
+ "uri": "magek://docs/architecture/best-practices",
70
+ "path": "architecture/best-practices.md",
71
+ "title": "Best Practices",
72
+ "description": "Quick reference for writing idiomatic Magek code."
73
+ },
62
74
  {
63
75
  "uri": "magek://docs/architecture/command",
64
76
  "path": "architecture/command.md",
@@ -105,7 +117,7 @@
105
117
  "uri": "magek://docs/architecture/read-model",
106
118
  "path": "architecture/read-model.md",
107
119
  "title": "Read model",
108
- "description": "A read model contains the data of your application that is exposed to the client through the GraphQL API. It's a _projection_ of one or more entities, so you dont have to directly expose them to th..."
120
+ "description": "A read model contains the data of your application that is exposed to the client through the GraphQL API. It's a _projection_ of one or more entities, so you don't have to directly expose them to t..."
109
121
  },
110
122
  {
111
123
  "uri": "magek://docs/contributing",
@@ -394,7 +394,7 @@ Now, let's run our application to see it working. It is as simple as running:
394
394
  npx magek start -e local
395
395
  ```
396
396
 
397
- This will execute a local `Express.js` server and will try to expose it in port `3000`. You can change the port by using the `-p` option:
397
+ This will execute a local `Fastify` server and will try to expose it on port `3000`. You can change the port by using the `-p` option:
398
398
 
399
399
  ```bash
400
400
  npx magek start -e local -p 8080
@@ -141,3 +141,10 @@ npx magek version
141
141
  > @magek/cli/0.16.1 darwin-x64 node-v22.12.0
142
142
 
143
143
  > **Tip:** The CLI is automatically included in every Magek project - no global installation needed! Use `npx magek` for all CLI commands within your project directory.
144
+
145
+ ## Next Steps
146
+
147
+ Now that you have Magek installed, continue with:
148
+
149
+ - **[CLI Reference](../magek-cli.md)** - Learn all available CLI commands
150
+ - **[Coding Tutorial](./coding.md)** - Build your first Magek application step by step
package/docs/index.md CHANGED
@@ -1,36 +1,43 @@
1
1
  ---
2
2
  title: "Documentation"
3
3
  children:
4
+ # Getting Started
4
5
  - ./introduction.md
5
6
  - ./getting-started/installation.md
7
+ - ./magek-cli.md
6
8
  - ./getting-started/coding.md
9
+ - ./getting-started/ai-coding-assistants.md
10
+ # Architecture (follows CQRS flow: Command → Event → Entity → Read Model → Handler)
7
11
  - ./architecture/event-driven.md
8
12
  - ./architecture/command.md
9
13
  - ./architecture/event.md
10
- - ./architecture/event-handler.md
11
14
  - ./architecture/entity.md
12
15
  - ./architecture/read-model.md
16
+ - ./architecture/event-handler.md
13
17
  - ./architecture/notifications.md
14
18
  - ./architecture/queries.md
19
+ - ./architecture/best-practices.md
20
+ # Features
21
+ - ./graphql.md
15
22
  - ./features/event-stream.md
16
23
  - ./features/schedule-actions.md
17
24
  - ./features/logging.md
18
25
  - ./features/error-handling.md
26
+ # Security
19
27
  - ./security/security.md
20
28
  - ./security/authentication.md
21
29
  - ./security/authorization.md
22
- - ./magek-cli.md
23
- - ./graphql.md
24
- - ./advanced/custom-templates.md
25
- - ./advanced/data-migrations.md
30
+ # Advanced
26
31
  - ./advanced/environment-configuration.md
27
- - ./advanced/framework-packages.md
32
+ - ./advanced/testing.md
33
+ - ./advanced/data-migrations.md
28
34
  - ./advanced/instrumentation.md
29
- - ./advanced/register.md
30
35
  - ./advanced/sensor.md
31
- - ./advanced/testing.md
32
- - ./advanced/touch-entities.md
33
36
  - ./advanced/health/sensor-health.md
37
+ - ./advanced/register.md
38
+ - ./advanced/touch-entities.md
39
+ - ./advanced/custom-templates.md
40
+ - ./advanced/framework-packages.md
34
41
  - ./contributing.md
35
42
  ---
36
43
 
@@ -42,21 +49,22 @@ Welcome to the Magek documentation! Use the navigation on the left to browse gui
42
49
 
43
50
  - **[Introduction](./introduction.md)** - Learn what Magek is
44
51
  - **[Installation](./getting-started/installation.md)** - Get started quickly
52
+ - **[CLI Reference](./magek-cli.md)** - Command-line tool
45
53
  - **[Coding Tutorial](./getting-started/coding.md)** - Build your first app
46
54
 
47
55
  ## Sections
48
56
 
49
57
  ### Getting Started
50
- Set up your environment and build your first Magek application.
58
+ Set up your environment, learn the CLI, and build your first Magek application.
51
59
 
52
60
  ### Architecture
53
- Understand Commands, Events, Entities, Read Models, and other core concepts.
61
+ Understand the CQRS pattern: Commands, Events, Entities, Read Models, and Event Handlers.
54
62
 
55
63
  ### Features
56
- Learn about Event Streams, Scheduled Actions, Logging, and Error Handling.
64
+ GraphQL API, Event Streams, Scheduled Actions, Logging, and Error Handling.
57
65
 
58
66
  ### Security
59
67
  Implement Authentication and Authorization in your applications.
60
68
 
61
69
  ### Advanced Topics
62
- Dive deeper into Providers, Migrations, Testing, and more.
70
+ Environment configuration, Testing, Migrations, Instrumentation, and more.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magek/mcp-server",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "MCP server for Magek documentation and CLI reference",
5
5
  "author": "Boosterin Labs SLU",
6
6
  "homepage": "https://magek.ai",
@@ -31,8 +31,8 @@
31
31
  "@modelcontextprotocol/sdk": "^1.12.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@magek/eslint-config": "^0.0.10",
35
- "@types/node": "22.19.7",
34
+ "@magek/eslint-config": "^0.0.11",
35
+ "@types/node": "22.19.9",
36
36
  "rimraf": "6.1.2",
37
37
  "typescript": "5.9.3"
38
38
  },