@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.
Files changed (172) hide show
  1. package/CHANGELOG.md +698 -0
  2. package/LICENSE +202 -0
  3. package/bin/reventless-dev.mjs +21 -0
  4. package/package.json +54 -0
  5. package/rescript.json +36 -0
  6. package/src/.gwtignore +10 -0
  7. package/src/Automation_GWT.res +207 -0
  8. package/src/Automation_GWT.res.mjs +160 -0
  9. package/src/Behavior_GWT.res +603 -0
  10. package/src/Behavior_GWT.res.mjs +426 -0
  11. package/src/Bind.res +29 -0
  12. package/src/Bind.res.mjs +22 -0
  13. package/src/BuildClassifier.res +137 -0
  14. package/src/BuildClassifier.res.mjs +136 -0
  15. package/src/Cancellation.res +31 -0
  16. package/src/Cancellation.res.mjs +45 -0
  17. package/src/ChildProcess.res +84 -0
  18. package/src/ChildProcess.res.mjs +92 -0
  19. package/src/Cli.res +671 -0
  20. package/src/Cli.res.mjs +754 -0
  21. package/src/Collector.res +201 -0
  22. package/src/Collector.res.mjs +215 -0
  23. package/src/ComponentMeta.res +104 -0
  24. package/src/ComponentMeta.res.mjs +120 -0
  25. package/src/ComponentScan.res +103 -0
  26. package/src/ComponentScan.res.mjs +105 -0
  27. package/src/Delegate_GWT.res +303 -0
  28. package/src/Delegate_GWT.res.mjs +372 -0
  29. package/src/Diff.res +91 -0
  30. package/src/Diff.res.mjs +98 -0
  31. package/src/Discovery.res +105 -0
  32. package/src/Discovery.res.mjs +103 -0
  33. package/src/DomainDeadCode.res +93 -0
  34. package/src/DomainDeadCode.res.mjs +69 -0
  35. package/src/DomainGraph.res +0 -0
  36. package/src/DomainGraph.res.mjs +0 -0
  37. package/src/EventMapping_GWT.res +29 -0
  38. package/src/EventMapping_GWT.res.mjs +44 -0
  39. package/src/Filter.res +36 -0
  40. package/src/Filter.res.mjs +40 -0
  41. package/src/Flow_GWT.res +664 -0
  42. package/src/Flow_GWT.res.mjs +604 -0
  43. package/src/FormatterHuman.res +138 -0
  44. package/src/FormatterHuman.res.mjs +122 -0
  45. package/src/FormatterJson.res +257 -0
  46. package/src/FormatterJson.res.mjs +320 -0
  47. package/src/FormatterJunit.res +93 -0
  48. package/src/FormatterJunit.res.mjs +90 -0
  49. package/src/FormatterTap.res +139 -0
  50. package/src/FormatterTap.res.mjs +149 -0
  51. package/src/FormatterVsCode.res +362 -0
  52. package/src/FormatterVsCode.res.mjs +564 -0
  53. package/src/Hint.res +119 -0
  54. package/src/Hint.res.mjs +124 -0
  55. package/src/InboundTranslation_GWT.res +83 -0
  56. package/src/InboundTranslation_GWT.res.mjs +94 -0
  57. package/src/JestBind.res +70 -0
  58. package/src/JestBind.res.mjs +63 -0
  59. package/src/Loader.res +24 -0
  60. package/src/Loader.res.mjs +23 -0
  61. package/src/LocalHost.res +135 -0
  62. package/src/LocalHost.res.mjs +127 -0
  63. package/src/Mapping_GWT.res +474 -0
  64. package/src/Mapping_GWT.res.mjs +322 -0
  65. package/src/MultiSourceProjection_GWT.res +316 -0
  66. package/src/MultiSourceProjection_GWT.res.mjs +354 -0
  67. package/src/OutboundTranslation_GWT.res +238 -0
  68. package/src/OutboundTranslation_GWT.res.mjs +186 -0
  69. package/src/Outcome.res +161 -0
  70. package/src/Outcome.res.mjs +140 -0
  71. package/src/PackageScan.res +80 -0
  72. package/src/PackageScan.res.mjs +81 -0
  73. package/src/PlatformRunner.res +160 -0
  74. package/src/PlatformRunner.res.mjs +155 -0
  75. package/src/PlatformScan.res +138 -0
  76. package/src/PlatformScan.res.mjs +131 -0
  77. package/src/ProcessManager.res +91 -0
  78. package/src/ProcessManager.res.mjs +80 -0
  79. package/src/Projection_GWT.res +312 -0
  80. package/src/Projection_GWT.res.mjs +332 -0
  81. package/src/Query_GWT.res +379 -0
  82. package/src/Query_GWT.res.mjs +367 -0
  83. package/src/RenderRescript.res +125 -0
  84. package/src/RenderRescript.res.mjs +141 -0
  85. package/src/RunnerTypes.res +68 -0
  86. package/src/RunnerTypes.res.mjs +60 -0
  87. package/src/SideEffect_GWT.res +157 -0
  88. package/src/SideEffect_GWT.res.mjs +113 -0
  89. package/src/StateChange/ExternalAddCategorySlice.res +25 -0
  90. package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
  91. package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
  92. package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
  93. package/src/StateChange/WithFixtures.res +22 -0
  94. package/src/StateChange/WithFixtures.res.mjs +31 -0
  95. package/src/StateChange/WithFixtures_Behavior.res +17 -0
  96. package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
  97. package/src/StateChange/WithManualOpen.res +23 -0
  98. package/src/StateChange/WithManualOpen.res.mjs +31 -0
  99. package/src/StateChange/WithManualOpen_Behavior.res +17 -0
  100. package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
  101. package/src/StubRuntime.res +53 -0
  102. package/src/StubRuntime.res.mjs +54 -0
  103. package/src/TestFixtures.res +16 -0
  104. package/src/TestFixtures.res.mjs +34 -0
  105. package/src/Watch.res +92 -0
  106. package/src/Watch.res.mjs +83 -0
  107. package/src/WatcherProbe.res +48 -0
  108. package/src/WatcherProbe.res.mjs +46 -0
  109. package/tests/AutomationGwtTest.res +71 -0
  110. package/tests/AutomationGwtTest.res.mjs +189 -0
  111. package/tests/BuildClassifierTest.res +94 -0
  112. package/tests/BuildClassifierTest.res.mjs +132 -0
  113. package/tests/ComponentMetaTest.res +52 -0
  114. package/tests/ComponentMetaTest.res.mjs +57 -0
  115. package/tests/DelegateGwtTest.res +487 -0
  116. package/tests/DelegateGwtTest.res.mjs +891 -0
  117. package/tests/DiscoveryTest.res +42 -0
  118. package/tests/DiscoveryTest.res.mjs +43 -0
  119. package/tests/DomainDeadCodeTest.res +116 -0
  120. package/tests/DomainDeadCodeTest.res.mjs +155 -0
  121. package/tests/DomainGraphTest.res +365 -0
  122. package/tests/DomainGraphTest.res.mjs +413 -0
  123. package/tests/FlowAggregateGwtTest.res +169 -0
  124. package/tests/FlowAggregateGwtTest.res.mjs +269 -0
  125. package/tests/FlowCrossPluginGwtTest.res +340 -0
  126. package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
  127. package/tests/FlowGwtTest.res +222 -0
  128. package/tests/FlowGwtTest.res.mjs +432 -0
  129. package/tests/FormatterVsCodeMessageTest.res +41 -0
  130. package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
  131. package/tests/InboundTranslationGwtTest.res +34 -0
  132. package/tests/InboundTranslationGwtTest.res.mjs +98 -0
  133. package/tests/LocalHostIntegration.res +70 -0
  134. package/tests/LocalHostIntegration.res.mjs +64 -0
  135. package/tests/LocalHostTest.res +71 -0
  136. package/tests/LocalHostTest.res.mjs +63 -0
  137. package/tests/MappingGwtTest.res +348 -0
  138. package/tests/MappingGwtTest.res.mjs +592 -0
  139. package/tests/OutboundTranslationGwtTest.res +59 -0
  140. package/tests/OutboundTranslationGwtTest.res.mjs +138 -0
  141. package/tests/PlatformRunnerTest.res +66 -0
  142. package/tests/PlatformRunnerTest.res.mjs +85 -0
  143. package/tests/PlatformScanTest.res +61 -0
  144. package/tests/PlatformScanTest.res.mjs +56 -0
  145. package/tests/QueryGwtTest.res +161 -0
  146. package/tests/QueryGwtTest.res.mjs +314 -0
  147. package/tests/QueryResolverGwtTest.res +71 -0
  148. package/tests/QueryResolverGwtTest.res.mjs +136 -0
  149. package/tests/RunnerUnitTest.res +219 -0
  150. package/tests/RunnerUnitTest.res.mjs +244 -0
  151. package/tests/SideEffect/FakeOrderNotification.res +23 -0
  152. package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
  153. package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
  154. package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
  155. package/tests/SideEffect/MockEmail.res +18 -0
  156. package/tests/SideEffect/MockEmail.res.mjs +47 -0
  157. package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
  158. package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
  159. package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
  160. package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
  161. package/tests/StateChange/WithFixtures_GWT.res +23 -0
  162. package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
  163. package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
  164. package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
  165. package/tests/StateChange/WithManualOpen_GWT.res +17 -0
  166. package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
  167. package/tests/StateChangeSliceGwtTest.res +160 -0
  168. package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
  169. package/tests/StateViewSliceGwtTest.res +64 -0
  170. package/tests/StateViewSliceGwtTest.res.mjs +161 -0
  171. package/tests/WatchDebounceTest.res +78 -0
  172. package/tests/WatchDebounceTest.res.mjs +97 -0
@@ -0,0 +1,487 @@
1
+ // Worked examples for Delegate_GWT — the ExtensionPoint / Extension boundary
2
+ // primitive. Mirrors the hybrid example's cross-plugin seam:
3
+ // * an ExtensionPoint mapping turns internal events into public EP events
4
+ // (including one-to-many fan-out), exercised via `FromExtensionPoint`;
5
+ // * an Extension delegate turns EP events into delegate commands, exercised
6
+ // via `FromExtension`.
7
+ // See `docs/plans/done/gwt-flow-and-extension-test-kinds.md` Phase 1.
8
+
9
+ S.enableJson()
10
+
11
+ module EPM = ReventlessInfra.ExtensionPointMapping
12
+ module EM = ReventlessInfra.ExtensionMapping
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Catalog Products extension point + its internal Product delegate.
16
+ // ---------------------------------------------------------------------------
17
+
18
+ module ProductsEpSpec = {
19
+ let name = "Catalog.Products"
20
+ let moduleUrl = ""
21
+
22
+ @schema
23
+ type command = NoCommand
24
+
25
+ @schema
26
+ type event =
27
+ | ProductBecameAvailable({productId: string, name: string, price: float})
28
+ | ProductPriceChanged({productId: string, price: float})
29
+
30
+ @schema
31
+ type directive = NoDirective
32
+ }
33
+
34
+ module ProductDelegate = {
35
+ module Id = Reventless.Id.StringPure
36
+ let name = "Product"
37
+
38
+ @schema
39
+ type command = NoCommand
40
+
41
+ @schema
42
+ type event =
43
+ | ProductAdded({productId: string, name: string, price: float})
44
+ | ProductPriceChanged({productId: string, price: float})
45
+
46
+ @schema
47
+ type error = NoError
48
+
49
+ let moduleUrl = ""
50
+ let commandAuthorization = (_: command): Reventless.Authorization.permission => AllowAuthenticated
51
+ }
52
+
53
+ module ProductsEpMapping = {
54
+ module ExtensionPoint = ProductsEpSpec
55
+ module Delegate = ProductDelegate
56
+
57
+ let moduleUrl = "test:DelegateGwtTest:ProductsEpMapping"
58
+
59
+ let mapIncomingCommand = (_id, _command, _meta) => []
60
+
61
+ let mapOutgoingEvent = Some(
62
+ (_id, event: ProductDelegate.event, _meta, _queryEngine) =>
63
+ switch event {
64
+ | ProductAdded({productId, name, price}) => [
65
+ EPM.PublishEvent(
66
+ productId,
67
+ ProductsEpSpec.ProductBecameAvailable({productId, name, price}),
68
+ ),
69
+ ]
70
+ | ProductPriceChanged({productId, price}) => [
71
+ EPM.PublishEvent(productId, ProductsEpSpec.ProductPriceChanged({productId, price})),
72
+ ]
73
+ },
74
+ )
75
+ }
76
+
77
+ module ProductsEpGwt = Delegate_GWT.FromExtensionPoint(ProductsEpMapping)
78
+
79
+ ProductsEpGwt.describe("Products ExtensionPoint mapping (FromExtensionPoint)", () => {
80
+ ProductsEpGwt.test("ProductAdded publishes ProductBecameAvailable", () =>
81
+ ProductsEpGwt.whenDelegateEvent(
82
+ ProductDelegate.ProductAdded({productId: "p1", name: "Book", price: 9.99}),
83
+ )->ProductsEpGwt.thenPublishesEvent(
84
+ "p1",
85
+ ProductsEpSpec.ProductBecameAvailable({productId: "p1", name: "Book", price: 9.99}),
86
+ )
87
+ )
88
+
89
+ ProductsEpGwt.test("ProductPriceChanged publishes ProductPriceChanged", () =>
90
+ ProductsEpGwt.whenDelegateEvent(
91
+ ProductDelegate.ProductPriceChanged({productId: "p1", price: 7.5}),
92
+ )->ProductsEpGwt.thenPublishesEvent(
93
+ "p1",
94
+ ProductsEpSpec.ProductPriceChanged({productId: "p1", price: 7.5}),
95
+ )
96
+ )
97
+ })
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Ordering Orders extension point — array-decomposed (one-to-many) fan-out.
101
+ // ---------------------------------------------------------------------------
102
+
103
+ module OrdersEpSpec = {
104
+ let name = "Ordering.Orders"
105
+ let moduleUrl = ""
106
+
107
+ @schema
108
+ type command = NoCommand
109
+
110
+ @schema
111
+ type event =
112
+ | ItemOrdered({productId: string, orderId: string, customerId: string})
113
+ | ItemOrderCancelled({productId: string, orderId: string})
114
+
115
+ @schema
116
+ type directive = NoDirective
117
+ }
118
+
119
+ module OrderDelegate = {
120
+ module Id = Reventless.Id.StringPure
121
+ let name = "Order"
122
+
123
+ @schema
124
+ type command = NoCommand
125
+
126
+ @schema
127
+ type event =
128
+ | OrderPlaced({orderId: string, customerId: string, productIds: array<string>})
129
+ | OrderCancelled({orderId: string, productIds: array<string>})
130
+
131
+ @schema
132
+ type error = NoError
133
+
134
+ let moduleUrl = ""
135
+ let commandAuthorization = (_: command): Reventless.Authorization.permission => AllowAuthenticated
136
+ }
137
+
138
+ module OrdersEpMapping = {
139
+ module ExtensionPoint = OrdersEpSpec
140
+ module Delegate = OrderDelegate
141
+
142
+ let moduleUrl = "test:DelegateGwtTest:OrdersEpMapping"
143
+
144
+ let mapIncomingCommand = (_id, _command, _meta) => []
145
+
146
+ let mapOutgoingEvent = Some(
147
+ (_id, event: OrderDelegate.event, _meta, _queryEngine) =>
148
+ switch event {
149
+ | OrderPlaced({orderId, customerId, productIds}) =>
150
+ productIds->Array.map(pid => EPM.PublishEvent(
151
+ pid,
152
+ OrdersEpSpec.ItemOrdered({productId: pid, orderId, customerId}),
153
+ ))
154
+ | OrderCancelled({orderId, productIds}) =>
155
+ productIds->Array.map(pid => EPM.PublishEvent(
156
+ pid,
157
+ OrdersEpSpec.ItemOrderCancelled({productId: pid, orderId}),
158
+ ))
159
+ },
160
+ )
161
+ }
162
+
163
+ module OrdersEpGwt = Delegate_GWT.FromExtensionPoint(OrdersEpMapping)
164
+
165
+ OrdersEpGwt.describe("Orders ExtensionPoint mapping — fan-out", () => {
166
+ OrdersEpGwt.test("OrderPlaced fans out to one ItemOrdered per product", () =>
167
+ OrdersEpGwt.whenDelegateEvent(
168
+ OrderDelegate.OrderPlaced({orderId: "o1", customerId: "c1", productIds: ["p1", "p2"]}),
169
+ )->OrdersEpGwt.thenPublishesEvents([
170
+ ("p1", OrdersEpSpec.ItemOrdered({productId: "p1", orderId: "o1", customerId: "c1"})),
171
+ ("p2", OrdersEpSpec.ItemOrdered({productId: "p2", orderId: "o1", customerId: "c1"})),
172
+ ])
173
+ )
174
+
175
+ OrdersEpGwt.test("OrderPlaced with no products publishes nothing", () =>
176
+ OrdersEpGwt.whenDelegateEvent(
177
+ OrderDelegate.OrderPlaced({orderId: "o2", customerId: "c1", productIds: []}),
178
+ )->OrdersEpGwt.thenPublishesNothing
179
+ )
180
+ })
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Ordering Products extension — delegates EP events to SyncCatalogProduct.
184
+ // ---------------------------------------------------------------------------
185
+
186
+ module SyncDelegate = {
187
+ module Id = Reventless.Id.StringPure
188
+ let name = "SyncCatalogProduct"
189
+
190
+ @schema
191
+ type command =
192
+ | SyncNewProduct({productId: string, name: string, price: float})
193
+ | ChangeSyncedPrice({productId: string, price: float})
194
+
195
+ @schema
196
+ type event = NoEvent
197
+
198
+ @schema
199
+ type error = NoError
200
+
201
+ let moduleUrl = ""
202
+ let commandAuthorization = (_: command): Reventless.Authorization.permission => AllowAuthenticated
203
+ }
204
+
205
+ module ProductsExtMapping = {
206
+ module ExtensionPoint = ProductsEpSpec
207
+ module Delegate = SyncDelegate
208
+
209
+ let moduleUrl = ""
210
+ let delegateModuleUrl = ""
211
+
212
+ let mapIncomingEvent = (_id, event: ProductsEpSpec.event, _meta, _pluginDef, _queryEngine) =>
213
+ switch event {
214
+ | ProductBecameAvailable({productId, name, price}) => [
215
+ EM.PublishStateChangeSliceCommand(SyncDelegate.SyncNewProduct({productId, name, price})),
216
+ ]
217
+ | ProductPriceChanged({productId, price}) => [
218
+ EM.PublishStateChangeSliceCommand(SyncDelegate.ChangeSyncedPrice({productId, price})),
219
+ ]
220
+ }
221
+
222
+ let mapOutgoingEvent = None
223
+ }
224
+
225
+ module ProductsExtGwt = Delegate_GWT.FromExtension(ProductsExtMapping)
226
+
227
+ ProductsExtGwt.describe("Products Extension delegate (FromExtension)", () => {
228
+ ProductsExtGwt.test("ProductBecameAvailable issues SyncNewProduct", () =>
229
+ ProductsExtGwt.whenIncomingEvent(
230
+ ProductsEpSpec.ProductBecameAvailable({productId: "p1", name: "Book", price: 9.99}),
231
+ )->ProductsExtGwt.thenPublishesCommand(
232
+ SyncDelegate.SyncNewProduct({productId: "p1", name: "Book", price: 9.99}),
233
+ )
234
+ )
235
+
236
+ ProductsExtGwt.test("ProductPriceChanged issues ChangeSyncedPrice", () =>
237
+ ProductsExtGwt.whenIncomingEvent(
238
+ ProductsEpSpec.ProductPriceChanged({productId: "p1", price: 7.5}),
239
+ )->ProductsExtGwt.thenPublishesCommand(
240
+ SyncDelegate.ChangeSyncedPrice({productId: "p1", price: 7.5}),
241
+ )
242
+ )
243
+ })
244
+
245
+ // ---------------------------------------------------------------------------
246
+ // EP `mapIncomingCommand` — an incoming protocol command routed to the wrapped
247
+ // aggregate (the other EP direction: `whenIncomingCommand`).
248
+ // ---------------------------------------------------------------------------
249
+
250
+ module InventoryEpSpec = {
251
+ let name = "Catalog.Inventory"
252
+ let moduleUrl = ""
253
+
254
+ @schema
255
+ type command = Restock({sku: string, qty: int})
256
+
257
+ @schema
258
+ type event = NoEvent
259
+
260
+ @schema
261
+ type directive = NoDirective
262
+ }
263
+
264
+ module StockDelegate = {
265
+ module Id = Reventless.Id.StringPure
266
+ let name = "Stock"
267
+
268
+ @schema
269
+ type command = Restock({sku: string, qty: int})
270
+
271
+ @schema
272
+ type event = NoEvent
273
+
274
+ @schema
275
+ type error = NoError
276
+
277
+ let moduleUrl = ""
278
+ let commandAuthorization = (_: command): Reventless.Authorization.permission => AllowAuthenticated
279
+ }
280
+
281
+ module InventoryEpMapping = {
282
+ module ExtensionPoint = InventoryEpSpec
283
+ module Delegate = StockDelegate
284
+
285
+ let moduleUrl = "test:DelegateGwtTest:InventoryEpMapping"
286
+
287
+ let mapIncomingCommand = (id, command: InventoryEpSpec.command, _meta) =>
288
+ switch command {
289
+ | Restock({sku, qty}) => [EPM.PublishCommand(id, StockDelegate.Restock({sku, qty}))]
290
+ }
291
+
292
+ let mapOutgoingEvent = None
293
+ }
294
+
295
+ module InventoryEpGwt = Delegate_GWT.FromExtensionPoint(InventoryEpMapping)
296
+
297
+ InventoryEpGwt.describe("Inventory ExtensionPoint mapping — incoming command", () => {
298
+ InventoryEpGwt.test("Restock protocol command routes to the Stock delegate", () =>
299
+ InventoryEpGwt.whenIncomingCommand(
300
+ InventoryEpSpec.Restock({sku: "s1", qty: 5}),
301
+ )->InventoryEpGwt.thenPublishesCommand("gwt-id", StockDelegate.Restock({sku: "s1", qty: 5}))
302
+ )
303
+ })
304
+
305
+ // ---------------------------------------------------------------------------
306
+ // Extension `mapOutgoingEvent` — an internal delegate event published back as a
307
+ // protocol command (the other Extension direction: `whenDelegateEvent`).
308
+ // ---------------------------------------------------------------------------
309
+
310
+ module FeedbackEpSpec = {
311
+ let name = "Ordering.Feedback"
312
+ let moduleUrl = ""
313
+
314
+ @schema
315
+ type command = AckProduct({productId: string})
316
+
317
+ @schema
318
+ type event = NoEvent
319
+
320
+ @schema
321
+ type directive = NoDirective
322
+ }
323
+
324
+ module SyncedDelegate = {
325
+ module Id = Reventless.Id.StringPure
326
+ let name = "SyncedProduct"
327
+
328
+ @schema
329
+ type command = NoCommand
330
+
331
+ @schema
332
+ type event = ProductSynced({productId: string})
333
+
334
+ @schema
335
+ type error = NoError
336
+
337
+ let moduleUrl = ""
338
+ let commandAuthorization = (_: command): Reventless.Authorization.permission => AllowAuthenticated
339
+ }
340
+
341
+ module FeedbackExtMapping = {
342
+ module ExtensionPoint = FeedbackEpSpec
343
+ module Delegate = SyncedDelegate
344
+
345
+ let moduleUrl = ""
346
+ let delegateModuleUrl = ""
347
+
348
+ let mapIncomingEvent = (_id, event: FeedbackEpSpec.event, _meta, _pluginDef, _queryEngine) =>
349
+ switch event {
350
+ | NoEvent => []
351
+ }
352
+
353
+ let mapOutgoingEvent = Some(
354
+ (id, event: SyncedDelegate.event, _meta, _pluginDef) =>
355
+ switch event {
356
+ | ProductSynced({productId}) => [
357
+ EM.PublishExtensionPointCommand(id, FeedbackEpSpec.AckProduct({productId: productId})),
358
+ ]
359
+ },
360
+ )
361
+ }
362
+
363
+ module FeedbackExtGwt = Delegate_GWT.FromExtension(FeedbackExtMapping)
364
+
365
+ FeedbackExtGwt.describe("Feedback Extension delegate — outgoing command", () => {
366
+ FeedbackExtGwt.test("ProductSynced publishes the AckProduct protocol command", () =>
367
+ FeedbackExtGwt.whenDelegateEvent(
368
+ SyncedDelegate.ProductSynced({productId: "p1"}),
369
+ )->FeedbackExtGwt.thenPublishesExtensionPointCommand(
370
+ "gwt-id",
371
+ FeedbackEpSpec.AckProduct({productId: "p1"}),
372
+ )
373
+ )
374
+ })
375
+
376
+ // ---------------------------------------------------------------------------
377
+ // Directives — a `HandleDirective` action carries a typed directive on its own
378
+ // channel: asserted via `thenHandlesDirective`, disjoint from published events
379
+ // (an arm can do both), and `thenHandlesNoDirective` confirms an arm raises none.
380
+ // ---------------------------------------------------------------------------
381
+
382
+ module NotifyEpSpec = {
383
+ let name = "Catalog.Notify"
384
+ let moduleUrl = ""
385
+
386
+ @schema
387
+ type command = NoCommand
388
+
389
+ @schema
390
+ type event = Pinged({productId: string})
391
+
392
+ @schema
393
+ type directive =
394
+ | NotifyOps({channel: string})
395
+ | FlushCache
396
+ }
397
+
398
+ module NotifyEpMapping = {
399
+ module ExtensionPoint = NotifyEpSpec
400
+ module Delegate = ProductDelegate
401
+
402
+ let moduleUrl = "test:DelegateGwtTest:NotifyEpMapping"
403
+
404
+ let handleDirective: EPM.directiveHandler<NotifyEpSpec.directive> = (_c, _d, _q, _dir) =>
405
+ Promise.resolve()
406
+
407
+ let mapIncomingCommand = (_id, _command, _meta) => []
408
+
409
+ // ProductAdded both PUBLISHES an event and HANDLES a directive; ProductPriceChanged
410
+ // publishes only — so `thenHandlesNoDirective` holds for it.
411
+ let mapOutgoingEvent = Some(
412
+ (_id, event: ProductDelegate.event, _meta, _queryEngine) =>
413
+ switch event {
414
+ | ProductAdded({productId}) => [
415
+ EPM.PublishEvent(productId, NotifyEpSpec.Pinged({productId: productId})),
416
+ EPM.HandleDirective(handleDirective, NotifyEpSpec.NotifyOps({channel: "ops"})),
417
+ ]
418
+ | ProductPriceChanged({productId}) => [
419
+ EPM.PublishEvent(productId, NotifyEpSpec.Pinged({productId: productId})),
420
+ ]
421
+ },
422
+ )
423
+ }
424
+
425
+ module NotifyEpGwt = Delegate_GWT.FromExtensionPoint(NotifyEpMapping)
426
+
427
+ NotifyEpGwt.describe("Notify ExtensionPoint mapping — directives", () => {
428
+ NotifyEpGwt.test("ProductAdded handles the NotifyOps directive", () =>
429
+ NotifyEpGwt.whenDelegateEvent(
430
+ ProductDelegate.ProductAdded({productId: "p1", name: "Book", price: 9.99}),
431
+ )->NotifyEpGwt.thenHandlesDirective(NotifyEpSpec.NotifyOps({channel: "ops"}))
432
+ )
433
+
434
+ // The directive does NOT leak into the published-event channel.
435
+ NotifyEpGwt.test("ProductAdded still publishes only the Pinged event", () =>
436
+ NotifyEpGwt.whenDelegateEvent(
437
+ ProductDelegate.ProductAdded({productId: "p1", name: "Book", price: 9.99}),
438
+ )->NotifyEpGwt.thenPublishesEvent("p1", NotifyEpSpec.Pinged({productId: "p1"}))
439
+ )
440
+
441
+ NotifyEpGwt.test("ProductPriceChanged raises no directive", () =>
442
+ NotifyEpGwt.whenDelegateEvent(
443
+ ProductDelegate.ProductPriceChanged({productId: "p1", price: 7.5}),
444
+ )->NotifyEpGwt.thenHandlesNoDirective
445
+ )
446
+ })
447
+
448
+ module NotifyExtMapping = {
449
+ module ExtensionPoint = NotifyEpSpec
450
+ module Delegate = SyncDelegate
451
+
452
+ let moduleUrl = ""
453
+ let delegateModuleUrl = ""
454
+
455
+ // An Extension directive handler is a bare `Reventless.Handler.handler` (no
456
+ // Schedule/QueryEngine args — extensions are user code).
457
+ let handleDirective: Reventless.Handler.handler<NotifyEpSpec.directive> = _dir =>
458
+ Promise.resolve()
459
+
460
+ let mapIncomingEvent = (_id, event: NotifyEpSpec.event, _meta, _pluginDef, _queryEngine) =>
461
+ switch event {
462
+ | Pinged({productId}) => [
463
+ EM.PublishStateChangeSliceCommand(SyncDelegate.ChangeSyncedPrice({productId, price: 0.0})),
464
+ EM.HandleDirective(handleDirective, NotifyEpSpec.FlushCache),
465
+ ]
466
+ }
467
+
468
+ let mapOutgoingEvent = None
469
+ }
470
+
471
+ module NotifyExtGwt = Delegate_GWT.FromExtension(NotifyExtMapping)
472
+
473
+ NotifyExtGwt.describe("Notify Extension delegate — directives", () => {
474
+ NotifyExtGwt.test("Pinged handles the FlushCache directive", () =>
475
+ NotifyExtGwt.whenIncomingEvent(
476
+ NotifyEpSpec.Pinged({productId: "p1"}),
477
+ )->NotifyExtGwt.thenHandlesDirective(NotifyEpSpec.FlushCache)
478
+ )
479
+
480
+ NotifyExtGwt.test("Pinged still publishes only the ChangeSyncedPrice command", () =>
481
+ NotifyExtGwt.whenIncomingEvent(
482
+ NotifyEpSpec.Pinged({productId: "p1"}),
483
+ )->NotifyExtGwt.thenPublishesCommand(
484
+ SyncDelegate.ChangeSyncedPrice({productId: "p1", price: 0.0}),
485
+ )
486
+ )
487
+ })