@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,322 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
7
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
8
+ import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
9
+ import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
10
+ import * as JestBind$ReventlessGwt from "./JestBind.res.mjs";
11
+ import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
12
+
13
+ function FromBehavior(Spec) {
14
+ return Behavior => ({
15
+ name: Spec.name,
16
+ Id: Spec.Id,
17
+ initialState: Behavior.initialState,
18
+ consumedEventSchema: Spec.eventSchema,
19
+ evolve: Behavior.evolve,
20
+ commandSchema: Spec.commandSchema,
21
+ eventSchema: Spec.eventSchema,
22
+ errorSchema: Spec.errorSchema,
23
+ decide: Behavior.decide
24
+ });
25
+ }
26
+
27
+ function FromStateChangeSlice(Spec) {
28
+ return {
29
+ name: Spec.name,
30
+ Id: undefined,
31
+ initialState: Spec.initialState,
32
+ consumedEventSchema: Spec.consumedEventSchema,
33
+ evolve: Spec.evolve,
34
+ commandSchema: Spec.commandSchema,
35
+ eventSchema: Spec.eventSchema,
36
+ errorSchema: Spec.errorSchema,
37
+ decide: Spec.decide
38
+ };
39
+ }
40
+
41
+ function Make(M) {
42
+ let Source = M.Source;
43
+ let Target = M.Target;
44
+ S.enableJson();
45
+ let sliceName = Source.name + `→` + Target.name;
46
+ let test = (name, timeout, body) => JestBind$ReventlessGwt.testPromise(sliceName, name, timeout, body);
47
+ let queryEngine_scan = (param, param$1, param$2) => Promise.resolve([]);
48
+ let queryEngine_query = (param, param$1, param$2, param$3, param$4, param$5, param$6) => Promise.resolve([]);
49
+ let queryEngine = {
50
+ scan: queryEngine_scan,
51
+ query: queryEngine_query
52
+ };
53
+ let sourceErrors = {
54
+ contents: []
55
+ };
56
+ let targetErrors = {
57
+ contents: []
58
+ };
59
+ let execSource = (history, cmd) => {
60
+ sourceErrors.contents = [];
61
+ let state = Stdlib_Array.reduce(history, Source.initialState, Source.evolve);
62
+ let events = Source.decide(state, cmd);
63
+ if (events.TAG === "Ok") {
64
+ return events._0;
65
+ }
66
+ sourceErrors.contents = [events._0];
67
+ return [];
68
+ };
69
+ let execTarget = (history, cmd) => {
70
+ let state = Stdlib_Array.reduce(history, Target.initialState, Target.evolve);
71
+ let events = Target.decide(state, cmd);
72
+ if (events.TAG === "Ok") {
73
+ return events._0;
74
+ }
75
+ targetErrors.contents = [events._0];
76
+ return [];
77
+ };
78
+ let givenSourceEvents = sourceHistory => [
79
+ sourceHistory,
80
+ []
81
+ ];
82
+ let andTargetEvents = (param, targetHistory) => [
83
+ param[0],
84
+ targetHistory
85
+ ];
86
+ let whenSourceCmd = async (param, sourceId, cmd) => {
87
+ targetErrors.contents = [];
88
+ let sourceEvents = execSource(param[0], cmd);
89
+ let targetActions = sourceEvents.map(sourceEvent => M.map(Source.Id.makeFromString(sourceId), sourceEvent, queryEngine)).flat();
90
+ let targetHistories = Object.fromEntries(param[1]);
91
+ let commands = (await Promise.all(targetActions.map(async action => {
92
+ switch (action.TAG) {
93
+ case "Publish" :
94
+ case "PublishDelayed" :
95
+ return [[
96
+ action._0,
97
+ action._1
98
+ ]];
99
+ case "PublishAsync" :
100
+ return await action._0;
101
+ default:
102
+ return [];
103
+ }
104
+ }))).flat();
105
+ return Stdlib_Array.reduce(commands, {}, (targetEvents, param) => {
106
+ let idStr = Target.Id.toString(param[0]);
107
+ let hist = Stdlib_Option.getOr(targetHistories[idStr], []);
108
+ let newEvents = execTarget(hist, param[1]);
109
+ targetEvents[idStr] = Stdlib_Option.getOr(targetEvents[idStr], []).concat(newEvents);
110
+ return targetEvents;
111
+ });
112
+ };
113
+ let encTargetEvent = e => Message$ReventlessCore.encode(e, Target.eventSchema);
114
+ let encDict = d => Object.entries(d).toSorted((param, param$1) => Primitive_string.compare(param[0], param$1[0])).map(param => {
115
+ let obj = {};
116
+ obj["id"] = param[0];
117
+ obj["events"] = param[1].map(encTargetEvent);
118
+ return obj;
119
+ });
120
+ let encExpected = pairs => pairs.toSorted((param, param$1) => Primitive_string.compare(param[0], param$1[0])).map(param => {
121
+ let obj = {};
122
+ obj["id"] = param[0];
123
+ obj["events"] = param[1].map(encTargetEvent);
124
+ return obj;
125
+ });
126
+ let unexpectedSideError = actualDict => {
127
+ let actualEvents = encDict(actualDict);
128
+ let srcErr = sourceErrors.contents[0];
129
+ if (srcErr !== undefined) {
130
+ return Outcome$ReventlessGwt.fail({
131
+ TAG: "ErrorMismatch",
132
+ expected: null,
133
+ actual: Message$ReventlessCore.encode(Primitive_option.valFromOption(srcErr), Source.errorSchema),
134
+ actualEvents: actualEvents
135
+ });
136
+ }
137
+ let tgtErr = targetErrors.contents[0];
138
+ if (tgtErr !== undefined) {
139
+ return Outcome$ReventlessGwt.fail({
140
+ TAG: "ErrorMismatch",
141
+ expected: null,
142
+ actual: Message$ReventlessCore.encode(Primitive_option.valFromOption(tgtErr), Target.errorSchema),
143
+ actualEvents: actualEvents
144
+ });
145
+ } else {
146
+ return Outcome$ReventlessGwt.pass;
147
+ }
148
+ };
149
+ let dictPairsEq = (a, b) => {
150
+ let ka = Object.keys(a).toSorted(Primitive_string.compare);
151
+ let kb = Object.keys(b).toSorted(Primitive_string.compare);
152
+ if (Primitive_object.equal(ka, kb)) {
153
+ return ka.every(k => {
154
+ let av = Stdlib_Option.getOr(a[k], []);
155
+ let bv = Stdlib_Option.getOr(b[k], []);
156
+ return Primitive_object.equal(av, bv);
157
+ });
158
+ } else {
159
+ return false;
160
+ }
161
+ };
162
+ let thenTargetEvents = async (targetEvents, expectedTargetEvents) => {
163
+ let actualDict = await targetEvents;
164
+ let out = unexpectedSideError(actualDict);
165
+ if (out.TAG !== "Ok") {
166
+ return out;
167
+ }
168
+ let expectedDict = Object.fromEntries(expectedTargetEvents);
169
+ if (dictPairsEq(actualDict, expectedDict)) {
170
+ return Outcome$ReventlessGwt.pass;
171
+ } else {
172
+ return Outcome$ReventlessGwt.fail({
173
+ TAG: "EventsMismatch",
174
+ expected: encExpected(expectedTargetEvents),
175
+ actual: encDict(actualDict)
176
+ });
177
+ }
178
+ };
179
+ let thenTargetEvent = async (targetEvents, id, expectedTargetEvent) => {
180
+ let actualDict = await targetEvents;
181
+ let out = unexpectedSideError(actualDict);
182
+ if (out.TAG !== "Ok") {
183
+ return out;
184
+ }
185
+ let pairs = Object.entries(actualDict);
186
+ let ok = false;
187
+ if (pairs.length === 1) {
188
+ let match = pairs[0];
189
+ let tmp;
190
+ if (match !== undefined) {
191
+ let match$1 = match[1];
192
+ if (match$1.length !== 1) {
193
+ tmp = false;
194
+ } else {
195
+ let actualEvent = match$1[0];
196
+ tmp = match[0] === id && Primitive_object.equal(actualEvent, expectedTargetEvent);
197
+ }
198
+ } else {
199
+ tmp = false;
200
+ }
201
+ ok = tmp;
202
+ }
203
+ if (ok) {
204
+ return Outcome$ReventlessGwt.pass;
205
+ } else {
206
+ return Outcome$ReventlessGwt.fail({
207
+ TAG: "EventsMismatch",
208
+ expected: encExpected([[
209
+ id,
210
+ [expectedTargetEvent]
211
+ ]]),
212
+ actual: encDict(actualDict)
213
+ });
214
+ }
215
+ };
216
+ let thenNoTargetEvent = targetEvents => thenTargetEvents(targetEvents, []);
217
+ let thenSourceError = async (targetEvents, expectedError) => {
218
+ let actualDict = await targetEvents;
219
+ let actualEvents = encDict(actualDict);
220
+ let expectedJson = Message$ReventlessCore.encode(expectedError, Source.errorSchema);
221
+ let actual = sourceErrors.contents[0];
222
+ if (actual === undefined) {
223
+ return Outcome$ReventlessGwt.fail({
224
+ TAG: "ErrorMismatch",
225
+ expected: expectedJson,
226
+ actual: undefined,
227
+ actualEvents: actualEvents
228
+ });
229
+ }
230
+ let actual$1 = Primitive_option.valFromOption(actual);
231
+ if (Primitive_object.notequal(actual$1, expectedError)) {
232
+ return Outcome$ReventlessGwt.fail({
233
+ TAG: "ErrorMismatch",
234
+ expected: expectedJson,
235
+ actual: Message$ReventlessCore.encode(actual$1, Source.errorSchema),
236
+ actualEvents: actualEvents
237
+ });
238
+ } else {
239
+ return Outcome$ReventlessGwt.pass;
240
+ }
241
+ };
242
+ let thenTargetError = async (targetEvents, expectedError) => {
243
+ let actualDict = await targetEvents;
244
+ let actualEvents = encDict(actualDict);
245
+ let expectedJson = Message$ReventlessCore.encode(expectedError, Target.errorSchema);
246
+ let actual = targetErrors.contents[0];
247
+ if (actual === undefined) {
248
+ return Outcome$ReventlessGwt.fail({
249
+ TAG: "ErrorMismatch",
250
+ expected: expectedJson,
251
+ actual: undefined,
252
+ actualEvents: actualEvents
253
+ });
254
+ }
255
+ let actual$1 = Primitive_option.valFromOption(actual);
256
+ if (Primitive_object.notequal(actual$1, expectedError)) {
257
+ return Outcome$ReventlessGwt.fail({
258
+ TAG: "ErrorMismatch",
259
+ expected: expectedJson,
260
+ actual: Message$ReventlessCore.encode(actual$1, Target.errorSchema),
261
+ actualEvents: actualEvents
262
+ });
263
+ } else {
264
+ return Outcome$ReventlessGwt.pass;
265
+ }
266
+ };
267
+ let thenTargetEventsWithError = async (targetEvents, expectedTargetEvents, expectedError) => {
268
+ let actualDict = await targetEvents;
269
+ let actualEvents = encDict(actualDict);
270
+ let expectedErrorJson = Message$ReventlessCore.encode(expectedError, Target.errorSchema);
271
+ let actual = targetErrors.contents[0];
272
+ if (actual === undefined) {
273
+ return Outcome$ReventlessGwt.fail({
274
+ TAG: "ErrorMismatch",
275
+ expected: expectedErrorJson,
276
+ actual: undefined,
277
+ actualEvents: actualEvents
278
+ });
279
+ }
280
+ let actual$1 = Primitive_option.valFromOption(actual);
281
+ if (Primitive_object.notequal(actual$1, expectedError)) {
282
+ return Outcome$ReventlessGwt.fail({
283
+ TAG: "ErrorMismatch",
284
+ expected: expectedErrorJson,
285
+ actual: Message$ReventlessCore.encode(actual$1, Target.errorSchema),
286
+ actualEvents: actualEvents
287
+ });
288
+ }
289
+ let expectedDict = Object.fromEntries(expectedTargetEvents);
290
+ if (dictPairsEq(actualDict, expectedDict)) {
291
+ return Outcome$ReventlessGwt.pass;
292
+ } else {
293
+ return Outcome$ReventlessGwt.fail({
294
+ TAG: "EventsMismatch",
295
+ expected: encExpected(expectedTargetEvents),
296
+ actual: actualEvents
297
+ });
298
+ }
299
+ };
300
+ return {
301
+ Source: Source,
302
+ Target: Target,
303
+ describe: JestBind$ReventlessGwt.describe,
304
+ test: test,
305
+ givenSourceEvents: givenSourceEvents,
306
+ andTargetEvents: andTargetEvents,
307
+ whenSourceCmd: whenSourceCmd,
308
+ thenTargetEvents: thenTargetEvents,
309
+ thenTargetEvent: thenTargetEvent,
310
+ thenNoTargetEvent: thenNoTargetEvent,
311
+ thenSourceError: thenSourceError,
312
+ thenTargetError: thenTargetError,
313
+ thenTargetEventsWithError: thenTargetEventsWithError
314
+ };
315
+ }
316
+
317
+ export {
318
+ FromBehavior,
319
+ FromStateChangeSlice,
320
+ Make,
321
+ }
322
+ /* S Not a pure module */
@@ -0,0 +1,316 @@
1
+ open ReventlessCore
2
+
3
+ module type T = {
4
+ type sourceEvent
5
+ type targetState
6
+
7
+ let describe: (string, unit => unit) => unit
8
+ let describeWithId: (string, string, unit => unit) => unit
9
+ let test: (string, ~timeout: int=?, unit => promise<Outcome.outcome>) => unit
10
+
11
+ type store = dict<array<targetState>>
12
+ type storeThunk = unit => promise<store>
13
+
14
+ let givenEvents: array<sourceEvent> => promise<store>
15
+ let givenEventsWithTime: array<(string, sourceEvent)> => promise<store>
16
+ let whenEvent: (promise<store>, sourceEvent) => storeThunk
17
+ let whenEvents: (promise<store>, array<sourceEvent>) => storeThunk
18
+ let whenEventWithTime: (promise<store>, string, sourceEvent) => storeThunk
19
+ let whenEventsWithTime: (promise<store>, array<(string, sourceEvent)>) => storeThunk
20
+ let thenStates: (storeThunk, array<targetState>) => promise<Outcome.outcome>
21
+ let thenStatesWithId: (storeThunk, string, array<targetState>) => promise<Outcome.outcome>
22
+ let thenAllStates: (storeThunk, store) => promise<Outcome.outcome>
23
+ let thenState: (storeThunk, targetState) => promise<Outcome.outcome>
24
+ let thenStateWithId: (storeThunk, string, targetState) => promise<Outcome.outcome>
25
+ let thenNoState: storeThunk => promise<Outcome.outcome>
26
+ let thenThrow: storeThunk => promise<Outcome.outcome>
27
+ let thenFail: storeThunk => promise<Outcome.outcome>
28
+ }
29
+
30
+ let handleActions = Projection.handleActions // create alias to avoid shadowing of same named modules
31
+
32
+ module Make = (Projection: Reventless.Projection.Mapping): (
33
+ T with type sourceEvent := Projection.sourceEvent and type targetState := Projection.targetState
34
+ ) => {
35
+ S.enableJson()
36
+
37
+ let testId = ref(TestFixtures.id)
38
+ let meta = ref(TestFixtures.meta)
39
+
40
+ let describe = JestBind.describe
41
+ let describeWithId = (description, id, fn) => {
42
+ testId := id
43
+ JestBind.describe(description, fn)
44
+ }
45
+ let test = (name, ~timeout=?, body) =>
46
+ JestBind.testPromise(~slice=Projection.sourceName, name, ~timeout?, body)
47
+
48
+ type store = dict<array<Projection.targetState>>
49
+ type storeThunk = unit => promise<store>
50
+
51
+ let getSubId = state => Projection.subIdConfig->Option.map(({getSubId}) => state->getSubId)
52
+ let hasSubId = (state, subId) => state->getSubId->Option.getOrThrow == subId
53
+ let states = (store, id) => store->Dict.get(id)->Option.getOr([])
54
+ let setStates = (store, id, states) => store->Dict.set(id, states)
55
+ let updateState = (store, id, subId, newState) =>
56
+ store->states(id)->Array.map(state => state->hasSubId(subId) ? newState : state)
57
+ let addState = (store, id, newState) => {
58
+ let (updatedStates, newStates) = switch newState->getSubId {
59
+ | Some(subId) =>
60
+ store->states(id)->Array.some(state => state->hasSubId(subId))
61
+ ? (store->updateState(id, subId, newState), [])
62
+ : (store->states(id), [newState])
63
+ | None => (store->states(id), [newState])
64
+ }
65
+ store->Dict.set(id, Array.concat(updatedStates, newStates))
66
+ }
67
+ let deleteStates = (store, id) => store->Dict.set(id, [])
68
+ let deleteSubState = (store, id, subId, getSubId) =>
69
+ store->Dict.set(
70
+ id,
71
+ store
72
+ ->Dict.get(id)
73
+ ->Option.map(states => states->Array.filter(state => state->getSubId != subId))
74
+ ->Option.getOr([]),
75
+ )
76
+
77
+ let load = (store, id) => store->states(id)->Ok->Promise.resolve
78
+ let save = (store, id, state, saveMode: QueryDb.saveMode, _ttl) =>
79
+ switch (store->states(id), saveMode) {
80
+ | (_, Any)
81
+ | ([], Init)
82
+ | ([_], Overwrite) =>
83
+ store->setStates(id, [state])
84
+ Ok()->Promise.resolve
85
+ | _ => Error(ReventlessInfra.QueryDb.StaleState)->Promise.resolve
86
+ }
87
+ let saveBatch = (store, batch) => {
88
+ batch->Array.forEach(((id, state, _ttl)) => store->addState(id, state))
89
+ Ok()->Promise.resolve
90
+ }
91
+ let delete = (store, id, subId) =>
92
+ switch (subId, Projection.subIdConfig) {
93
+ | (None, _) =>
94
+ store->deleteStates(id)
95
+ Ok()->Promise.resolve
96
+ | (Some((_, subId)), Some({Reventless.ReadModel.getSubId: getSubId})) =>
97
+ store->deleteSubState(id, subId, getSubId)
98
+ Ok()->Promise.resolve
99
+ | _ => Ok()->Promise.resolve
100
+ }
101
+ let deleteBatch = (store, ids) => {
102
+ ids->Array.forEach(((id, subId)) =>
103
+ switch (subId, Projection.subIdConfig) {
104
+ | (None, _) => store->deleteStates(id)
105
+ | (Some((_, subId)), Some({Reventless.ReadModel.getSubId: getSubId})) =>
106
+ store->deleteSubState(id, subId, getSubId)
107
+ | _ => ()
108
+ }
109
+ )
110
+ Ok()->Promise.resolve
111
+ }
112
+
113
+ let handleActions = (actions, operations) =>
114
+ actions->handleActions(operations, Projection.subIdConfig)
115
+
116
+ let sortStore = store =>
117
+ switch Projection.subIdConfig {
118
+ | None => store
119
+ | Some(_) =>
120
+ Dict.mapValues(store, states =>
121
+ states->Array.toSorted((state1, state2) =>
122
+ String.compare(state1->getSubId->Option.getUnsafe, state2->getSubId->Option.getUnsafe)
123
+ )
124
+ )
125
+ }
126
+
127
+ let update = async (store, events') => {
128
+ await events'
129
+ ->Array.map(event' => event'->Projection.project)
130
+ ->handleActions({
131
+ load: load(store, ...),
132
+ loadStream: id => store->states(id)->Stream.fromIterable,
133
+ save: save(store, ...),
134
+ saveBatch: saveBatch(store, ...),
135
+ count: async (_, _, _) => Ok(0),
136
+ delete: delete(store, ...),
137
+ deleteBatch: deleteBatch(store, ...),
138
+ })
139
+
140
+ store->sortStore
141
+ }
142
+
143
+ let givenEvents = events =>
144
+ Dict.make()->update(
145
+ events->Array.map(event => {Message.id: testId.contents, meta: meta.contents, event}),
146
+ )
147
+
148
+ let givenEventsWithTime = events =>
149
+ Dict.make()->update(
150
+ events->Array.map(((time, event)) => {
151
+ Message.id: testId.contents,
152
+ meta: {...meta.contents, time},
153
+ event,
154
+ }),
155
+ )
156
+
157
+ let whenEvent = (store, event): storeThunk =>
158
+ () =>
159
+ (
160
+ async () =>
161
+ await (await store)->update([{Message.id: testId.contents, meta: meta.contents, event}])
162
+ )()
163
+ let whenEvents = (store, events): storeThunk =>
164
+ () =>
165
+ (
166
+ async () =>
167
+ await (await store)->update(
168
+ events->Array.map(event => {Message.id: testId.contents, meta: meta.contents, event}),
169
+ )
170
+ )()
171
+ let whenEventWithTime = (store, time, event): storeThunk =>
172
+ () =>
173
+ (
174
+ async () =>
175
+ await (await store)->update([
176
+ {Message.id: testId.contents, meta: {...meta.contents, time}, event},
177
+ ])
178
+ )()
179
+ let whenEventsWithTime = (store, events): storeThunk =>
180
+ () =>
181
+ (
182
+ async () =>
183
+ await (await store)->update(
184
+ events->Array.map(((time, event)) => {
185
+ Message.id: testId.contents,
186
+ meta: {...meta.contents, time},
187
+ event,
188
+ }),
189
+ )
190
+ )()
191
+
192
+ let encState = (s: Projection.targetState) => s->Message.encode(Projection.targetStateSchema)
193
+ let encStates = (arr: array<Projection.targetState>) => arr->Array.map(encState)
194
+
195
+ // State equality via encoded JSON — handles ReScript structural comparison
196
+ // nuances when states contain types whose `==` is not meaningful.
197
+ let stateEq = (a: Projection.targetState, b: Projection.targetState) =>
198
+ JSON.stringify(encState(a)) == JSON.stringify(encState(b))
199
+ let statesEq = (a, b) =>
200
+ Array.length(a) == Array.length(b) &&
201
+ Array.zip(a, b)->Array.every(((x, y)) => stateEq(x, y))
202
+ let storeEq = (a: store, b: store) => {
203
+ let ka = a->Dict.keysToArray->Array.toSorted(String.compare)
204
+ let kb = b->Dict.keysToArray->Array.toSorted(String.compare)
205
+ ka == kb &&
206
+ ka->Array.every(key =>
207
+ statesEq(a->Dict.get(key)->Option.getOr([]), b->Dict.get(key)->Option.getOr([]))
208
+ )
209
+ }
210
+
211
+ let encStore = (s: store) => {
212
+ let obj = Dict.make()
213
+ s
214
+ ->Dict.toArray
215
+ ->Array.forEach(((k, v)) => obj->Dict.set(k, JSON.Encode.array(v->encStates)))
216
+ JSON.Encode.object(obj)
217
+ }
218
+
219
+ let thenStates = async (thunk, expectedStates) => {
220
+ let store = await thunk()
221
+ let keys = store->Dict.keysToArray
222
+ let actualId = keys->Array.get(0)
223
+ let actualStates = store->Dict.valuesToArray->Array.get(0)->Option.getOr([])
224
+ if (
225
+ keys->Array.length == 1 &&
226
+ actualId == Some(testId.contents) &&
227
+ statesEq(actualStates, expectedStates)
228
+ ) {
229
+ Outcome.pass
230
+ } else {
231
+ Outcome.fail(
232
+ StateMismatch({
233
+ key: testId.contents,
234
+ expected: Some(JSON.Encode.array(expectedStates->encStates)),
235
+ actual: Some(encStore(store)),
236
+ }),
237
+ )
238
+ }
239
+ }
240
+
241
+ let thenStatesWithId = async (thunk, id, expectedStates) => {
242
+ let store = await thunk()
243
+ let keys = store->Dict.keysToArray
244
+ let actualId = keys->Array.get(0)
245
+ let actualStates = store->Dict.valuesToArray->Array.get(0)->Option.getOr([])
246
+ if keys->Array.length == 1 && actualId == Some(id) && statesEq(actualStates, expectedStates) {
247
+ Outcome.pass
248
+ } else {
249
+ Outcome.fail(
250
+ StateMismatch({
251
+ key: id,
252
+ expected: Some(JSON.Encode.array(expectedStates->encStates)),
253
+ actual: Some(encStore(store)),
254
+ }),
255
+ )
256
+ }
257
+ }
258
+
259
+ let thenAllStates = async (thunk, expectedStore: store) => {
260
+ let store = await thunk()
261
+ if storeEq(store, expectedStore) {
262
+ Outcome.pass
263
+ } else {
264
+ Outcome.fail(
265
+ StateMismatch({
266
+ key: "<all>",
267
+ expected: Some(encStore(expectedStore)),
268
+ actual: Some(encStore(store)),
269
+ }),
270
+ )
271
+ }
272
+ }
273
+
274
+ let thenState = (thunk, expectedState) => thenStates(thunk, [expectedState])
275
+
276
+ let thenStateWithId = (thunk, id, expectedState) =>
277
+ thenStatesWithId(thunk, id, [expectedState])
278
+
279
+ let thenNoState = async thunk => {
280
+ let store = await thunk()
281
+ let total =
282
+ store->Dict.valuesToArray->Array.reduce(0, (acc, states) => acc + states->Array.length)
283
+ if total == 0 {
284
+ Outcome.pass
285
+ } else {
286
+ Outcome.fail(
287
+ StateMismatch({
288
+ key: "<any>",
289
+ expected: None,
290
+ actual: Some(encStore(store)),
291
+ }),
292
+ )
293
+ }
294
+ }
295
+
296
+ let thenThrow = async thunk =>
297
+ switch await thunk() {
298
+ | _ =>
299
+ Outcome.fail(
300
+ Throw({
301
+ error: "Expected thunk to throw but it returned normally",
302
+ stack: "",
303
+ }),
304
+ )
305
+ | exception _ => Outcome.pass
306
+ }
307
+
308
+ let thenFail = async thunk =>
309
+ switch await thunk() {
310
+ | _ =>
311
+ Outcome.fail(
312
+ Throw({error: "Expected failure but thunk returned normally", stack: ""}),
313
+ )
314
+ | exception _ => Outcome.pass
315
+ }
316
+ }