@magek/mcp-server 0.0.8

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.
Files changed (52) hide show
  1. package/README.md +42 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +25 -0
  4. package/dist/prompts/cqrs-flow.d.ts +15 -0
  5. package/dist/prompts/cqrs-flow.js +252 -0
  6. package/dist/prompts/troubleshooting.d.ts +15 -0
  7. package/dist/prompts/troubleshooting.js +239 -0
  8. package/dist/resources/cli-reference.d.ts +13 -0
  9. package/dist/resources/cli-reference.js +193 -0
  10. package/dist/resources/documentation.d.ts +18 -0
  11. package/dist/resources/documentation.js +62 -0
  12. package/dist/server.d.ts +5 -0
  13. package/dist/server.js +127 -0
  14. package/dist/utils/docs-loader.d.ts +19 -0
  15. package/dist/utils/docs-loader.js +111 -0
  16. package/docs/advanced/custom-templates.md +96 -0
  17. package/docs/advanced/data-migrations.md +181 -0
  18. package/docs/advanced/environment-configuration.md +74 -0
  19. package/docs/advanced/framework-packages.md +17 -0
  20. package/docs/advanced/health/sensor-health.md +389 -0
  21. package/docs/advanced/instrumentation.md +135 -0
  22. package/docs/advanced/register.md +119 -0
  23. package/docs/advanced/sensor.md +10 -0
  24. package/docs/advanced/testing.md +96 -0
  25. package/docs/advanced/touch-entities.md +45 -0
  26. package/docs/architecture/command.md +367 -0
  27. package/docs/architecture/entity.md +214 -0
  28. package/docs/architecture/event-driven.md +30 -0
  29. package/docs/architecture/event-handler.md +108 -0
  30. package/docs/architecture/event.md +145 -0
  31. package/docs/architecture/notifications.md +54 -0
  32. package/docs/architecture/queries.md +207 -0
  33. package/docs/architecture/read-model.md +507 -0
  34. package/docs/contributing.md +349 -0
  35. package/docs/docs-index.json +200 -0
  36. package/docs/features/error-handling.md +204 -0
  37. package/docs/features/event-stream.md +35 -0
  38. package/docs/features/logging.md +81 -0
  39. package/docs/features/schedule-actions.md +44 -0
  40. package/docs/getting-started/ai-coding-assistants.md +181 -0
  41. package/docs/getting-started/coding.md +543 -0
  42. package/docs/getting-started/installation.md +143 -0
  43. package/docs/graphql.md +1213 -0
  44. package/docs/index.md +62 -0
  45. package/docs/introduction.md +58 -0
  46. package/docs/magek-arch.png +0 -0
  47. package/docs/magek-cli.md +67 -0
  48. package/docs/magek-logo.svg +1 -0
  49. package/docs/security/authentication.md +189 -0
  50. package/docs/security/authorization.md +242 -0
  51. package/docs/security/security.md +16 -0
  52. package/package.json +46 -0
@@ -0,0 +1,108 @@
1
+ ---
2
+ title: "Event Handlers"
3
+ group: "Architecture"
4
+ ---
5
+
6
+ # Event handler
7
+
8
+ An event handler is a class that reacts to events. They are commonly used to trigger side effects in case of a new event. For instance, if a new event is registered in the system, an event handler could send an email to the user.
9
+
10
+ ## Creating an event handler
11
+
12
+ The Magek CLI will help you to create new event handlers. You just need to run the following command and the CLI will generate all the boilerplate for you:
13
+
14
+ ```bash
15
+ npx magek new:event-handler HandleAvailability --event StockMoved
16
+ ```
17
+
18
+ This will generate a new file called `handle-availability.ts` in the `src/event-handlers` directory. You can also create the file manually, but you will need to create the class and decorate it, so we recommend using the CLI.
19
+
20
+ ## Declaring an event handler
21
+
22
+ In Magek, event handlers are classes decorated with the `@EventHandler` decorator. The parameter of the decorator is the event that the handler will react to. The logic to be triggered after an event is registered is defined in the `handle` method of the class. This `handle` function will receive the event that triggered the handler.
23
+
24
+ ```typescript title="src/event-handlers/handle-availability.ts"
25
+ // highlight-next-line
26
+ @EventHandler(StockMoved)
27
+ export class HandleAvailability {
28
+ // highlight-start
29
+ public static async handle(event: StockMoved): Promise<void> {
30
+ // Do something here
31
+ }
32
+ // highlight-end
33
+ }
34
+ ```
35
+
36
+ ## Creating an event handler
37
+
38
+ Event handlers can be easily created using the Magek CLI command `npx magek new:event-handler`. There are two mandatory arguments: the event handler name, and the name of the event it will react to. For instance:
39
+
40
+ ```typescript
41
+ npx magek new:event-handler HandleAvailability --event StockMoved
42
+ ```
43
+
44
+ Once the creation is completed, there will be a new file in the event handlers directory `<project-root>/src/event-handlers/handle-availability.ts`.
45
+
46
+ ```text
47
+ <project-root>
48
+ ├── src
49
+ │ ├── commands
50
+ │ ├── common
51
+ │ ├── config
52
+ │ ├── entities
53
+ │ ├── events
54
+ │ ├── event-handlers <------ put them here
55
+ │ └── read-models
56
+ ```
57
+
58
+ ## Registering events from an event handler
59
+
60
+ Event handlers can also register new events. This is useful when you want to trigger a new event after a certain condition is met. For example, if you want to send an email to the user when a product is out of stock.
61
+
62
+ In order to register new events, Magek injects the `register` instance in the `handle` method as a second parameter. This `register` instance has a `events(...)` method that allows you to store any side effect events, you can specify as many as you need separated by commas as arguments of the function.
63
+
64
+ ```typescript title="src/event-handlers/handle-availability.ts"
65
+ @EventHandler(StockMoved)
66
+ export class HandleAvailability {
67
+ public static async handle(event: StockMoved, register: Register): Promise<void> {
68
+ if (event.quantity < 0) {
69
+ // highlight-next-line
70
+ register.events([new ProductOutOfStock(event.productID)])
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Reading entities from event handlers
77
+
78
+ There are cases where you need to read an entity to make a decision based on its current state. Different side effects can be triggered depending on the current state of the entity. Given the previous example, if a user does not want to receive emails when a product is out of stock, we should be able check the user preferences before sending the email.
79
+
80
+ For that reason, Magek provides the `Magek.entity` function. This function allows you to retrieve the current state of an entity. Let's say that we want to check the status of a product before we trigger its availability update. In that case we would call the `Magek.entity` function, which will return information about the entity.
81
+
82
+ ```typescript title="src/event-handlers/handle-availability.ts"
83
+ @EventHandler(StockMoved)
84
+ export class HandleAvailability {
85
+ public static async handle(event: StockMoved, register: Register): Promise<void> {
86
+ // highlight-next-line
87
+ const product = await Magek.entity(Product, event.productID)
88
+ if (product.stock < 0) {
89
+ register.events([new ProductOutOfStock(event.productID)])
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ## Creating a global event handler
96
+
97
+ **Magek** includes a `Global event handler`. This feature allows you to react to any event that occurs within the system.
98
+ By annotating a class with the @GlobalEventHandler decorator, the handle method within that class will be automatically called for any event that is generated
99
+
100
+ ```typescript
101
+ @GlobalEventHandler
102
+ export class GlobalHandler {
103
+ public static async handle(event: EventInterface | NotificationInterface, register: Register): Promise<void> {
104
+ if (event instanceof LogEventReceived) {
105
+ register.events(new LogEventReceivedTest(event.entityID(), event.value))
106
+ }
107
+ }
108
+ ```
@@ -0,0 +1,145 @@
1
+ ---
2
+ title: "Events"
3
+ group: "Architecture"
4
+ ---
5
+
6
+ # Event
7
+
8
+ An event is a fact of something that has happened in your application. Every action that takes place on your application should be stored as an event. They are stored in a single collection, forming a set of **immutable records of facts** that contain the whole story of your application. This collection of events is commonly known as the **Event Store**.
9
+
10
+ ## Creating an event
11
+
12
+ The Magek CLI will help you to create new events. You just need to run the following command and the CLI will generate all the boilerplate for you:
13
+
14
+ ```bash
15
+ npx magek new:event StockMoved --fields productID:string origin:string destination:string quantity:number
16
+ ```
17
+
18
+ This will generate a new file called `stock-moved.ts` in the `src/events` directory. You can also create the file manually, but you will need to create the class and decorate it, so we recommend using the CLI.
19
+
20
+ ## Declaring an event
21
+
22
+ Events are the cornerstone of Magek because of its event-driven and event-sourced nature. Magek events are TypeScript classes decorated with `@Event`. An event class may look like this:
23
+
24
+ ```typescript title="src/events/event-name.ts"
25
+ @Event
26
+ export class EventName {
27
+ @field()
28
+ public readonly field1!: SomeType
29
+
30
+ @field()
31
+ public readonly field2!: SomeOtherType
32
+
33
+ public constructor(field1: SomeType, field2: SomeOtherType) {
34
+ this.field1 = field1
35
+ this.field2 = field2
36
+ }
37
+
38
+ public entityID(): UUID {
39
+ return /* the associated entity ID */
40
+ }
41
+ }
42
+ ```
43
+
44
+ The class name is the name of the event. The event name is used to identify the event in the application. It is also used to generate the GraphQL schema. The class parameter names are the names of the fields of the event and their types are the types of the fields of the event.
45
+
46
+ ## Events and entities
47
+
48
+ Events and [Entities](./entity.md) are closely related. Each event will be aggregated (or _reduced_) into an entity. Therefore, Magek needs a way to know which entity is associated with each event. For that reason, it is required to provide an entity ID with each event. You can declare it with a class function named `entityID`. For example:
49
+
50
+ ```typescript title="src/events/cart-paid.ts"
51
+ @Event
52
+ export class CartPaid {
53
+ @field()
54
+ public readonly cartID!: UUID
55
+
56
+ @field()
57
+ public readonly paymentID!: UUID
58
+
59
+ public constructor(cartID: UUID, paymentID: UUID) {
60
+ this.cartID = cartID
61
+ this.paymentID = paymentID
62
+ }
63
+
64
+ // highlight-start
65
+ public entityID(): UUID {
66
+ // returns cartID because we want to associate it with
67
+ // (and reduce it within) the Cart entity
68
+ return this.cartID
69
+ }
70
+ // highlight-end
71
+ }
72
+ ```
73
+
74
+ > **Tip:** If your domain requires a **_Singleton_** entity, where there's only one instance of that entity in your whole application, you can return a constant value.
75
+
76
+ > **Caution:** Make sure that the `entityID` method always returns the same value for the same event's instance. Otherwise, the result of the entity reduction will be unpredictable.
77
+
78
+ ## Registering events in the event store
79
+
80
+ We have shown you how to _declare_ an event in Magek, but we haven't explained how to store them in the event store. In Magek terminology, creating an instance of an event and storing in the event store is known as `registering` it. You can do that on Magek using the `register.events(...)` function. The `register` object is provided as a parameter in the `handle` method of both [commands](./command.md#registering-events) and the [event handlers](./event-handler.md#registering-events-from-an-event-handler). For example:
81
+
82
+ ### Registering events from command handlers
83
+
84
+ ```typescript title="src/commands/move-stock.ts"
85
+ @Command({
86
+ authorize: [Admin],
87
+ })
88
+ export class MoveStock {
89
+ @field()
90
+ readonly productID!: string
91
+
92
+ @field()
93
+ readonly origin!: string
94
+
95
+ @field()
96
+ readonly destination!: string
97
+
98
+ @field()
99
+ readonly quantity!: number
100
+
101
+ public static async handle(command: MoveStock, register: Register): Promise<void> {
102
+ if (!command.enoughStock(command.origin, command.quantity, command.productID)) {
103
+ // highlight-next-line
104
+ register.events(new ErrorEvent(`There is not enough stock for ${command.productID} at ${command.origin}`))
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ ### Registering events from event handlers
111
+
112
+ ```typescript title="src/event-handlers/stock-moved.ts"
113
+ @EventHandler(StockMoved)
114
+ export class HandleAvailability {
115
+ public static async handle(event: StockMoved, register: Register): Promise<void> {
116
+ // highlight-next-line
117
+ register.events(new ProductAvailabilityChanged(event.productID, event.quantity))
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
123
+ ## Events naming convention
124
+
125
+ As with commands, you can name events in any way you want, depending on your application's domain. However, we recommend you to choose short sentences written in past tense because events are facts that have happened and can't be changed. Some event names would be:
126
+
127
+ - ProductCreated
128
+ - ProductUpdated
129
+ - ProductDeleted
130
+ - CartItemChanged
131
+ - StockMoved
132
+
133
+ As with other Magek files, events have their own directory:
134
+
135
+ ```text
136
+ <project-root>
137
+ ├── src
138
+ │ ├── commands
139
+ │ ├── common
140
+ │ ├── config
141
+ │ ├── entities
142
+ │ ├── events <------ put them here
143
+ │ ├── index.ts
144
+ │ └── read-models
145
+ ```
@@ -0,0 +1,54 @@
1
+ ---
2
+ title: "Notifications"
3
+ group: "Architecture"
4
+ ---
5
+
6
+ # Notifications
7
+
8
+ Notifications are an important concept in event-driven architecture, and they play a crucial role in informing interested parties about certain events that take place within an application.
9
+
10
+ ## Declaring a notification
11
+
12
+ In Magek, notifications are defined as classes decorated with the `@Notification` decorator. Here's a minimal example to illustrate this:
13
+
14
+ ```typescript title="src/notifications/cart-abandoned.ts"
15
+
16
+ @Notification()
17
+ export class CartAbandoned {}
18
+ ```
19
+
20
+ As you can see, to define a notification you simply need to import the `@Notification` decorator from the @magek/core library and use it to decorate a class. In this case, the class `CartAbandoned` represents a notification that informs interested parties that a cart has been abandoned.
21
+
22
+ ## Separating by topic
23
+
24
+ By default, all notifications in the application will be sent to the same topic called `defaultTopic`. To configure this, you can specify a different topic name in the `@Notification` decorator:
25
+
26
+ ```typescript title="src/notifications/cart-abandoned-topic.ts"
27
+
28
+ @Notification({ topic: 'cart-abandoned' })
29
+ export class CartAbandoned {}
30
+ ```
31
+
32
+ In this example, the `CartAbandoned` notification will be sent to the `cart-abandoned` topic, instead of the default topic.
33
+
34
+ ## Separating by partition key
35
+
36
+ By default, all the notifications in the application will share a partition key called `default`. This means that, by default, all the notifications in the application will be processed in order, which may not be as performant.
37
+
38
+ To change this, you can use the @partitionKey decorator to specify a field that will be used as a partition key for each notification:
39
+
40
+ ```typescript title="src/notifications/cart-abandoned-partition-key.ts"
41
+
42
+ @Notification({ topic: 'cart-abandoned' })
43
+ export class CartAbandoned {
44
+ public constructor(@partitionKey readonly key: string) {}
45
+ }
46
+ ```
47
+
48
+ In this example, each `CartAbandoned` notification will have its own partition key, which is specified in the constructor as the field `key`, it can be called in any way you want. This will allow for parallel processing of notifications, making the system more performant.
49
+
50
+ ## Reacting to notifications
51
+
52
+ Just like events, notifications can be handled by event handlers in order to trigger other processes. Event handlers are responsible for listening to events and notifications, and then performing specific actions in response to them.
53
+
54
+ In conclusion, defining notifications in the Magek Framework is a simple and straightforward process that can be done using the `@Notification` and `@partitionKey` decorators.
@@ -0,0 +1,207 @@
1
+ ---
2
+ title: "Queries"
3
+ group: "Architecture"
4
+ ---
5
+
6
+ # Queries
7
+
8
+ ReadModels offer read operations over reduced events. On the other hand, Queries provide a way to do custom read operations.
9
+
10
+ Queries are classes decorated with the `@Query` decorator that have a `handle` method. When the handler returns a value, use the `@returns` decorator to specify the GraphQL return type.
11
+
12
+ ```typescript
13
+ import {
14
+ beforeHookQueryID,
15
+ beforeHookQueryMultiply,
16
+ queryHandlerErrorCartId,
17
+ queryHandlerErrorCartMessage,
18
+ } from '../constants'
19
+
20
+ @Query({
21
+ authorize: 'all',
22
+ })
23
+ export class CartTotalQuantity {
24
+ @field()
25
+ readonly cartId!: UUID
26
+
27
+ @field()
28
+ @nonExposed
29
+ readonly multiply!: number
30
+
31
+ @returns(type => Number)
32
+ public static async handle(query: CartTotalQuantity, queryInfo: QueryInfo): Promise<number> {
33
+ const cart = await Magek.entity(Cart, query.cartId)
34
+ if (!cart || !cart.cartItems || cart.cartItems.length === 0) {
35
+ return 0
36
+ }
37
+ return cart?.cartItems
38
+ .map((cartItem) => cartItem.quantity)
39
+ .reduce((accumulator, value) => {
40
+ return accumulator + value
41
+ }, 0)
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Queries naming convention
47
+
48
+ We recommend use the `Query` suffix in your queries name.
49
+
50
+ Despite you can place your queries in any directory, we strongly recommend you to put them in `<project-root>/src/queries`.
51
+
52
+ ```text
53
+ <project-root>
54
+ ├── src
55
+ │ ├── commands
56
+ │ ├── common
57
+ │ ├── config
58
+ │ ├── entities
59
+ │ ├── read-models
60
+ │ ├── events
61
+ │ ├── queries <------ put them here
62
+ │ └── index.ts
63
+ ```
64
+
65
+ ## Creating a query
66
+
67
+ The preferred way to create a query is by using the generator, e.g.
68
+
69
+ ```shell
70
+ npx magek new:query ItemsInCountry --fields country:string
71
+ ```
72
+
73
+ The generator will create a Typescript class under the queries directory `<project-root>/src/queries/items-in-country.ts`.
74
+
75
+ Queries classes can also be created by hand and there are no restrictions. The structure of the data is totally open and can be as complex as you can manage in your projection functions.
76
+
77
+ ## The query handler function
78
+
79
+ Each query class must have a method called `handle`. This function is the command handler, and it will be called by the framework every time one instance of this query is submitted. Inside the handler you can run validations, return errors and query entities to make decisions.
80
+
81
+ Handler function receive a QueryInfo object to let users interact with the execution context. It can be used for a variety of purposes, including:
82
+
83
+ * Access the current signed in user, their roles and other claims included in their JWT token
84
+ * Access the request context or alter the HTTP response headers
85
+
86
+ ### Validating data
87
+
88
+ Magek uses the typed nature of GraphQL to ensure that types are correct before reaching the handler, so you don't have to validate types.
89
+
90
+ #### Throw an error
91
+
92
+ There are still business rules to be checked before proceeding with a query. For example, a given number must be between a threshold or a string must match a regular expression. In that case, it is enough just to throw an error in the handler. Magek will use the error's message as the response to make it descriptive.
93
+
94
+ ### Registering events
95
+
96
+ Within the query handler execution, it is not possible to register domain events. If you need to register events, then use a Command. For more details about events and the register parameter, see the [`Events`](/architecture/event) section.
97
+
98
+ ## Authorizing queries
99
+
100
+ You can define who is authorized to access your queries. The Magek authorization feature is covered in [the auth section](/security/authentication). So far, we have seen that you can make a query publicly accessible by authorizing `'all'` to query it, or you can set specific roles providing an array of roles in this way: `authorize: [Admin]`.
101
+
102
+ ## Querying
103
+
104
+ For every query, Magek automatically creates the corresponding GraphQL query. For example, given this `CartTotalQuantityQuery`:
105
+
106
+ ```typescript
107
+ @Query({
108
+ authorize: 'all',
109
+ })
110
+ export class CartTotalQuantityQuery {
111
+ @field()
112
+ readonly cartId!: UUID
113
+
114
+ @returns(type => Number)
115
+ public static async handle(query: CartTotalQuantity, queryInfo: QueryInfo): Promise<number> {
116
+ const cart = await Magek.entity(Cart, query.cartId)
117
+ if (!cart || !cart.cartItems || cart.cartItems.length === 0) {
118
+ return 0
119
+ }
120
+ return cart?.cartItems
121
+ .map((cartItem) => cartItem.quantity)
122
+ .reduce((accumulator, value) => {
123
+ return accumulator + value
124
+ }, 0)
125
+ }
126
+ }
127
+ ```
128
+
129
+ You will get the following GraphQL query and subscriptions:
130
+
131
+ ```graphQL
132
+ query CartTotalQuantityQuery($cartId: ID!): Float!
133
+ ```
134
+
135
+ > **Note:** Query subscriptions are not supported yet
136
+
137
+ ### Returning union types
138
+ Magek supports returning graphql union types. For example, this `SearchMedia` query returning books and movies.
139
+
140
+ ```typescript
141
+ export type MediaValue = BookReadModel | MovieReadModel
142
+
143
+ class SearchResult {
144
+ readonly results!: MediaValue[]
145
+ constructor(results: MediaValue[]) {
146
+ this.results = results
147
+ }
148
+ }
149
+
150
+ @Query({
151
+ authorize: 'all',
152
+ })
153
+ export class SearchMedia {
154
+ @field()
155
+ readonly searchword!: string
156
+
157
+ @returns(type => SearchResult)
158
+ public static async handle(query: SearchMedia, queryInfo: QueryInfo): Promise<SearchResult> {
159
+ const [books, movies] = await Promise.all([
160
+ Magek.readModel(BookReadModel)
161
+ .filter({
162
+ title: {
163
+ contains: query.searchword,
164
+ },
165
+ })
166
+ .search(),
167
+ Magek.readModel(MovieReadModel)
168
+ .filter({
169
+ title: {
170
+ contains: query.searchword,
171
+ },
172
+ })
173
+ .search(),
174
+ ])
175
+ const response = [...books, ...movies]
176
+
177
+ return {
178
+ results: response,
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ This generates the following query
185
+
186
+ ```graphql
187
+ SearchMedia ( input SearchMediaInput! ) SearchResult!
188
+ ```
189
+
190
+ The GraphQL union querying functionality can then be used. An example for the query above could be the following.
191
+
192
+ ```graphql
193
+ {
194
+ SearchMedia(input: { searchword: "Oppenheimer" }) {
195
+ results {
196
+ __typename
197
+ ... on BookReadModel {
198
+ title
199
+ pages
200
+ }
201
+ ... on MovieReadModel {
202
+ title
203
+ }
204
+ }
205
+ }
206
+ }
207
+ ```