@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,93 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Projection_GWT$ReventlessGwt from "@reventlessdev/reventless-gwt/src/Projection_GWT.res.mjs";
|
|
4
|
+
import * as AvailableProducts$OrderingPlugin from "../../../src/CatalogProduct/StateViewSliceStream/AvailableProducts.res.mjs";
|
|
5
|
+
import * as AvailableProducts_Projection$OrderingPlugin from "../../../src/CatalogProduct/StateViewSliceStream/AvailableProducts_Projection.res.mjs";
|
|
6
|
+
|
|
7
|
+
let include = Projection_GWT$ReventlessGwt.Make({
|
|
8
|
+
name: AvailableProducts$OrderingPlugin.name,
|
|
9
|
+
stateSchema: AvailableProducts$OrderingPlugin.stateSchema,
|
|
10
|
+
consumedEventSchema: AvailableProducts$OrderingPlugin.consumedEventSchema,
|
|
11
|
+
subIdConfig: undefined
|
|
12
|
+
})({
|
|
13
|
+
project: AvailableProducts_Projection$OrderingPlugin.project
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
let describe = include.describe;
|
|
17
|
+
|
|
18
|
+
let test = include.test;
|
|
19
|
+
|
|
20
|
+
let givenEvents = include.givenEvents;
|
|
21
|
+
|
|
22
|
+
let whenEvent = include.whenEvent;
|
|
23
|
+
|
|
24
|
+
let thenStateWithId = include.thenStateWithId;
|
|
25
|
+
|
|
26
|
+
describe("AvailableProducts StateViewSliceStream", () => {
|
|
27
|
+
test("CatalogProductSynced creates a row", undefined, () => thenStateWithId(whenEvent(givenEvents([]), {
|
|
28
|
+
TAG: "CatalogProductSynced",
|
|
29
|
+
productId: "p1",
|
|
30
|
+
name: "Laptop",
|
|
31
|
+
price: 999.99
|
|
32
|
+
}), "p1", {
|
|
33
|
+
productId: "p1",
|
|
34
|
+
name: "Laptop",
|
|
35
|
+
price: 999.99
|
|
36
|
+
}));
|
|
37
|
+
test("CatalogProductPriceChanged updates the price", undefined, () => thenStateWithId(whenEvent(givenEvents([{
|
|
38
|
+
TAG: "CatalogProductSynced",
|
|
39
|
+
productId: "p1",
|
|
40
|
+
name: "Laptop",
|
|
41
|
+
price: 999.99
|
|
42
|
+
}]), {
|
|
43
|
+
TAG: "CatalogProductPriceChanged",
|
|
44
|
+
productId: "p1",
|
|
45
|
+
price: 899.99
|
|
46
|
+
}), "p1", {
|
|
47
|
+
productId: "p1",
|
|
48
|
+
name: "Laptop",
|
|
49
|
+
price: 899.99
|
|
50
|
+
}));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let Spec = include.Spec;
|
|
54
|
+
|
|
55
|
+
let describeWithId = include.describeWithId;
|
|
56
|
+
|
|
57
|
+
let todo = include.todo;
|
|
58
|
+
|
|
59
|
+
let whenEvents = include.whenEvents;
|
|
60
|
+
|
|
61
|
+
let thenStates = include.thenStates;
|
|
62
|
+
|
|
63
|
+
let thenStatesWithId = include.thenStatesWithId;
|
|
64
|
+
|
|
65
|
+
let thenAllStates = include.thenAllStates;
|
|
66
|
+
|
|
67
|
+
let thenState = include.thenState;
|
|
68
|
+
|
|
69
|
+
let thenNoState = include.thenNoState;
|
|
70
|
+
|
|
71
|
+
let thenThrow = include.thenThrow;
|
|
72
|
+
|
|
73
|
+
let thenFail = include.thenFail;
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
Spec,
|
|
77
|
+
describe,
|
|
78
|
+
describeWithId,
|
|
79
|
+
todo,
|
|
80
|
+
test,
|
|
81
|
+
givenEvents,
|
|
82
|
+
whenEvent,
|
|
83
|
+
whenEvents,
|
|
84
|
+
thenStates,
|
|
85
|
+
thenStatesWithId,
|
|
86
|
+
thenAllStates,
|
|
87
|
+
thenState,
|
|
88
|
+
thenStateWithId,
|
|
89
|
+
thenNoState,
|
|
90
|
+
thenThrow,
|
|
91
|
+
thenFail,
|
|
92
|
+
}
|
|
93
|
+
/* include Not a pure module */
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@@reventless.gwt
|
|
2
|
+
|
|
3
|
+
describe("Customer Behavior", () => {
|
|
4
|
+
test("Register on new aggregate produces Registered", () =>
|
|
5
|
+
givenEvents([])
|
|
6
|
+
->whenCmd(Register({email: "alice@x.y", address: "123 Main"}))
|
|
7
|
+
->thenEvent(Registered({email: "alice@x.y", address: "123 Main"}))
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
test("Register on existing aggregate returns CustomerAlreadyRegistered", () =>
|
|
11
|
+
givenEvents([Registered({email: "alice@x.y", address: "123 Main"})])
|
|
12
|
+
->whenCmd(Register({email: "bob@x.y", address: "456 Oak"}))
|
|
13
|
+
->thenError(CustomerAlreadyRegistered)
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
test("UpdateEmail on non-existent aggregate returns CustomerNotFound", () =>
|
|
17
|
+
givenEvents([])
|
|
18
|
+
->whenCmd(UpdateEmail({email: "x@y"}))
|
|
19
|
+
->thenError(CustomerNotFound)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
test("UpdateEmail on active customer produces EmailUpdated", () =>
|
|
23
|
+
givenEvents([Registered({email: "alice@x.y", address: "123 Main"})])
|
|
24
|
+
->whenCmd(UpdateEmail({email: "alice2@x.y"}))
|
|
25
|
+
->thenEvent(EmailUpdated({email: "alice2@x.y"}))
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
test("UpdateEmail to same email produces no events (idempotent)", () =>
|
|
29
|
+
givenEvents([Registered({email: "alice@x.y", address: "123 Main"})])
|
|
30
|
+
->whenCmd(UpdateEmail({email: "alice@x.y"}))
|
|
31
|
+
->thenNoEvent
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
test("UpdateEmail on deactivated customer returns CustomerAlreadyDeactivated", () =>
|
|
35
|
+
givenEvents([Registered({email: "alice@x.y", address: "123 Main"}), Deactivated])
|
|
36
|
+
->whenCmd(UpdateEmail({email: "x@y"}))
|
|
37
|
+
->thenError(CustomerAlreadyDeactivated)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
test("UpdateAddress on active customer produces AddressUpdated", () =>
|
|
41
|
+
givenEvents([Registered({email: "alice@x.y", address: "123 Main"})])
|
|
42
|
+
->whenCmd(UpdateAddress({address: "789 Pine"}))
|
|
43
|
+
->thenEvent(AddressUpdated({address: "789 Pine"}))
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
test("UpdateAddress to same address produces no events (idempotent)", () =>
|
|
47
|
+
givenEvents([Registered({email: "alice@x.y", address: "123 Main"})])
|
|
48
|
+
->whenCmd(UpdateAddress({address: "123 Main"}))
|
|
49
|
+
->thenNoEvent
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
test("Deactivate on active customer produces Deactivated", () =>
|
|
53
|
+
givenEvents([Registered({email: "alice@x.y", address: "123 Main"})])
|
|
54
|
+
->whenCmd(Deactivate)
|
|
55
|
+
->thenEvent(Deactivated)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
test("Deactivate on deactivated customer produces no events (idempotent)", () =>
|
|
59
|
+
givenEvents([Registered({email: "alice@x.y", address: "123 Main"}), Deactivated])
|
|
60
|
+
->whenCmd(Deactivate)
|
|
61
|
+
->thenNoEvent
|
|
62
|
+
)
|
|
63
|
+
})
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Customer$OrderingPlugin from "../../../src/Customer/Aggregate/Customer.res.mjs";
|
|
4
|
+
import * as Behavior_GWT$ReventlessGwt from "@reventlessdev/reventless-gwt/src/Behavior_GWT.res.mjs";
|
|
5
|
+
import * as Customer_Behavior$OrderingPlugin from "../../../src/Customer/Aggregate/Customer_Behavior.res.mjs";
|
|
6
|
+
|
|
7
|
+
let include = Behavior_GWT$ReventlessGwt.MakeFromAggregate({
|
|
8
|
+
name: Customer$OrderingPlugin.name,
|
|
9
|
+
commandSchema: Customer$OrderingPlugin.commandSchema,
|
|
10
|
+
eventSchema: Customer$OrderingPlugin.eventSchema,
|
|
11
|
+
errorSchema: Customer$OrderingPlugin.errorSchema
|
|
12
|
+
})({
|
|
13
|
+
Spec: {
|
|
14
|
+
name: Customer$OrderingPlugin.name,
|
|
15
|
+
commandSchema: Customer$OrderingPlugin.commandSchema,
|
|
16
|
+
eventSchema: Customer$OrderingPlugin.eventSchema,
|
|
17
|
+
errorSchema: Customer$OrderingPlugin.errorSchema
|
|
18
|
+
},
|
|
19
|
+
initialState: Customer_Behavior$OrderingPlugin.initialState,
|
|
20
|
+
evolve: Customer_Behavior$OrderingPlugin.evolve,
|
|
21
|
+
decide: Customer_Behavior$OrderingPlugin.decide,
|
|
22
|
+
moduleUrl: Customer_Behavior$OrderingPlugin.moduleUrl
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
let describe = include.describe;
|
|
26
|
+
|
|
27
|
+
let test = include.test;
|
|
28
|
+
|
|
29
|
+
let givenEvents = include.givenEvents;
|
|
30
|
+
|
|
31
|
+
let whenCmd = include.whenCmd;
|
|
32
|
+
|
|
33
|
+
let thenEvent = include.thenEvent;
|
|
34
|
+
|
|
35
|
+
let thenNoEvent = include.thenNoEvent;
|
|
36
|
+
|
|
37
|
+
let thenError = include.thenError;
|
|
38
|
+
|
|
39
|
+
describe("Customer Behavior", () => {
|
|
40
|
+
test("Register on new aggregate produces Registered", () => thenEvent(whenCmd(givenEvents([]), {
|
|
41
|
+
TAG: "Register",
|
|
42
|
+
email: "alice@x.y",
|
|
43
|
+
address: "123 Main"
|
|
44
|
+
}), {
|
|
45
|
+
TAG: "Registered",
|
|
46
|
+
email: "alice@x.y",
|
|
47
|
+
address: "123 Main"
|
|
48
|
+
}));
|
|
49
|
+
test("Register on existing aggregate returns CustomerAlreadyRegistered", () => thenError(whenCmd(givenEvents([{
|
|
50
|
+
TAG: "Registered",
|
|
51
|
+
email: "alice@x.y",
|
|
52
|
+
address: "123 Main"
|
|
53
|
+
}]), {
|
|
54
|
+
TAG: "Register",
|
|
55
|
+
email: "bob@x.y",
|
|
56
|
+
address: "456 Oak"
|
|
57
|
+
}), "CustomerAlreadyRegistered"));
|
|
58
|
+
test("UpdateEmail on non-existent aggregate returns CustomerNotFound", () => thenError(whenCmd(givenEvents([]), {
|
|
59
|
+
TAG: "UpdateEmail",
|
|
60
|
+
email: "x@y"
|
|
61
|
+
}), "CustomerNotFound"));
|
|
62
|
+
test("UpdateEmail on active customer produces EmailUpdated", () => thenEvent(whenCmd(givenEvents([{
|
|
63
|
+
TAG: "Registered",
|
|
64
|
+
email: "alice@x.y",
|
|
65
|
+
address: "123 Main"
|
|
66
|
+
}]), {
|
|
67
|
+
TAG: "UpdateEmail",
|
|
68
|
+
email: "alice2@x.y"
|
|
69
|
+
}), {
|
|
70
|
+
TAG: "EmailUpdated",
|
|
71
|
+
email: "alice2@x.y"
|
|
72
|
+
}));
|
|
73
|
+
test("UpdateEmail to same email produces no events (idempotent)", () => thenNoEvent(whenCmd(givenEvents([{
|
|
74
|
+
TAG: "Registered",
|
|
75
|
+
email: "alice@x.y",
|
|
76
|
+
address: "123 Main"
|
|
77
|
+
}]), {
|
|
78
|
+
TAG: "UpdateEmail",
|
|
79
|
+
email: "alice@x.y"
|
|
80
|
+
})));
|
|
81
|
+
test("UpdateEmail on deactivated customer returns CustomerAlreadyDeactivated", () => thenError(whenCmd(givenEvents([
|
|
82
|
+
{
|
|
83
|
+
TAG: "Registered",
|
|
84
|
+
email: "alice@x.y",
|
|
85
|
+
address: "123 Main"
|
|
86
|
+
},
|
|
87
|
+
"Deactivated"
|
|
88
|
+
]), {
|
|
89
|
+
TAG: "UpdateEmail",
|
|
90
|
+
email: "x@y"
|
|
91
|
+
}), "CustomerAlreadyDeactivated"));
|
|
92
|
+
test("UpdateAddress on active customer produces AddressUpdated", () => thenEvent(whenCmd(givenEvents([{
|
|
93
|
+
TAG: "Registered",
|
|
94
|
+
email: "alice@x.y",
|
|
95
|
+
address: "123 Main"
|
|
96
|
+
}]), {
|
|
97
|
+
TAG: "UpdateAddress",
|
|
98
|
+
address: "789 Pine"
|
|
99
|
+
}), {
|
|
100
|
+
TAG: "AddressUpdated",
|
|
101
|
+
address: "789 Pine"
|
|
102
|
+
}));
|
|
103
|
+
test("UpdateAddress to same address produces no events (idempotent)", () => thenNoEvent(whenCmd(givenEvents([{
|
|
104
|
+
TAG: "Registered",
|
|
105
|
+
email: "alice@x.y",
|
|
106
|
+
address: "123 Main"
|
|
107
|
+
}]), {
|
|
108
|
+
TAG: "UpdateAddress",
|
|
109
|
+
address: "123 Main"
|
|
110
|
+
})));
|
|
111
|
+
test("Deactivate on active customer produces Deactivated", () => thenEvent(whenCmd(givenEvents([{
|
|
112
|
+
TAG: "Registered",
|
|
113
|
+
email: "alice@x.y",
|
|
114
|
+
address: "123 Main"
|
|
115
|
+
}]), "Deactivate"), "Deactivated"));
|
|
116
|
+
test("Deactivate on deactivated customer produces no events (idempotent)", () => thenNoEvent(whenCmd(givenEvents([
|
|
117
|
+
{
|
|
118
|
+
TAG: "Registered",
|
|
119
|
+
email: "alice@x.y",
|
|
120
|
+
address: "123 Main"
|
|
121
|
+
},
|
|
122
|
+
"Deactivated"
|
|
123
|
+
]), "Deactivate")));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
let Spec = include.Spec;
|
|
127
|
+
|
|
128
|
+
let thenCompareEvent = include.thenCompareEvent;
|
|
129
|
+
|
|
130
|
+
let thenEventWithError = include.thenEventWithError;
|
|
131
|
+
|
|
132
|
+
let thenEvents = include.thenEvents;
|
|
133
|
+
|
|
134
|
+
let thenCompareEvents = include.thenCompareEvents;
|
|
135
|
+
|
|
136
|
+
let thenEventsWithError = include.thenEventsWithError;
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
Spec,
|
|
140
|
+
describe,
|
|
141
|
+
test,
|
|
142
|
+
givenEvents,
|
|
143
|
+
whenCmd,
|
|
144
|
+
thenEvent,
|
|
145
|
+
thenCompareEvent,
|
|
146
|
+
thenNoEvent,
|
|
147
|
+
thenEventWithError,
|
|
148
|
+
thenEvents,
|
|
149
|
+
thenCompareEvents,
|
|
150
|
+
thenEventsWithError,
|
|
151
|
+
thenError,
|
|
152
|
+
}
|
|
153
|
+
/* include Not a pure module */
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Multi-source ReadModel: one `_GWT` instance per source mapping. The
|
|
2
|
+
// `MultiSourceProjection_GWT.Make` functor is single-source — wire one GWT
|
|
3
|
+
// module per source view and let each describe its own slice of behaviour.
|
|
4
|
+
// (At runtime the two mappings merge into one row keyed by `customerId`; each
|
|
5
|
+
// module here exercises its source in isolation.)
|
|
6
|
+
|
|
7
|
+
module CustomerGwt = ReventlessGwt.MultiSourceProjection_GWT.Make(
|
|
8
|
+
Customers_Projections.CustomerMapping,
|
|
9
|
+
)
|
|
10
|
+
module OrderGwt = ReventlessGwt.MultiSourceProjection_GWT.Make(
|
|
11
|
+
Customers_Projections.CustomerOrdersMapping,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
CustomerGwt.describe("Customers ReadModel ← Customer aggregate", () => {
|
|
15
|
+
CustomerGwt.test("Registered sets initial read model state", () =>
|
|
16
|
+
CustomerGwt.givenEvents([])
|
|
17
|
+
->CustomerGwt.whenEvent(Customer.Registered({email: "alice@x.y", address: "123 Main"}))
|
|
18
|
+
->CustomerGwt.thenState({
|
|
19
|
+
Customers.email: "alice@x.y",
|
|
20
|
+
address: "123 Main",
|
|
21
|
+
deactivated: false,
|
|
22
|
+
orderCount: 0,
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
CustomerGwt.test("EmailUpdated updates the email", () =>
|
|
27
|
+
CustomerGwt.givenEvents([Customer.Registered({email: "alice@x.y", address: "123 Main"})])
|
|
28
|
+
->CustomerGwt.whenEvent(Customer.EmailUpdated({email: "alice2@x.y"}))
|
|
29
|
+
->CustomerGwt.thenState({
|
|
30
|
+
Customers.email: "alice2@x.y",
|
|
31
|
+
address: "123 Main",
|
|
32
|
+
deactivated: false,
|
|
33
|
+
orderCount: 0,
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
CustomerGwt.test("AddressUpdated updates the address", () =>
|
|
38
|
+
CustomerGwt.givenEvents([Customer.Registered({email: "alice@x.y", address: "123 Main"})])
|
|
39
|
+
->CustomerGwt.whenEvent(Customer.AddressUpdated({address: "789 Pine"}))
|
|
40
|
+
->CustomerGwt.thenState({
|
|
41
|
+
Customers.email: "alice@x.y",
|
|
42
|
+
address: "789 Pine",
|
|
43
|
+
deactivated: false,
|
|
44
|
+
orderCount: 0,
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
CustomerGwt.test("Deactivated sets deactivated flag", () =>
|
|
49
|
+
CustomerGwt.givenEvents([Customer.Registered({email: "alice@x.y", address: "123 Main"})])
|
|
50
|
+
->CustomerGwt.whenEvent(Customer.Deactivated)
|
|
51
|
+
->CustomerGwt.thenState({
|
|
52
|
+
Customers.email: "alice@x.y",
|
|
53
|
+
address: "123 Main",
|
|
54
|
+
deactivated: true,
|
|
55
|
+
orderCount: 0,
|
|
56
|
+
})
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
OrderGwt.describe("Customers ReadModel ← Ordering DCB log", () => {
|
|
61
|
+
OrderGwt.test("OrderPlaced creates a row and counts the placement", () =>
|
|
62
|
+
OrderGwt.givenEvents([])
|
|
63
|
+
->OrderGwt.whenEvent(
|
|
64
|
+
Customers_Projections.OrderEvents.OrderPlaced({orderId: "o1", customerId: "c1"}),
|
|
65
|
+
)
|
|
66
|
+
->OrderGwt.thenStateWithId(
|
|
67
|
+
"c1",
|
|
68
|
+
{Customers.email: "", address: "", deactivated: false, orderCount: 1},
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
OrderGwt.test("a second OrderPlaced increments orderCount", () =>
|
|
73
|
+
OrderGwt.givenEvents([
|
|
74
|
+
Customers_Projections.OrderEvents.OrderPlaced({orderId: "o1", customerId: "c1"}),
|
|
75
|
+
])
|
|
76
|
+
->OrderGwt.whenEvent(
|
|
77
|
+
Customers_Projections.OrderEvents.OrderPlaced({orderId: "o2", customerId: "c1"}),
|
|
78
|
+
)
|
|
79
|
+
->OrderGwt.thenStateWithId(
|
|
80
|
+
"c1",
|
|
81
|
+
{Customers.email: "", address: "", deactivated: false, orderCount: 2},
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
})
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Customers_Projections$OrderingPlugin from "../../../src/Customer/ReadModelStream/Customers_Projections.res.mjs";
|
|
4
|
+
import * as MultiSourceProjection_GWT$ReventlessGwt from "@reventlessdev/reventless-gwt/src/MultiSourceProjection_GWT.res.mjs";
|
|
5
|
+
|
|
6
|
+
let CustomerGwt = MultiSourceProjection_GWT$ReventlessGwt.Make(Customers_Projections$OrderingPlugin.CustomerMapping);
|
|
7
|
+
|
|
8
|
+
let OrderGwt = MultiSourceProjection_GWT$ReventlessGwt.Make(Customers_Projections$OrderingPlugin.CustomerOrdersMapping);
|
|
9
|
+
|
|
10
|
+
CustomerGwt.describe("Customers ReadModel ← Customer aggregate", () => {
|
|
11
|
+
CustomerGwt.test("Registered sets initial read model state", undefined, () => CustomerGwt.thenState(CustomerGwt.whenEvent(CustomerGwt.givenEvents([]), {
|
|
12
|
+
TAG: "Registered",
|
|
13
|
+
email: "alice@x.y",
|
|
14
|
+
address: "123 Main"
|
|
15
|
+
}), {
|
|
16
|
+
email: "alice@x.y",
|
|
17
|
+
address: "123 Main",
|
|
18
|
+
deactivated: false,
|
|
19
|
+
orderCount: 0
|
|
20
|
+
}));
|
|
21
|
+
CustomerGwt.test("EmailUpdated updates the email", undefined, () => CustomerGwt.thenState(CustomerGwt.whenEvent(CustomerGwt.givenEvents([{
|
|
22
|
+
TAG: "Registered",
|
|
23
|
+
email: "alice@x.y",
|
|
24
|
+
address: "123 Main"
|
|
25
|
+
}]), {
|
|
26
|
+
TAG: "EmailUpdated",
|
|
27
|
+
email: "alice2@x.y"
|
|
28
|
+
}), {
|
|
29
|
+
email: "alice2@x.y",
|
|
30
|
+
address: "123 Main",
|
|
31
|
+
deactivated: false,
|
|
32
|
+
orderCount: 0
|
|
33
|
+
}));
|
|
34
|
+
CustomerGwt.test("AddressUpdated updates the address", undefined, () => CustomerGwt.thenState(CustomerGwt.whenEvent(CustomerGwt.givenEvents([{
|
|
35
|
+
TAG: "Registered",
|
|
36
|
+
email: "alice@x.y",
|
|
37
|
+
address: "123 Main"
|
|
38
|
+
}]), {
|
|
39
|
+
TAG: "AddressUpdated",
|
|
40
|
+
address: "789 Pine"
|
|
41
|
+
}), {
|
|
42
|
+
email: "alice@x.y",
|
|
43
|
+
address: "789 Pine",
|
|
44
|
+
deactivated: false,
|
|
45
|
+
orderCount: 0
|
|
46
|
+
}));
|
|
47
|
+
CustomerGwt.test("Deactivated sets deactivated flag", undefined, () => CustomerGwt.thenState(CustomerGwt.whenEvent(CustomerGwt.givenEvents([{
|
|
48
|
+
TAG: "Registered",
|
|
49
|
+
email: "alice@x.y",
|
|
50
|
+
address: "123 Main"
|
|
51
|
+
}]), "Deactivated"), {
|
|
52
|
+
email: "alice@x.y",
|
|
53
|
+
address: "123 Main",
|
|
54
|
+
deactivated: true,
|
|
55
|
+
orderCount: 0
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
OrderGwt.describe("Customers ReadModel ← Ordering DCB log", () => {
|
|
60
|
+
OrderGwt.test("OrderPlaced creates a row and counts the placement", undefined, () => OrderGwt.thenStateWithId(OrderGwt.whenEvent(OrderGwt.givenEvents([]), {
|
|
61
|
+
TAG: "OrderPlaced",
|
|
62
|
+
orderId: "o1",
|
|
63
|
+
customerId: "c1"
|
|
64
|
+
}), "c1", {
|
|
65
|
+
email: "",
|
|
66
|
+
address: "",
|
|
67
|
+
deactivated: false,
|
|
68
|
+
orderCount: 1
|
|
69
|
+
}));
|
|
70
|
+
OrderGwt.test("a second OrderPlaced increments orderCount", undefined, () => OrderGwt.thenStateWithId(OrderGwt.whenEvent(OrderGwt.givenEvents([{
|
|
71
|
+
TAG: "OrderPlaced",
|
|
72
|
+
orderId: "o1",
|
|
73
|
+
customerId: "c1"
|
|
74
|
+
}]), {
|
|
75
|
+
TAG: "OrderPlaced",
|
|
76
|
+
orderId: "o2",
|
|
77
|
+
customerId: "c1"
|
|
78
|
+
}), "c1", {
|
|
79
|
+
email: "",
|
|
80
|
+
address: "",
|
|
81
|
+
deactivated: false,
|
|
82
|
+
orderCount: 2
|
|
83
|
+
}));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
CustomerGwt,
|
|
88
|
+
OrderGwt,
|
|
89
|
+
}
|
|
90
|
+
/* CustomerGwt Not a pure module */
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Boundary GWT for Ordering's Products extension: public Catalog events become
|
|
2
|
+
// SyncCatalogProduct commands on Ordering's local shadow slice.
|
|
3
|
+
@@reventless.gwt
|
|
4
|
+
|
|
5
|
+
// `Mapping` is brought into scope by the PPX `open Products_Extension`; opening
|
|
6
|
+
// it surfaces the extension point's events and the delegate's commands.
|
|
7
|
+
open Mapping
|
|
8
|
+
|
|
9
|
+
describe("Products Extension delegate", () => {
|
|
10
|
+
test("ProductBecameAvailable issues SyncNewProduct", () =>
|
|
11
|
+
whenIncomingEvent(
|
|
12
|
+
ExtensionPoint.ProductBecameAvailable({productId: "p1", name: "Book", price: 9.99}),
|
|
13
|
+
)->thenPublishesCommand(
|
|
14
|
+
Delegate.SyncNewProduct({productId: "p1", name: "Book", price: 9.99}),
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
test("ProductPriceChanged issues ChangeSyncedPrice", () =>
|
|
19
|
+
whenIncomingEvent(
|
|
20
|
+
ExtensionPoint.ProductPriceChanged({productId: "p1", price: 7.5}),
|
|
21
|
+
)->thenPublishesCommand(Delegate.ChangeSyncedPrice({productId: "p1", price: 7.5}))
|
|
22
|
+
)
|
|
23
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
4
|
+
import * as Delegate_GWT$ReventlessGwt from "@reventlessdev/reventless-gwt/src/Delegate_GWT.res.mjs";
|
|
5
|
+
import * as Products_Extension$OrderingPlugin from "../../src/Extension/Products_Extension.res.mjs";
|
|
6
|
+
import * as SyncCatalogProduct$OrderingPlugin from "../../src/CatalogProduct/StateChangeSlice/SyncCatalogProduct.res.mjs";
|
|
7
|
+
import * as Products_ExtensionPoint$CatalogSpec from "@reventlessdev/online-shop-hybrid-catalog-spec/src/Products_ExtensionPoint.res.mjs";
|
|
8
|
+
|
|
9
|
+
let include = Delegate_GWT$ReventlessGwt.FromExtension({
|
|
10
|
+
ExtensionPoint: {
|
|
11
|
+
name: Products_ExtensionPoint$CatalogSpec.name,
|
|
12
|
+
moduleUrl: Products_ExtensionPoint$CatalogSpec.moduleUrl,
|
|
13
|
+
commandSchema: Products_ExtensionPoint$CatalogSpec.commandSchema,
|
|
14
|
+
eventSchema: Products_ExtensionPoint$CatalogSpec.eventSchema,
|
|
15
|
+
directiveSchema: Products_ExtensionPoint$CatalogSpec.directiveSchema
|
|
16
|
+
},
|
|
17
|
+
Delegate: {
|
|
18
|
+
Id: Id$Reventless.$$String,
|
|
19
|
+
name: SyncCatalogProduct$OrderingPlugin.name,
|
|
20
|
+
eventSchema: SyncCatalogProduct$OrderingPlugin.eventSchema,
|
|
21
|
+
errorSchema: SyncCatalogProduct$OrderingPlugin.errorSchema,
|
|
22
|
+
commandSchema: SyncCatalogProduct$OrderingPlugin.commandSchema,
|
|
23
|
+
moduleUrl: SyncCatalogProduct$OrderingPlugin.moduleUrl,
|
|
24
|
+
commandAuthorization: SyncCatalogProduct$OrderingPlugin.commandAuthorization
|
|
25
|
+
},
|
|
26
|
+
moduleUrl: Products_Extension$OrderingPlugin.Mapping.moduleUrl,
|
|
27
|
+
delegateModuleUrl: Products_Extension$OrderingPlugin.Mapping.delegateModuleUrl,
|
|
28
|
+
mapIncomingEvent: Products_Extension$OrderingPlugin.Mapping.mapIncomingEvent,
|
|
29
|
+
mapOutgoingEvent: Products_Extension$OrderingPlugin.Mapping.mapOutgoingEvent
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
let describe = include.describe;
|
|
33
|
+
|
|
34
|
+
let test = include.test;
|
|
35
|
+
|
|
36
|
+
let whenIncomingEvent = include.whenIncomingEvent;
|
|
37
|
+
|
|
38
|
+
let thenPublishesCommand = include.thenPublishesCommand;
|
|
39
|
+
|
|
40
|
+
describe("Products Extension delegate", () => {
|
|
41
|
+
test("ProductBecameAvailable issues SyncNewProduct", undefined, () => thenPublishesCommand(whenIncomingEvent({
|
|
42
|
+
TAG: "ProductBecameAvailable",
|
|
43
|
+
productId: "p1",
|
|
44
|
+
name: "Book",
|
|
45
|
+
price: 9.99
|
|
46
|
+
}), {
|
|
47
|
+
TAG: "SyncNewProduct",
|
|
48
|
+
productId: "p1",
|
|
49
|
+
name: "Book",
|
|
50
|
+
price: 9.99
|
|
51
|
+
}));
|
|
52
|
+
test("ProductPriceChanged issues ChangeSyncedPrice", undefined, () => thenPublishesCommand(whenIncomingEvent({
|
|
53
|
+
TAG: "ProductPriceChanged",
|
|
54
|
+
productId: "p1",
|
|
55
|
+
price: 7.5
|
|
56
|
+
}), {
|
|
57
|
+
TAG: "ChangeSyncedPrice",
|
|
58
|
+
productId: "p1",
|
|
59
|
+
price: 7.5
|
|
60
|
+
}));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let encCmd = include.encCmd;
|
|
64
|
+
|
|
65
|
+
let encEpCmd = include.encEpCmd;
|
|
66
|
+
|
|
67
|
+
let encDirective = include.encDirective;
|
|
68
|
+
|
|
69
|
+
let EvCore = include.EvCore;
|
|
70
|
+
|
|
71
|
+
let OutCore = include.OutCore;
|
|
72
|
+
|
|
73
|
+
let thenPublishesNothing = include.thenPublishesNothing;
|
|
74
|
+
|
|
75
|
+
let thenPublishesCommands = include.thenPublishesCommands;
|
|
76
|
+
|
|
77
|
+
let thenPublishesAggregateCommand = include.thenPublishesAggregateCommand;
|
|
78
|
+
|
|
79
|
+
let whenDelegateEvent = include.whenDelegateEvent;
|
|
80
|
+
|
|
81
|
+
let thenPublishesExtensionPointCommand = include.thenPublishesExtensionPointCommand;
|
|
82
|
+
|
|
83
|
+
let thenHandlesDirective = include.thenHandlesDirective;
|
|
84
|
+
|
|
85
|
+
let thenHandlesDirectives = include.thenHandlesDirectives;
|
|
86
|
+
|
|
87
|
+
let thenHandlesNoDirective = include.thenHandlesNoDirective;
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
encCmd,
|
|
91
|
+
encEpCmd,
|
|
92
|
+
encDirective,
|
|
93
|
+
EvCore,
|
|
94
|
+
OutCore,
|
|
95
|
+
describe,
|
|
96
|
+
test,
|
|
97
|
+
thenPublishesNothing,
|
|
98
|
+
whenIncomingEvent,
|
|
99
|
+
thenPublishesCommand,
|
|
100
|
+
thenPublishesCommands,
|
|
101
|
+
thenPublishesAggregateCommand,
|
|
102
|
+
whenDelegateEvent,
|
|
103
|
+
thenPublishesExtensionPointCommand,
|
|
104
|
+
thenHandlesDirective,
|
|
105
|
+
thenHandlesDirectives,
|
|
106
|
+
thenHandlesNoDirective,
|
|
107
|
+
}
|
|
108
|
+
/* include Not a pure module */
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Boundary GWT for the Orders extension point: batch order events decompose
|
|
2
|
+
// into one public per-product event each (one-to-many fan-out).
|
|
3
|
+
@@reventless.gwt
|
|
4
|
+
|
|
5
|
+
describe("Orders ExtensionPoint mapping — per-product fan-out", () => {
|
|
6
|
+
test("OrderPlaced fans out to one ItemOrdered per product", () =>
|
|
7
|
+
whenDelegateEvent(
|
|
8
|
+
Delegate.OrderPlaced({orderId: "o1", customerId: "c1", productIds: ["p1", "p2"]}),
|
|
9
|
+
)->thenPublishesEvents([
|
|
10
|
+
("p1", ExtensionPoint.ItemOrdered({productId: "p1", orderId: "o1", customerId: "c1"})),
|
|
11
|
+
("p2", ExtensionPoint.ItemOrdered({productId: "p2", orderId: "o1", customerId: "c1"})),
|
|
12
|
+
])
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
test("OrderCancelled fans out to one ItemOrderCancelled per product", () =>
|
|
16
|
+
whenDelegateEvent(
|
|
17
|
+
Delegate.OrderCancelled({orderId: "o1", productIds: ["p1", "p2"]}),
|
|
18
|
+
)->thenPublishesEvents([
|
|
19
|
+
("p1", ExtensionPoint.ItemOrderCancelled({productId: "p1", orderId: "o1"})),
|
|
20
|
+
("p2", ExtensionPoint.ItemOrderCancelled({productId: "p2", orderId: "o1"})),
|
|
21
|
+
])
|
|
22
|
+
)
|
|
23
|
+
})
|