@reventlessdev/online-shop-hybrid-ordering 1.0.0-alpha.100
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/CHANGELOG.md +904 -0
- package/LICENSE +202 -0
- package/__mocks__/emptyModule.js +1 -0
- package/package.json +57 -0
- package/rescript.json +37 -0
- package/src/CatalogProduct/StateChangeSlice/SyncCatalogProduct.res +29 -0
- package/src/CatalogProduct/StateChangeSlice/SyncCatalogProduct.res.mjs +71 -0
- package/src/CatalogProduct/StateChangeSlice/SyncCatalogProduct_Behavior.res +27 -0
- package/src/CatalogProduct/StateChangeSlice/SyncCatalogProduct_Behavior.res.mjs +73 -0
- package/src/CatalogProduct/StateViewSliceStream/AvailableProducts.res +15 -0
- package/src/CatalogProduct/StateViewSliceStream/AvailableProducts.res.mjs +69 -0
- package/src/CatalogProduct/StateViewSliceStream/AvailableProducts_Projection.res +8 -0
- package/src/CatalogProduct/StateViewSliceStream/AvailableProducts_Projection.res.mjs +38 -0
- package/src/Customer/Aggregate/Customer.res +24 -0
- package/src/Customer/Aggregate/Customer.res.mjs +64 -0
- package/src/Customer/Aggregate/Customer_Behavior.res +41 -0
- package/src/Customer/Aggregate/Customer_Behavior.res.mjs +143 -0
- package/src/Customer/ReadModelStream/Customers.res +15 -0
- package/src/Customer/ReadModelStream/Customers.res.mjs +44 -0
- package/src/Customer/ReadModelStream/Customers_Projections.res +78 -0
- package/src/Customer/ReadModelStream/Customers_Projections.res.mjs +153 -0
- package/src/Extension/Products_Extension.res +23 -0
- package/src/Extension/Products_Extension.res.mjs +43 -0
- package/src/ExtensionPoint/Orders_ExtensionPointMapping.res +33 -0
- package/src/ExtensionPoint/Orders_ExtensionPointMapping.res.mjs +82 -0
- package/src/Order/AutomationSlice/AutoShipOrder.res +19 -0
- package/src/Order/AutomationSlice/AutoShipOrder.res.mjs +42 -0
- package/src/Order/AutomationSlice/AutoShipOrder_Automation.res +37 -0
- package/src/Order/AutomationSlice/AutoShipOrder_Automation.res.mjs +101 -0
- package/src/Order/OutboundTranslationSlice/SendOrderConfirmation.res +22 -0
- package/src/Order/OutboundTranslationSlice/SendOrderConfirmation.res.mjs +45 -0
- package/src/Order/OutboundTranslationSlice/SendOrderConfirmation_Translation.res +24 -0
- package/src/Order/OutboundTranslationSlice/SendOrderConfirmation_Translation.res.mjs +46 -0
- package/src/Order/StateChangeSlice/CancelOrder.res +29 -0
- package/src/Order/StateChangeSlice/CancelOrder.res.mjs +75 -0
- package/src/Order/StateChangeSlice/CancelOrder_Behavior.res +35 -0
- package/src/Order/StateChangeSlice/CancelOrder_Behavior.res.mjs +110 -0
- package/src/Order/StateChangeSlice/PlaceOrder.res +28 -0
- package/src/Order/StateChangeSlice/PlaceOrder.res.mjs +63 -0
- package/src/Order/StateChangeSlice/PlaceOrder_Behavior.res +39 -0
- package/src/Order/StateChangeSlice/PlaceOrder_Behavior.res.mjs +70 -0
- package/src/Order/StateChangeSlice/RefundOrder.res +27 -0
- package/src/Order/StateChangeSlice/RefundOrder.res.mjs +59 -0
- package/src/Order/StateChangeSlice/RefundOrder_Behavior.res +26 -0
- package/src/Order/StateChangeSlice/RefundOrder_Behavior.res.mjs +77 -0
- package/src/Order/StateChangeSlice/ShipOrder.res +23 -0
- package/src/Order/StateChangeSlice/ShipOrder.res.mjs +56 -0
- package/src/Order/StateChangeSlice/ShipOrder_Behavior.res +26 -0
- package/src/Order/StateChangeSlice/ShipOrder_Behavior.res.mjs +73 -0
- package/src/Order/StateViewSliceStream/Orders.res +24 -0
- package/src/Order/StateViewSliceStream/Orders.res.mjs +80 -0
- package/src/Order/StateViewSliceStream/Orders_Projection.res +10 -0
- package/src/Order/StateViewSliceStream/Orders_Projection.res.mjs +52 -0
- package/src/Plugin.res +74 -0
- package/src/Plugin.res.mjs +304 -0
- package/src/Service/EmailService.res +3 -0
- package/src/Service/EmailService.res.mjs +11 -0
- package/src/plugin.json +1 -0
- package/tests/CatalogProduct/StateChangeSlice/SyncCatalogProduct_GWT.res +27 -0
- package/tests/CatalogProduct/StateChangeSlice/SyncCatalogProduct_GWT.res.mjs +109 -0
- package/tests/CatalogProduct/StateViewSliceStream/AvailableProducts_GWT.res +15 -0
- package/tests/CatalogProduct/StateViewSliceStream/AvailableProducts_GWT.res.mjs +93 -0
- package/tests/Customer/Aggregate/Customer_GWT.res +63 -0
- package/tests/Customer/Aggregate/Customer_GWT.res.mjs +153 -0
- package/tests/Customer/ReadModelStream/Customers_GWT.res +84 -0
- package/tests/Customer/ReadModelStream/Customers_GWT.res.mjs +90 -0
- package/tests/Extension/Products_Extension_GWT.res +23 -0
- package/tests/Extension/Products_Extension_GWT.res.mjs +108 -0
- package/tests/ExtensionPoint/Orders_ExtensionPointMapping_GWT.res +23 -0
- package/tests/ExtensionPoint/Orders_ExtensionPointMapping_GWT.res.mjs +139 -0
- package/tests/Flow/OrderingFlow_GWT.res +69 -0
- package/tests/Flow/OrderingFlow_GWT.res.mjs +184 -0
- package/tests/Order/AutomationSlice/AutoShipOrder_GWT.res +55 -0
- package/tests/Order/AutomationSlice/AutoShipOrder_GWT.res.mjs +133 -0
- package/tests/Order/OutboundTranslationSlice/SendOrderConfirmation_GWT.res +30 -0
- package/tests/Order/OutboundTranslationSlice/SendOrderConfirmation_GWT.res.mjs +102 -0
- package/tests/Order/StateChangeSlice/CancelOrder_GWT.res +45 -0
- package/tests/Order/StateChangeSlice/CancelOrder_GWT.res.mjs +131 -0
- package/tests/Order/StateChangeSlice/PlaceOrder_GWT.res +33 -0
- package/tests/Order/StateChangeSlice/PlaceOrder_GWT.res.mjs +139 -0
- package/tests/Order/StateChangeSlice/RefundOrder_GWT.res +31 -0
- package/tests/Order/StateChangeSlice/RefundOrder_GWT.res.mjs +100 -0
- package/tests/Order/StateChangeSlice/ShipOrder_GWT.res +27 -0
- package/tests/Order/StateChangeSlice/ShipOrder_GWT.res.mjs +91 -0
- package/tests/Order/StateViewSliceStream/Orders_GWT.res +36 -0
- package/tests/Order/StateViewSliceStream/Orders_GWT.res.mjs +114 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
|
|
5
|
+
let commandSchema = S.union([
|
|
6
|
+
S.schema(s => ({
|
|
7
|
+
TAG: "Register",
|
|
8
|
+
email: s.m(S.string),
|
|
9
|
+
address: s.m(S.string)
|
|
10
|
+
})),
|
|
11
|
+
S.schema(s => ({
|
|
12
|
+
TAG: "UpdateEmail",
|
|
13
|
+
email: s.m(S.string)
|
|
14
|
+
})),
|
|
15
|
+
S.schema(s => ({
|
|
16
|
+
TAG: "UpdateAddress",
|
|
17
|
+
address: s.m(S.string)
|
|
18
|
+
})),
|
|
19
|
+
S.literal("Deactivate")
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
let eventSchema = S.union([
|
|
23
|
+
S.schema(s => ({
|
|
24
|
+
TAG: "Registered",
|
|
25
|
+
email: s.m(S.string),
|
|
26
|
+
address: s.m(S.string)
|
|
27
|
+
})),
|
|
28
|
+
S.schema(s => ({
|
|
29
|
+
TAG: "EmailUpdated",
|
|
30
|
+
email: s.m(S.string)
|
|
31
|
+
})),
|
|
32
|
+
S.schema(s => ({
|
|
33
|
+
TAG: "AddressUpdated",
|
|
34
|
+
address: s.m(S.string)
|
|
35
|
+
})),
|
|
36
|
+
S.literal("Deactivated")
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
let errorSchema = S.union([
|
|
40
|
+
S.literal("CustomerAlreadyRegistered"),
|
|
41
|
+
S.literal("CustomerNotFound"),
|
|
42
|
+
S.literal("CustomerAlreadyDeactivated")
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
function commandAuthorization(param) {
|
|
46
|
+
return "AllowAuthenticated";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let name = "Customer";
|
|
50
|
+
|
|
51
|
+
let Id;
|
|
52
|
+
|
|
53
|
+
let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Customer/Aggregate/Customer.res.mjs";
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
name,
|
|
57
|
+
Id,
|
|
58
|
+
commandSchema,
|
|
59
|
+
eventSchema,
|
|
60
|
+
errorSchema,
|
|
61
|
+
moduleUrl,
|
|
62
|
+
commandAuthorization,
|
|
63
|
+
}
|
|
64
|
+
/* commandSchema Not a pure module */
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Customer aggregate behavior.
|
|
2
|
+
// Implements the state machine for registering and managing customers.
|
|
3
|
+
|
|
4
|
+
@@reventless.behavior
|
|
5
|
+
|
|
6
|
+
@schema
|
|
7
|
+
type state =
|
|
8
|
+
| NotCreated
|
|
9
|
+
| Active({email: string, address: string})
|
|
10
|
+
| Deactivated
|
|
11
|
+
|
|
12
|
+
let initialState = NotCreated
|
|
13
|
+
|
|
14
|
+
let evolve = (state, event) =>
|
|
15
|
+
switch (state, event) {
|
|
16
|
+
| (NotCreated, Registered({email, address})) => Active({email, address})
|
|
17
|
+
| (Active(_), Registered({email, address})) => Active({email, address})
|
|
18
|
+
| (Active(s), EmailUpdated({email})) => Active({...s, email})
|
|
19
|
+
| (Active(s), AddressUpdated({address})) => Active({...s, address})
|
|
20
|
+
| (Active(_), Customer.Deactivated) => Deactivated
|
|
21
|
+
| (Deactivated, _) => state
|
|
22
|
+
| (NotCreated, _) => state
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let decide = (state, command) =>
|
|
26
|
+
switch (state, command) {
|
|
27
|
+
| (NotCreated, Register({email, address})) => Ok([Registered({email, address})])
|
|
28
|
+
| (NotCreated, UpdateEmail(_)) => Error(CustomerNotFound)
|
|
29
|
+
| (NotCreated, UpdateAddress(_)) => Error(CustomerNotFound)
|
|
30
|
+
| (NotCreated, Deactivate) => Error(CustomerNotFound)
|
|
31
|
+
| (Active(_), Register(_)) => Error(CustomerAlreadyRegistered)
|
|
32
|
+
| (Active(s), UpdateEmail({email})) if email == s.email => Ok([])
|
|
33
|
+
| (Active(_), UpdateEmail({email})) => Ok([EmailUpdated({email: email})])
|
|
34
|
+
| (Active(s), UpdateAddress({address})) if address == s.address => Ok([])
|
|
35
|
+
| (Active(_), UpdateAddress({address})) => Ok([AddressUpdated({address: address})])
|
|
36
|
+
| (Active(_), Deactivate) => Ok([Customer.Deactivated])
|
|
37
|
+
| (Deactivated, Register(_)) => Error(CustomerAlreadyDeactivated)
|
|
38
|
+
| (Deactivated, UpdateEmail(_)) => Error(CustomerAlreadyDeactivated)
|
|
39
|
+
| (Deactivated, UpdateAddress(_)) => Error(CustomerAlreadyDeactivated)
|
|
40
|
+
| (Deactivated, Deactivate) => Ok([]) // idempotent
|
|
41
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
|
|
5
|
+
let stateSchema = S.union([
|
|
6
|
+
S.literal("NotCreated"),
|
|
7
|
+
S.schema(s => ({
|
|
8
|
+
TAG: "Active",
|
|
9
|
+
email: s.m(S.string),
|
|
10
|
+
address: s.m(S.string)
|
|
11
|
+
})),
|
|
12
|
+
S.literal("Deactivated")
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function evolve(state, event) {
|
|
16
|
+
if (typeof state !== "object") {
|
|
17
|
+
if (state === "NotCreated" && typeof event === "object" && event.TAG === "Registered") {
|
|
18
|
+
return {
|
|
19
|
+
TAG: "Active",
|
|
20
|
+
email: event.email,
|
|
21
|
+
address: event.address
|
|
22
|
+
};
|
|
23
|
+
} else {
|
|
24
|
+
return state;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (typeof event !== "object") {
|
|
28
|
+
return "Deactivated";
|
|
29
|
+
}
|
|
30
|
+
switch (event.TAG) {
|
|
31
|
+
case "Registered" :
|
|
32
|
+
return {
|
|
33
|
+
TAG: "Active",
|
|
34
|
+
email: event.email,
|
|
35
|
+
address: event.address
|
|
36
|
+
};
|
|
37
|
+
case "EmailUpdated" :
|
|
38
|
+
return {
|
|
39
|
+
TAG: "Active",
|
|
40
|
+
email: event.email,
|
|
41
|
+
address: state.address
|
|
42
|
+
};
|
|
43
|
+
case "AddressUpdated" :
|
|
44
|
+
return {
|
|
45
|
+
TAG: "Active",
|
|
46
|
+
email: state.email,
|
|
47
|
+
address: event.address
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function decide(state, command) {
|
|
53
|
+
if (typeof state !== "object") {
|
|
54
|
+
if (state === "NotCreated") {
|
|
55
|
+
if (typeof command !== "object" || command.TAG !== "Register") {
|
|
56
|
+
return {
|
|
57
|
+
TAG: "Error",
|
|
58
|
+
_0: "CustomerNotFound"
|
|
59
|
+
};
|
|
60
|
+
} else {
|
|
61
|
+
return {
|
|
62
|
+
TAG: "Ok",
|
|
63
|
+
_0: [{
|
|
64
|
+
TAG: "Registered",
|
|
65
|
+
email: command.email,
|
|
66
|
+
address: command.address
|
|
67
|
+
}]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
} else if (typeof command !== "object") {
|
|
71
|
+
return {
|
|
72
|
+
TAG: "Ok",
|
|
73
|
+
_0: []
|
|
74
|
+
};
|
|
75
|
+
} else {
|
|
76
|
+
return {
|
|
77
|
+
TAG: "Error",
|
|
78
|
+
_0: "CustomerAlreadyDeactivated"
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (typeof command !== "object") {
|
|
83
|
+
return {
|
|
84
|
+
TAG: "Ok",
|
|
85
|
+
_0: ["Deactivated"]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
switch (command.TAG) {
|
|
89
|
+
case "Register" :
|
|
90
|
+
return {
|
|
91
|
+
TAG: "Error",
|
|
92
|
+
_0: "CustomerAlreadyRegistered"
|
|
93
|
+
};
|
|
94
|
+
case "UpdateEmail" :
|
|
95
|
+
let email = command.email;
|
|
96
|
+
if (email === state.email) {
|
|
97
|
+
return {
|
|
98
|
+
TAG: "Ok",
|
|
99
|
+
_0: []
|
|
100
|
+
};
|
|
101
|
+
} else {
|
|
102
|
+
return {
|
|
103
|
+
TAG: "Ok",
|
|
104
|
+
_0: [{
|
|
105
|
+
TAG: "EmailUpdated",
|
|
106
|
+
email: email
|
|
107
|
+
}]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
case "UpdateAddress" :
|
|
111
|
+
let address = command.address;
|
|
112
|
+
if (address === state.address) {
|
|
113
|
+
return {
|
|
114
|
+
TAG: "Ok",
|
|
115
|
+
_0: []
|
|
116
|
+
};
|
|
117
|
+
} else {
|
|
118
|
+
return {
|
|
119
|
+
TAG: "Ok",
|
|
120
|
+
_0: [{
|
|
121
|
+
TAG: "AddressUpdated",
|
|
122
|
+
address: address
|
|
123
|
+
}]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let Spec;
|
|
130
|
+
|
|
131
|
+
let initialState = "NotCreated";
|
|
132
|
+
|
|
133
|
+
let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Customer/Aggregate/Customer_Behavior.res.mjs";
|
|
134
|
+
|
|
135
|
+
export {
|
|
136
|
+
Spec,
|
|
137
|
+
stateSchema,
|
|
138
|
+
initialState,
|
|
139
|
+
evolve,
|
|
140
|
+
decide,
|
|
141
|
+
moduleUrl,
|
|
142
|
+
}
|
|
143
|
+
/* stateSchema Not a pure module */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Customers read model specification.
|
|
2
|
+
// Query-side state for registered customers. This is a **mixed-source** read
|
|
3
|
+
// model: the customer profile fields come from the Customer aggregate, while
|
|
4
|
+
// `orderCount` is fed by the Ordering DCB log's `OrderPlaced` events (keyed by
|
|
5
|
+
// `customerId`). See `Customers_Projections.res` for the two source mappings.
|
|
6
|
+
|
|
7
|
+
@@reventless.spec
|
|
8
|
+
|
|
9
|
+
@schema
|
|
10
|
+
type state = {
|
|
11
|
+
@displayName email: string,
|
|
12
|
+
address: string,
|
|
13
|
+
deactivated: bool,
|
|
14
|
+
orderCount: int,
|
|
15
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
|
|
5
|
+
import * as DisplayName$Reventless from "@reventlessdev/reventless-spec/src/components/DisplayName.res.mjs";
|
|
6
|
+
|
|
7
|
+
let stateSchema = S.schema(s => ({
|
|
8
|
+
email: s.m(S.string),
|
|
9
|
+
address: s.m(S.string),
|
|
10
|
+
deactivated: s.m(S.bool),
|
|
11
|
+
orderCount: s.m(S.int),
|
|
12
|
+
displayName: s.m(S.option(S.string))
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
let stateSchema$1 = S.Metadata.set(stateSchema, DisplayName$Reventless.displayNameId, {
|
|
16
|
+
fields: ["email"],
|
|
17
|
+
separator: " "
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let config = ReadModel$Reventless.config(undefined, undefined, undefined);
|
|
21
|
+
|
|
22
|
+
let name = "Customers";
|
|
23
|
+
|
|
24
|
+
let Id;
|
|
25
|
+
|
|
26
|
+
let subIdConfig;
|
|
27
|
+
|
|
28
|
+
let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Customer/ReadModelStream/Customers.res.mjs";
|
|
29
|
+
|
|
30
|
+
let authorization = "AllowAuthenticated";
|
|
31
|
+
|
|
32
|
+
let visibility = "Public";
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
name,
|
|
36
|
+
Id,
|
|
37
|
+
stateSchema$1 as stateSchema,
|
|
38
|
+
config,
|
|
39
|
+
subIdConfig,
|
|
40
|
+
moduleUrl,
|
|
41
|
+
authorization,
|
|
42
|
+
visibility,
|
|
43
|
+
}
|
|
44
|
+
/* stateSchema Not a pure module */
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Customer projection mappings — a single read model fed by TWO sources:
|
|
2
|
+
//
|
|
3
|
+
// 1. the **Customer aggregate** (profile: email, address, deactivated), and
|
|
4
|
+
// 2. the **Ordering DCB EventLog** (`orderCount`, from `OrderPlaced` events).
|
|
5
|
+
//
|
|
6
|
+
// This is the canonical mixed **aggregate + DCB** read model: an aggregate's
|
|
7
|
+
// per-instance state correlated with DCB events that reference it by a shared
|
|
8
|
+
// key (`customerId`). Both mappings write to the same row id, so the framework
|
|
9
|
+
// merges them into one `Customers` record.
|
|
10
|
+
//
|
|
11
|
+
// The DCB source's `name` MUST equal `<pluginName>DcbEventLog`
|
|
12
|
+
// ("OrderingDcbEventLog") — the key under which `Plugin_Builder` registers the
|
|
13
|
+
// ordering plugin's DCB EventTopic AND the `meta.service` DcbEventLog stamps on
|
|
14
|
+
// every published event.
|
|
15
|
+
//
|
|
16
|
+
// Both mappings use `UpdateWithDefault` so the merge is order-independent: an
|
|
17
|
+
// `OrderPlaced` that arrives before its customer's `Registered` still creates a
|
|
18
|
+
// row (and vice-versa), and neither source clobbers the other's fields.
|
|
19
|
+
|
|
20
|
+
@@reventless.mappings
|
|
21
|
+
|
|
22
|
+
// ─────────────────────────────────────────────────────────────
|
|
23
|
+
// DCB source view — Order-side events (only what this read model needs)
|
|
24
|
+
// ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
module OrderEvents = {
|
|
27
|
+
let name = "OrderingDcbEventLog"
|
|
28
|
+
|
|
29
|
+
@schema
|
|
30
|
+
type event = OrderPlaced({orderId: string, customerId: string})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─────────────────────────────────────────────────────────────
|
|
34
|
+
// Source 1 — Customer aggregate (profile)
|
|
35
|
+
// ─────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
module CustomerMapping = Mapping.Make(
|
|
38
|
+
Customer,
|
|
39
|
+
Customers,
|
|
40
|
+
{
|
|
41
|
+
open Customer
|
|
42
|
+
let project = ({event, id, _}) =>
|
|
43
|
+
switch event {
|
|
44
|
+
| Registered({email, address}) =>
|
|
45
|
+
UpdateWithDefault(
|
|
46
|
+
id,
|
|
47
|
+
{Customers.email: email, address, deactivated: false, orderCount: 0},
|
|
48
|
+
state => {...state, email, address, deactivated: false},
|
|
49
|
+
)
|
|
50
|
+
| EmailUpdated({email}) => Update(id, state => {...state, email})
|
|
51
|
+
| AddressUpdated({address}) => Update(id, state => {...state, address})
|
|
52
|
+
| Deactivated => Update(id, state => {...state, deactivated: true})
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
// ─────────────────────────────────────────────────────────────
|
|
58
|
+
// Source 2 — Ordering DCB EventLog (order count, keyed by customerId)
|
|
59
|
+
// ─────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
module CustomerOrdersMapping = Mapping.Make(
|
|
62
|
+
OrderEvents,
|
|
63
|
+
Customers,
|
|
64
|
+
{
|
|
65
|
+
open OrderEvents
|
|
66
|
+
let project = ({event, _}) =>
|
|
67
|
+
switch event {
|
|
68
|
+
| OrderPlaced({customerId}) =>
|
|
69
|
+
UpdateWithDefault(
|
|
70
|
+
customerId,
|
|
71
|
+
{Customers.email: "", address: "", deactivated: false, orderCount: 1},
|
|
72
|
+
state => {...state, orderCount: state.orderCount + 1},
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
let mappings: array<module(Mapping)> = [module(CustomerMapping), module(CustomerOrdersMapping)]
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
5
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
6
|
+
import * as Projection$Reventless from "@reventlessdev/reventless-spec/src/types/Projection.res.mjs";
|
|
7
|
+
import * as Customer$OrderingPlugin from "../Aggregate/Customer.res.mjs";
|
|
8
|
+
import * as Customers$OrderingPlugin from "./Customers.res.mjs";
|
|
9
|
+
|
|
10
|
+
let M = Projection$Reventless.Mappings.Make({
|
|
11
|
+
Id: Id$Reventless.$$String,
|
|
12
|
+
name: Customers$OrderingPlugin.name,
|
|
13
|
+
stateSchema: Customers$OrderingPlugin.stateSchema,
|
|
14
|
+
subIdConfig: undefined
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
let name = "OrderingDcbEventLog";
|
|
18
|
+
|
|
19
|
+
let eventSchema = S.schema(s => ({
|
|
20
|
+
TAG: "OrderPlaced",
|
|
21
|
+
orderId: s.m(DcbTag$Reventless.string),
|
|
22
|
+
customerId: s.m(DcbTag$Reventless.string)
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
let OrderEvents = {
|
|
26
|
+
Id: undefined,
|
|
27
|
+
name: name,
|
|
28
|
+
eventSchema: eventSchema
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function project(param) {
|
|
32
|
+
let event = param.event;
|
|
33
|
+
let id = param.id;
|
|
34
|
+
if (typeof event !== "object") {
|
|
35
|
+
return {
|
|
36
|
+
TAG: "Update",
|
|
37
|
+
_0: id,
|
|
38
|
+
_1: state => {
|
|
39
|
+
let newrecord = {...state};
|
|
40
|
+
newrecord.deactivated = true;
|
|
41
|
+
return newrecord;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
switch (event.TAG) {
|
|
46
|
+
case "Registered" :
|
|
47
|
+
let address = event.address;
|
|
48
|
+
let email = event.email;
|
|
49
|
+
return {
|
|
50
|
+
TAG: "UpdateWithDefault",
|
|
51
|
+
_0: id,
|
|
52
|
+
_1: {
|
|
53
|
+
email: email,
|
|
54
|
+
address: address,
|
|
55
|
+
deactivated: false,
|
|
56
|
+
orderCount: 0
|
|
57
|
+
},
|
|
58
|
+
_2: state => {
|
|
59
|
+
let newrecord = {...state};
|
|
60
|
+
newrecord.deactivated = false;
|
|
61
|
+
newrecord.address = address;
|
|
62
|
+
newrecord.email = email;
|
|
63
|
+
return newrecord;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
case "EmailUpdated" :
|
|
67
|
+
let email$1 = event.email;
|
|
68
|
+
return {
|
|
69
|
+
TAG: "Update",
|
|
70
|
+
_0: id,
|
|
71
|
+
_1: state => {
|
|
72
|
+
let newrecord = {...state};
|
|
73
|
+
newrecord.email = email$1;
|
|
74
|
+
return newrecord;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
case "AddressUpdated" :
|
|
78
|
+
let address$1 = event.address;
|
|
79
|
+
return {
|
|
80
|
+
TAG: "Update",
|
|
81
|
+
_0: id,
|
|
82
|
+
_1: state => {
|
|
83
|
+
let newrecord = {...state};
|
|
84
|
+
newrecord.address = address$1;
|
|
85
|
+
return newrecord;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let CustomerMapping = Projection$Reventless.Mapping.Make({
|
|
92
|
+
Id: Id$Reventless.$$String,
|
|
93
|
+
name: Customer$OrderingPlugin.name,
|
|
94
|
+
eventSchema: Customer$OrderingPlugin.eventSchema
|
|
95
|
+
})({
|
|
96
|
+
Id: Id$Reventless.$$String,
|
|
97
|
+
name: Customers$OrderingPlugin.name,
|
|
98
|
+
stateSchema: Customers$OrderingPlugin.stateSchema,
|
|
99
|
+
subIdConfig: undefined
|
|
100
|
+
})({
|
|
101
|
+
project: project
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function project$1(param) {
|
|
105
|
+
return {
|
|
106
|
+
TAG: "UpdateWithDefault",
|
|
107
|
+
_0: param.event.customerId,
|
|
108
|
+
_1: {
|
|
109
|
+
email: "",
|
|
110
|
+
address: "",
|
|
111
|
+
deactivated: false,
|
|
112
|
+
orderCount: 1
|
|
113
|
+
},
|
|
114
|
+
_2: state => {
|
|
115
|
+
let newrecord = {...state};
|
|
116
|
+
newrecord.orderCount = state.orderCount + 1 | 0;
|
|
117
|
+
return newrecord;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let CustomerOrdersMapping = Projection$Reventless.Mapping.Make({
|
|
123
|
+
Id: Id$Reventless.$$String,
|
|
124
|
+
name: name,
|
|
125
|
+
eventSchema: eventSchema
|
|
126
|
+
})({
|
|
127
|
+
Id: Id$Reventless.$$String,
|
|
128
|
+
name: Customers$OrderingPlugin.name,
|
|
129
|
+
stateSchema: Customers$OrderingPlugin.stateSchema,
|
|
130
|
+
subIdConfig: undefined
|
|
131
|
+
})({
|
|
132
|
+
project: project$1
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
let mappings = [
|
|
136
|
+
CustomerMapping,
|
|
137
|
+
CustomerOrdersMapping
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
let Target;
|
|
141
|
+
|
|
142
|
+
let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Customer/ReadModelStream/Customers_Projections.res.mjs";
|
|
143
|
+
|
|
144
|
+
export {
|
|
145
|
+
Target,
|
|
146
|
+
M,
|
|
147
|
+
OrderEvents,
|
|
148
|
+
CustomerMapping,
|
|
149
|
+
CustomerOrdersMapping,
|
|
150
|
+
mappings,
|
|
151
|
+
moduleUrl,
|
|
152
|
+
}
|
|
153
|
+
/* M Not a pure module */
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Ordering's extension subscribing to Catalog's Products_ExtensionPoint.
|
|
2
|
+
// Routes ProductBecameAvailable / ProductPriceChanged events to SyncCatalogProduct commands.
|
|
3
|
+
|
|
4
|
+
@@reventless.extension
|
|
5
|
+
|
|
6
|
+
module Mapping = {
|
|
7
|
+
module ExtensionPoint = CatalogSpec.Products_ExtensionPoint
|
|
8
|
+
module Delegate = SyncCatalogProduct
|
|
9
|
+
|
|
10
|
+
open ExtensionPoint
|
|
11
|
+
open SyncCatalogProduct
|
|
12
|
+
let mapIncomingEvent = (_id, event, _meta, _pluginDef, _queryEngine) =>
|
|
13
|
+
switch event {
|
|
14
|
+
| ProductBecameAvailable({productId, name, price}) => [
|
|
15
|
+
PublishStateChangeSliceCommand(SyncNewProduct({productId, name, price})),
|
|
16
|
+
]
|
|
17
|
+
| ProductPriceChanged({productId, price}) => [
|
|
18
|
+
PublishStateChangeSliceCommand(ChangeSyncedPrice({productId, price})),
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let mapOutgoingEvent = None
|
|
23
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as SyncCatalogProduct$OrderingPlugin from "../CatalogProduct/StateChangeSlice/SyncCatalogProduct.res.mjs";
|
|
4
|
+
|
|
5
|
+
function mapIncomingEvent(_id, event, _meta, _pluginDef, _queryEngine) {
|
|
6
|
+
if (event.TAG === "ProductBecameAvailable") {
|
|
7
|
+
return [{
|
|
8
|
+
TAG: "PublishStateChangeSliceCommand",
|
|
9
|
+
_0: {
|
|
10
|
+
TAG: "SyncNewProduct",
|
|
11
|
+
productId: event.productId,
|
|
12
|
+
name: event.name,
|
|
13
|
+
price: event.price
|
|
14
|
+
}
|
|
15
|
+
}];
|
|
16
|
+
} else {
|
|
17
|
+
return [{
|
|
18
|
+
TAG: "PublishStateChangeSliceCommand",
|
|
19
|
+
_0: {
|
|
20
|
+
TAG: "ChangeSyncedPrice",
|
|
21
|
+
productId: event.productId,
|
|
22
|
+
price: event.price
|
|
23
|
+
}
|
|
24
|
+
}];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let Mapping = {
|
|
29
|
+
ExtensionPoint: undefined,
|
|
30
|
+
Delegate: undefined,
|
|
31
|
+
mapIncomingEvent: mapIncomingEvent,
|
|
32
|
+
mapOutgoingEvent: undefined,
|
|
33
|
+
delegateModuleUrl: SyncCatalogProduct$OrderingPlugin.moduleUrl,
|
|
34
|
+
moduleUrl: "@reventlessdev/online-shop-hybrid-ordering/src/Extension/Products_Extension.res.mjs"
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Extension/Products_Extension.res.mjs";
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
Mapping,
|
|
41
|
+
moduleUrl,
|
|
42
|
+
}
|
|
43
|
+
/* SyncCatalogProduct-OrderingPlugin Not a pure module */
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Maps internal Ordering events to the stable Orders_ExtensionPoint public API.
|
|
2
|
+
// Decomposes batch OrderPlaced / OrderCancelled events into per-product EP events.
|
|
3
|
+
@@reventless.spec
|
|
4
|
+
|
|
5
|
+
module ExtensionPoint = OrderingSpec.Orders_ExtensionPoint
|
|
6
|
+
|
|
7
|
+
// DCB adapter: defines the event type used for outgoing event mapping.
|
|
8
|
+
// Only the events relevant to the extension point are included.
|
|
9
|
+
module Delegate = {
|
|
10
|
+
let name = "OrderingDcbEventLog"
|
|
11
|
+
@schema
|
|
12
|
+
type event =
|
|
13
|
+
| OrderPlaced({orderId: string, customerId: string, productIds: array<string>})
|
|
14
|
+
| OrderCancelled({orderId: string, productIds: array<string>})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let mapIncomingCommand = (_id, _command, _meta) => []
|
|
18
|
+
|
|
19
|
+
let mapOutgoingEvent = Some((_id, event, _meta, _queryEngine) =>
|
|
20
|
+
switch event {
|
|
21
|
+
| Delegate.OrderPlaced({orderId, customerId, productIds}) =>
|
|
22
|
+
productIds->Array.map(pid =>
|
|
23
|
+
PublishEvent(
|
|
24
|
+
pid,
|
|
25
|
+
OrderingSpec.Orders_ExtensionPoint.ItemOrdered({productId: pid, orderId, customerId}),
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
| Delegate.OrderCancelled({orderId, productIds}) =>
|
|
29
|
+
productIds->Array.map(pid =>
|
|
30
|
+
PublishEvent(pid, OrderingSpec.Orders_ExtensionPoint.ItemOrderCancelled({productId: pid, orderId}))
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
)
|