@reventlessdev/reventless-gwt 1.0.0-alpha.74
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 +698 -0
- package/LICENSE +202 -0
- package/bin/reventless-dev.mjs +21 -0
- package/package.json +54 -0
- package/rescript.json +36 -0
- package/src/.gwtignore +10 -0
- package/src/Automation_GWT.res +207 -0
- package/src/Automation_GWT.res.mjs +160 -0
- package/src/Behavior_GWT.res +603 -0
- package/src/Behavior_GWT.res.mjs +426 -0
- package/src/Bind.res +29 -0
- package/src/Bind.res.mjs +22 -0
- package/src/BuildClassifier.res +137 -0
- package/src/BuildClassifier.res.mjs +136 -0
- package/src/Cancellation.res +31 -0
- package/src/Cancellation.res.mjs +45 -0
- package/src/ChildProcess.res +84 -0
- package/src/ChildProcess.res.mjs +92 -0
- package/src/Cli.res +671 -0
- package/src/Cli.res.mjs +754 -0
- package/src/Collector.res +201 -0
- package/src/Collector.res.mjs +215 -0
- package/src/ComponentMeta.res +104 -0
- package/src/ComponentMeta.res.mjs +120 -0
- package/src/ComponentScan.res +103 -0
- package/src/ComponentScan.res.mjs +105 -0
- package/src/Delegate_GWT.res +303 -0
- package/src/Delegate_GWT.res.mjs +372 -0
- package/src/Diff.res +91 -0
- package/src/Diff.res.mjs +98 -0
- package/src/Discovery.res +105 -0
- package/src/Discovery.res.mjs +103 -0
- package/src/DomainDeadCode.res +93 -0
- package/src/DomainDeadCode.res.mjs +69 -0
- package/src/DomainGraph.res +0 -0
- package/src/DomainGraph.res.mjs +0 -0
- package/src/EventMapping_GWT.res +29 -0
- package/src/EventMapping_GWT.res.mjs +44 -0
- package/src/Filter.res +36 -0
- package/src/Filter.res.mjs +40 -0
- package/src/Flow_GWT.res +664 -0
- package/src/Flow_GWT.res.mjs +604 -0
- package/src/FormatterHuman.res +138 -0
- package/src/FormatterHuman.res.mjs +122 -0
- package/src/FormatterJson.res +257 -0
- package/src/FormatterJson.res.mjs +320 -0
- package/src/FormatterJunit.res +93 -0
- package/src/FormatterJunit.res.mjs +90 -0
- package/src/FormatterTap.res +139 -0
- package/src/FormatterTap.res.mjs +149 -0
- package/src/FormatterVsCode.res +362 -0
- package/src/FormatterVsCode.res.mjs +564 -0
- package/src/Hint.res +119 -0
- package/src/Hint.res.mjs +124 -0
- package/src/InboundTranslation_GWT.res +83 -0
- package/src/InboundTranslation_GWT.res.mjs +94 -0
- package/src/JestBind.res +70 -0
- package/src/JestBind.res.mjs +63 -0
- package/src/Loader.res +24 -0
- package/src/Loader.res.mjs +23 -0
- package/src/LocalHost.res +135 -0
- package/src/LocalHost.res.mjs +127 -0
- package/src/Mapping_GWT.res +474 -0
- package/src/Mapping_GWT.res.mjs +322 -0
- package/src/MultiSourceProjection_GWT.res +316 -0
- package/src/MultiSourceProjection_GWT.res.mjs +354 -0
- package/src/OutboundTranslation_GWT.res +238 -0
- package/src/OutboundTranslation_GWT.res.mjs +186 -0
- package/src/Outcome.res +161 -0
- package/src/Outcome.res.mjs +140 -0
- package/src/PackageScan.res +80 -0
- package/src/PackageScan.res.mjs +81 -0
- package/src/PlatformRunner.res +160 -0
- package/src/PlatformRunner.res.mjs +155 -0
- package/src/PlatformScan.res +138 -0
- package/src/PlatformScan.res.mjs +131 -0
- package/src/ProcessManager.res +91 -0
- package/src/ProcessManager.res.mjs +80 -0
- package/src/Projection_GWT.res +312 -0
- package/src/Projection_GWT.res.mjs +332 -0
- package/src/Query_GWT.res +379 -0
- package/src/Query_GWT.res.mjs +367 -0
- package/src/RenderRescript.res +125 -0
- package/src/RenderRescript.res.mjs +141 -0
- package/src/RunnerTypes.res +68 -0
- package/src/RunnerTypes.res.mjs +60 -0
- package/src/SideEffect_GWT.res +157 -0
- package/src/SideEffect_GWT.res.mjs +113 -0
- package/src/StateChange/ExternalAddCategorySlice.res +25 -0
- package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
- package/src/StateChange/WithFixtures.res +22 -0
- package/src/StateChange/WithFixtures.res.mjs +31 -0
- package/src/StateChange/WithFixtures_Behavior.res +17 -0
- package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
- package/src/StateChange/WithManualOpen.res +23 -0
- package/src/StateChange/WithManualOpen.res.mjs +31 -0
- package/src/StateChange/WithManualOpen_Behavior.res +17 -0
- package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
- package/src/StubRuntime.res +53 -0
- package/src/StubRuntime.res.mjs +54 -0
- package/src/TestFixtures.res +16 -0
- package/src/TestFixtures.res.mjs +34 -0
- package/src/Watch.res +92 -0
- package/src/Watch.res.mjs +83 -0
- package/src/WatcherProbe.res +48 -0
- package/src/WatcherProbe.res.mjs +46 -0
- package/tests/AutomationGwtTest.res +71 -0
- package/tests/AutomationGwtTest.res.mjs +189 -0
- package/tests/BuildClassifierTest.res +94 -0
- package/tests/BuildClassifierTest.res.mjs +132 -0
- package/tests/ComponentMetaTest.res +52 -0
- package/tests/ComponentMetaTest.res.mjs +57 -0
- package/tests/DelegateGwtTest.res +487 -0
- package/tests/DelegateGwtTest.res.mjs +891 -0
- package/tests/DiscoveryTest.res +42 -0
- package/tests/DiscoveryTest.res.mjs +43 -0
- package/tests/DomainDeadCodeTest.res +116 -0
- package/tests/DomainDeadCodeTest.res.mjs +155 -0
- package/tests/DomainGraphTest.res +365 -0
- package/tests/DomainGraphTest.res.mjs +413 -0
- package/tests/FlowAggregateGwtTest.res +169 -0
- package/tests/FlowAggregateGwtTest.res.mjs +269 -0
- package/tests/FlowCrossPluginGwtTest.res +340 -0
- package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
- package/tests/FlowGwtTest.res +222 -0
- package/tests/FlowGwtTest.res.mjs +432 -0
- package/tests/FormatterVsCodeMessageTest.res +41 -0
- package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
- package/tests/InboundTranslationGwtTest.res +34 -0
- package/tests/InboundTranslationGwtTest.res.mjs +98 -0
- package/tests/LocalHostIntegration.res +70 -0
- package/tests/LocalHostIntegration.res.mjs +64 -0
- package/tests/LocalHostTest.res +71 -0
- package/tests/LocalHostTest.res.mjs +63 -0
- package/tests/MappingGwtTest.res +348 -0
- package/tests/MappingGwtTest.res.mjs +592 -0
- package/tests/OutboundTranslationGwtTest.res +59 -0
- package/tests/OutboundTranslationGwtTest.res.mjs +138 -0
- package/tests/PlatformRunnerTest.res +66 -0
- package/tests/PlatformRunnerTest.res.mjs +85 -0
- package/tests/PlatformScanTest.res +61 -0
- package/tests/PlatformScanTest.res.mjs +56 -0
- package/tests/QueryGwtTest.res +161 -0
- package/tests/QueryGwtTest.res.mjs +314 -0
- package/tests/QueryResolverGwtTest.res +71 -0
- package/tests/QueryResolverGwtTest.res.mjs +136 -0
- package/tests/RunnerUnitTest.res +219 -0
- package/tests/RunnerUnitTest.res.mjs +244 -0
- package/tests/SideEffect/FakeOrderNotification.res +23 -0
- package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
- package/tests/SideEffect/MockEmail.res +18 -0
- package/tests/SideEffect/MockEmail.res.mjs +47 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
- package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
- package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithFixtures_GWT.res +23 -0
- package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithManualOpen_GWT.res +17 -0
- package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
- package/tests/StateChangeSliceGwtTest.res +160 -0
- package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
- package/tests/StateViewSliceGwtTest.res +64 -0
- package/tests/StateViewSliceGwtTest.res.mjs +161 -0
- package/tests/WatchDebounceTest.res +78 -0
- package/tests/WatchDebounceTest.res.mjs +97 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// Worked examples for Mapping_GWT — one for each producer/consumer combination
|
|
2
|
+
// (Aggr→Aggr, Aggr→DCB, DCB→Aggr, DCB→DCB). See
|
|
3
|
+
// `docs/analysis/event-source-connection-matrix.md` for the connection matrix
|
|
4
|
+
// and `docs/plans/reventless-gwt.md` Stage 5 for the plan context.
|
|
5
|
+
//
|
|
6
|
+
// Each test exercises the full source-command → mapping → target-command →
|
|
7
|
+
// target-decide pipeline end-to-end against the unified `GwtSource`/`GwtTarget`
|
|
8
|
+
// module types.
|
|
9
|
+
|
|
10
|
+
S.enableJson()
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Shared Aggregate specs + behaviours used by the Aggr→* cases below.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
module CategorySpec = {
|
|
17
|
+
module Id = Reventless.Id.StringPure
|
|
18
|
+
let name = "Category"
|
|
19
|
+
|
|
20
|
+
@schema
|
|
21
|
+
type command = AddCategory({categoryId: string, name: string})
|
|
22
|
+
|
|
23
|
+
@schema
|
|
24
|
+
type event = CategoryAdded({categoryId: string, name: string})
|
|
25
|
+
|
|
26
|
+
@schema
|
|
27
|
+
type error = CategoryAlreadyExists
|
|
28
|
+
|
|
29
|
+
let moduleUrl = ""
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module CategoryBehavior = {
|
|
33
|
+
module Spec = CategorySpec
|
|
34
|
+
type state = NotCreated | Created
|
|
35
|
+
let initialState = NotCreated
|
|
36
|
+
|
|
37
|
+
let evolve = (_state, event: CategorySpec.event) =>
|
|
38
|
+
switch event {
|
|
39
|
+
| CategoryAdded(_) => Created
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let decide = (state, cmd: CategorySpec.command) =>
|
|
43
|
+
switch (state, cmd) {
|
|
44
|
+
| (NotCreated, AddCategory({categoryId, name})) =>
|
|
45
|
+
Ok([CategorySpec.CategoryAdded({categoryId, name})])
|
|
46
|
+
| (Created, AddCategory(_)) => Error(CategorySpec.CategoryAlreadyExists)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let moduleUrl = ""
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Target aggregate for Aggr→Aggr and DCB→Aggr cases.
|
|
53
|
+
module ProductSpec = {
|
|
54
|
+
module Id = Reventless.Id.StringPure
|
|
55
|
+
let name = "Product"
|
|
56
|
+
|
|
57
|
+
@schema
|
|
58
|
+
type command = MirrorCategory({productId: string, categoryId: string, name: string})
|
|
59
|
+
|
|
60
|
+
@schema
|
|
61
|
+
type event = ProductCategoryMirrored({productId: string, categoryId: string, name: string})
|
|
62
|
+
|
|
63
|
+
@schema
|
|
64
|
+
type error = ProductAlreadyMirrored
|
|
65
|
+
|
|
66
|
+
let moduleUrl = ""
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module ProductBehavior = {
|
|
70
|
+
module Spec = ProductSpec
|
|
71
|
+
type state = Pristine | Mirrored
|
|
72
|
+
let initialState = Pristine
|
|
73
|
+
|
|
74
|
+
let evolve = (_state, event: ProductSpec.event) =>
|
|
75
|
+
switch event {
|
|
76
|
+
| ProductCategoryMirrored(_) => Mirrored
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let decide = (state, cmd: ProductSpec.command) =>
|
|
80
|
+
switch (state, cmd) {
|
|
81
|
+
| (Pristine, MirrorCategory({productId, categoryId, name})) =>
|
|
82
|
+
Ok([ProductSpec.ProductCategoryMirrored({productId, categoryId, name})])
|
|
83
|
+
| (Mirrored, MirrorCategory(_)) => Error(ProductSpec.ProductAlreadyMirrored)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let moduleUrl = ""
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Shared StateChangeSlice specs used by the *→DCB and DCB→* cases.
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
module NotificationSlice = {
|
|
94
|
+
let name = "Notification"
|
|
95
|
+
|
|
96
|
+
type state = {sent: bool}
|
|
97
|
+
let initialState = {sent: false}
|
|
98
|
+
|
|
99
|
+
@schema
|
|
100
|
+
type consumedEvent =
|
|
101
|
+
| NotificationSent({
|
|
102
|
+
notificationId: @s.matches(Reventless.DcbTag.string) string,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
let evolve = (_state, event) =>
|
|
106
|
+
switch event {
|
|
107
|
+
| NotificationSent(_) => {sent: true}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@schema
|
|
111
|
+
type command =
|
|
112
|
+
| SendNotification({
|
|
113
|
+
notificationId: @s.matches(Reventless.DcbTag.string) string,
|
|
114
|
+
message: string,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
@schema
|
|
118
|
+
type error = NotificationAlreadySent
|
|
119
|
+
|
|
120
|
+
@schema
|
|
121
|
+
type event =
|
|
122
|
+
| NotificationSent({
|
|
123
|
+
notificationId: @s.matches(Reventless.DcbTag.string) string,
|
|
124
|
+
message: string,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
let decide = (state, command) =>
|
|
128
|
+
switch command {
|
|
129
|
+
| SendNotification({notificationId, message}) =>
|
|
130
|
+
state.sent
|
|
131
|
+
? Error(NotificationAlreadySent)
|
|
132
|
+
: Ok([NotificationSent({notificationId, message})])
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module InventorySlice = {
|
|
137
|
+
let name = "Inventory"
|
|
138
|
+
|
|
139
|
+
type state = {reserved: bool}
|
|
140
|
+
let initialState = {reserved: false}
|
|
141
|
+
|
|
142
|
+
@schema
|
|
143
|
+
type consumedEvent =
|
|
144
|
+
| StockReserved({itemId: @s.matches(Reventless.DcbTag.string) string})
|
|
145
|
+
|
|
146
|
+
let evolve = (_state, event) =>
|
|
147
|
+
switch event {
|
|
148
|
+
| StockReserved(_) => {reserved: true}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@schema
|
|
152
|
+
type command =
|
|
153
|
+
| ReserveStock({
|
|
154
|
+
itemId: @s.matches(Reventless.DcbTag.string) string,
|
|
155
|
+
quantity: int,
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
@schema
|
|
159
|
+
type error = OutOfStock
|
|
160
|
+
|
|
161
|
+
@schema
|
|
162
|
+
type event =
|
|
163
|
+
| StockReserved({
|
|
164
|
+
itemId: @s.matches(Reventless.DcbTag.string) string,
|
|
165
|
+
quantity: int,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
let decide = (state, command) =>
|
|
169
|
+
switch command {
|
|
170
|
+
| ReserveStock({itemId, quantity}) =>
|
|
171
|
+
state.reserved ? Error(OutOfStock) : Ok([StockReserved({itemId, quantity})])
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Adapter aliases — built once, shared across mappings.
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
module CategorySource = Mapping_GWT.FromBehavior(CategorySpec, CategoryBehavior)
|
|
180
|
+
module ProductTarget = Mapping_GWT.FromBehavior(ProductSpec, ProductBehavior)
|
|
181
|
+
module NotificationSource = Mapping_GWT.FromStateChangeSlice(NotificationSlice)
|
|
182
|
+
module NotificationTarget = Mapping_GWT.FromStateChangeSlice(NotificationSlice)
|
|
183
|
+
module InventoryTarget = Mapping_GWT.FromStateChangeSlice(InventorySlice)
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Case 1 — Aggr → Aggr: CategoryAdded triggers MirrorCategory on Product.
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
module AggrToAggrMapping = {
|
|
190
|
+
module Source = CategorySource
|
|
191
|
+
module Target = ProductTarget
|
|
192
|
+
|
|
193
|
+
let map = (_sourceId, event: CategorySpec.event, _q) =>
|
|
194
|
+
switch event {
|
|
195
|
+
| CategoryAdded({categoryId, name}) => [
|
|
196
|
+
Reventless.EventMapping.Publish(
|
|
197
|
+
("prod-for-" ++ categoryId)->ProductSpec.Id.makeFromString,
|
|
198
|
+
ProductSpec.MirrorCategory({productId: "prod-for-" ++ categoryId, categoryId, name}),
|
|
199
|
+
),
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module AggrToAggrGwt = Mapping_GWT.Make(AggrToAggrMapping)
|
|
205
|
+
|
|
206
|
+
AggrToAggrGwt.describe("Category → Product (Aggr → Aggr)", () =>
|
|
207
|
+
AggrToAggrGwt.test("AddCategory produces ProductCategoryMirrored on product", () =>
|
|
208
|
+
AggrToAggrGwt.givenSourceEvents([])
|
|
209
|
+
->AggrToAggrGwt.andTargetEvents([])
|
|
210
|
+
->AggrToAggrGwt.whenSourceCmd("c1", AddCategory({categoryId: "c1", name: "Books"}))
|
|
211
|
+
->AggrToAggrGwt.thenTargetEvent(
|
|
212
|
+
"prod-for-c1",
|
|
213
|
+
ProductCategoryMirrored({productId: "prod-for-c1", categoryId: "c1", name: "Books"}),
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Case 2 — Aggr → DCB: CategoryAdded triggers SendNotification on Notification slice.
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
module AggrToDcbMapping = {
|
|
223
|
+
module Source = CategorySource
|
|
224
|
+
module Target = NotificationTarget
|
|
225
|
+
|
|
226
|
+
let map = (_sourceId, event: CategorySpec.event, _q) =>
|
|
227
|
+
switch event {
|
|
228
|
+
| CategoryAdded({categoryId, name}) => [
|
|
229
|
+
Reventless.EventMapping.Publish(
|
|
230
|
+
("notif-" ++ categoryId)->NotificationTarget.Id.makeFromString,
|
|
231
|
+
NotificationSlice.SendNotification({
|
|
232
|
+
notificationId: "notif-" ++ categoryId,
|
|
233
|
+
message: `Category ${name} was created`,
|
|
234
|
+
}),
|
|
235
|
+
),
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module AggrToDcbGwt = Mapping_GWT.Make(AggrToDcbMapping)
|
|
241
|
+
|
|
242
|
+
AggrToDcbGwt.describe("Category → Notification (Aggr → DCB)", () =>
|
|
243
|
+
AggrToDcbGwt.test("AddCategory triggers NotificationSent on the DCB slice", () =>
|
|
244
|
+
AggrToDcbGwt.givenSourceEvents([])
|
|
245
|
+
->AggrToDcbGwt.andTargetEvents([])
|
|
246
|
+
->AggrToDcbGwt.whenSourceCmd("c2", AddCategory({categoryId: "c2", name: "Media"}))
|
|
247
|
+
->AggrToDcbGwt.thenTargetEvent(
|
|
248
|
+
"notif-c2",
|
|
249
|
+
NotificationSlice.NotificationSent({
|
|
250
|
+
notificationId: "notif-c2",
|
|
251
|
+
message: "Category Media was created",
|
|
252
|
+
}),
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Case 3 — DCB → Aggr: Inventory StockReserved triggers MirrorCategory on Product.
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
module InventorySource = Mapping_GWT.FromStateChangeSlice(InventorySlice)
|
|
262
|
+
|
|
263
|
+
module DcbToAggrMapping = {
|
|
264
|
+
module Source = InventorySource
|
|
265
|
+
module Target = ProductTarget
|
|
266
|
+
|
|
267
|
+
let map = (_sourceId, event: InventorySlice.event, _q) =>
|
|
268
|
+
switch event {
|
|
269
|
+
| StockReserved({itemId, quantity: _}) => [
|
|
270
|
+
Reventless.EventMapping.Publish(
|
|
271
|
+
("prod-" ++ itemId)->ProductSpec.Id.makeFromString,
|
|
272
|
+
ProductSpec.MirrorCategory({
|
|
273
|
+
productId: "prod-" ++ itemId,
|
|
274
|
+
categoryId: itemId,
|
|
275
|
+
name: itemId,
|
|
276
|
+
}),
|
|
277
|
+
),
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module DcbToAggrGwt = Mapping_GWT.Make(DcbToAggrMapping)
|
|
283
|
+
|
|
284
|
+
DcbToAggrGwt.describe("Inventory → Product (DCB → Aggr)", () =>
|
|
285
|
+
DcbToAggrGwt.test("ReserveStock triggers ProductCategoryMirrored", () =>
|
|
286
|
+
DcbToAggrGwt.givenSourceEvents([])
|
|
287
|
+
->DcbToAggrGwt.andTargetEvents([])
|
|
288
|
+
->DcbToAggrGwt.whenSourceCmd("i1", ReserveStock({itemId: "i1", quantity: 5}))
|
|
289
|
+
->DcbToAggrGwt.thenTargetEvent(
|
|
290
|
+
"prod-i1",
|
|
291
|
+
ProductCategoryMirrored({productId: "prod-i1", categoryId: "i1", name: "i1"}),
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
// Case 4 — DCB → DCB: Inventory StockReserved triggers SendNotification on Notification slice.
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
module DcbToDcbMapping = {
|
|
301
|
+
module Source = InventorySource
|
|
302
|
+
module Target = NotificationTarget
|
|
303
|
+
|
|
304
|
+
let map = (_sourceId, event: InventorySlice.event, _q) =>
|
|
305
|
+
switch event {
|
|
306
|
+
| StockReserved({itemId, quantity}) => [
|
|
307
|
+
Reventless.EventMapping.Publish(
|
|
308
|
+
("reserved-" ++ itemId)->NotificationTarget.Id.makeFromString,
|
|
309
|
+
NotificationSlice.SendNotification({
|
|
310
|
+
notificationId: "reserved-" ++ itemId,
|
|
311
|
+
message: `Reserved ${quantity->Int.toString} of ${itemId}`,
|
|
312
|
+
}),
|
|
313
|
+
),
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
module DcbToDcbGwt = Mapping_GWT.Make(DcbToDcbMapping)
|
|
319
|
+
|
|
320
|
+
DcbToDcbGwt.describe("Inventory → Notification (DCB → DCB)", () => {
|
|
321
|
+
DcbToDcbGwt.test("ReserveStock triggers NotificationSent", () =>
|
|
322
|
+
DcbToDcbGwt.givenSourceEvents([])
|
|
323
|
+
->DcbToDcbGwt.andTargetEvents([])
|
|
324
|
+
->DcbToDcbGwt.whenSourceCmd("i7", ReserveStock({itemId: "i7", quantity: 2}))
|
|
325
|
+
->DcbToDcbGwt.thenTargetEvent(
|
|
326
|
+
"reserved-i7",
|
|
327
|
+
NotificationSlice.NotificationSent({
|
|
328
|
+
notificationId: "reserved-i7",
|
|
329
|
+
message: "Reserved 2 of i7",
|
|
330
|
+
}),
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
// Target-error coverage: if the notification was already sent, SendNotification
|
|
335
|
+
// fails with `NotificationAlreadySent`. `thenTargetError` surfaces the target's
|
|
336
|
+
// decide error without requiring the source to produce no events.
|
|
337
|
+
DcbToDcbGwt.test("existing NotificationSent causes target decide to reject", () =>
|
|
338
|
+
DcbToDcbGwt.givenSourceEvents([])
|
|
339
|
+
->DcbToDcbGwt.andTargetEvents([
|
|
340
|
+
(
|
|
341
|
+
"reserved-i7",
|
|
342
|
+
[NotificationSlice.NotificationSent({notificationId: "reserved-i7"})],
|
|
343
|
+
),
|
|
344
|
+
])
|
|
345
|
+
->DcbToDcbGwt.whenSourceCmd("i7", ReserveStock({itemId: "i7", quantity: 2}))
|
|
346
|
+
->DcbToDcbGwt.thenTargetError(NotificationAlreadySent)
|
|
347
|
+
)
|
|
348
|
+
})
|