@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.
- package/README.md +42 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/prompts/cqrs-flow.d.ts +15 -0
- package/dist/prompts/cqrs-flow.js +252 -0
- package/dist/prompts/troubleshooting.d.ts +15 -0
- package/dist/prompts/troubleshooting.js +239 -0
- package/dist/resources/cli-reference.d.ts +13 -0
- package/dist/resources/cli-reference.js +193 -0
- package/dist/resources/documentation.d.ts +18 -0
- package/dist/resources/documentation.js +62 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +127 -0
- package/dist/utils/docs-loader.d.ts +19 -0
- package/dist/utils/docs-loader.js +111 -0
- package/docs/advanced/custom-templates.md +96 -0
- package/docs/advanced/data-migrations.md +181 -0
- package/docs/advanced/environment-configuration.md +74 -0
- package/docs/advanced/framework-packages.md +17 -0
- package/docs/advanced/health/sensor-health.md +389 -0
- package/docs/advanced/instrumentation.md +135 -0
- package/docs/advanced/register.md +119 -0
- package/docs/advanced/sensor.md +10 -0
- package/docs/advanced/testing.md +96 -0
- package/docs/advanced/touch-entities.md +45 -0
- package/docs/architecture/command.md +367 -0
- package/docs/architecture/entity.md +214 -0
- package/docs/architecture/event-driven.md +30 -0
- package/docs/architecture/event-handler.md +108 -0
- package/docs/architecture/event.md +145 -0
- package/docs/architecture/notifications.md +54 -0
- package/docs/architecture/queries.md +207 -0
- package/docs/architecture/read-model.md +507 -0
- package/docs/contributing.md +349 -0
- package/docs/docs-index.json +200 -0
- package/docs/features/error-handling.md +204 -0
- package/docs/features/event-stream.md +35 -0
- package/docs/features/logging.md +81 -0
- package/docs/features/schedule-actions.md +44 -0
- package/docs/getting-started/ai-coding-assistants.md +181 -0
- package/docs/getting-started/coding.md +543 -0
- package/docs/getting-started/installation.md +143 -0
- package/docs/graphql.md +1213 -0
- package/docs/index.md +62 -0
- package/docs/introduction.md +58 -0
- package/docs/magek-arch.png +0 -0
- package/docs/magek-cli.md +67 -0
- package/docs/magek-logo.svg +1 -0
- package/docs/security/authentication.md +189 -0
- package/docs/security/authorization.md +242 -0
- package/docs/security/security.md +16 -0
- 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
|
+
```
|