@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,47 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+
5
+ let callSchema = S.schema(s => ({
6
+ TAG: "SendConfirmation",
7
+ email: s.m(S.string),
8
+ orderId: s.m(S.string)
9
+ }));
10
+
11
+ let calls = {
12
+ contents: []
13
+ };
14
+
15
+ function reset() {
16
+ calls.contents = [];
17
+ }
18
+
19
+ function recordSend(email, orderId) {
20
+ calls.contents = calls.contents.concat([{
21
+ TAG: "SendConfirmation",
22
+ email: email,
23
+ orderId: orderId
24
+ }]);
25
+ }
26
+
27
+ function mock_snapshot() {
28
+ return calls.contents;
29
+ }
30
+
31
+ function mock_encode(c) {
32
+ return S.reverseConvertToJsonOrThrow(c, callSchema);
33
+ }
34
+
35
+ let mock = {
36
+ snapshot: mock_snapshot,
37
+ encode: mock_encode
38
+ };
39
+
40
+ export {
41
+ callSchema,
42
+ calls,
43
+ reset,
44
+ recordSend,
45
+ mock,
46
+ }
47
+ /* callSchema Not a pure module */
@@ -0,0 +1,27 @@
1
+ // External-Spec verification test for @@reventless.gwt.
2
+ //
3
+ // Zero payload, no local module, no alias. The PPX resolves:
4
+ // - Kind from the folder segment "StateChange" -> Behavior
5
+ // - Spec from the filename stem (strip "_GWT") -> ExternalAddCategorySlice
6
+ // - Impl derived as ExternalAddCategorySlice_Behavior
7
+ // - open ExternalAddCategorySlice +
8
+ // include Behavior_GWT.Make(ExternalAddCategorySlice, ExternalAddCategorySlice_Behavior)
9
+ //
10
+ // Mirrors the canonical consumer pattern documented in
11
+ // docs/guides/given-when-then.md § 4.10.
12
+
13
+ @@reventless.gwt
14
+
15
+ describe("ExternalAddCategory StateChangeSlice (external Spec)", () => {
16
+ test("empty event log produces CategoryAdded", () =>
17
+ givenEvents([])
18
+ ->whenCmd(AddCategory({categoryId: "c1", name: "Electronics"}))
19
+ ->thenEvent(CategoryAdded({categoryId: "c1", name: "Electronics"}))
20
+ )
21
+
22
+ test("existing category returns CategoryAlreadyExists", () =>
23
+ givenEvents([CategoryAdded])
24
+ ->whenCmd(AddCategory({categoryId: "c1", name: "X"}))
25
+ ->thenError(CategoryAlreadyExists)
26
+ )
27
+ })
@@ -0,0 +1,70 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Behavior_GWT$ReventlessGwt from "../../src/Behavior_GWT.res.mjs";
4
+ import * as ExternalAddCategorySlice$ReventlessGwt from "../../src/StateChange/ExternalAddCategorySlice.res.mjs";
5
+ import * as ExternalAddCategorySlice_Behavior$ReventlessGwt from "../../src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs";
6
+
7
+ let include = Behavior_GWT$ReventlessGwt.Make(ExternalAddCategorySlice$ReventlessGwt)(ExternalAddCategorySlice_Behavior$ReventlessGwt);
8
+
9
+ let describe = include.describe;
10
+
11
+ let test = include.test;
12
+
13
+ let givenEvents = include.givenEvents;
14
+
15
+ let whenCmd = include.whenCmd;
16
+
17
+ let thenEvent = include.thenEvent;
18
+
19
+ let thenError = include.thenError;
20
+
21
+ describe("ExternalAddCategory StateChangeSlice (external Spec)", () => {
22
+ test("empty event log produces CategoryAdded", () => thenEvent(whenCmd(givenEvents([]), {
23
+ TAG: "AddCategory",
24
+ categoryId: "c1",
25
+ name: "Electronics"
26
+ }), {
27
+ TAG: "CategoryAdded",
28
+ categoryId: "c1",
29
+ name: "Electronics"
30
+ }));
31
+ test("existing category returns CategoryAlreadyExists", () => thenError(whenCmd(givenEvents(["CategoryAdded"]), {
32
+ TAG: "AddCategory",
33
+ categoryId: "c1",
34
+ name: "X"
35
+ }), "CategoryAlreadyExists"));
36
+ });
37
+
38
+ let Spec = include.Spec;
39
+
40
+ let todo = include.todo;
41
+
42
+ let thenEvents = include.thenEvents;
43
+
44
+ let thenNoEvent = include.thenNoEvent;
45
+
46
+ let thenEventWithError = include.thenEventWithError;
47
+
48
+ let thenEventsWithError = include.thenEventsWithError;
49
+
50
+ let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
51
+
52
+ let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
53
+
54
+ export {
55
+ Spec,
56
+ describe,
57
+ todo,
58
+ test,
59
+ givenEvents,
60
+ whenCmd,
61
+ thenEvent,
62
+ thenEvents,
63
+ thenNoEvent,
64
+ thenEventWithError,
65
+ thenEventsWithError,
66
+ thenError,
67
+ thenAppendsConditionedOn,
68
+ thenAppendsConditionedOnExactly,
69
+ }
70
+ /* include Not a pure module */
@@ -0,0 +1,9 @@
1
+ // Companion fixtures module for [WithFixtures_GWT.res]. The PPX detects this
2
+ // sibling by filename and auto-injects [open WithFixtures_Fixtures] into the
3
+ // GWT test body, so the test can reference [addCategoryElectronics] /
4
+ // [electronicsCategoryAdded] unqualified.
5
+
6
+ open WithFixtures
7
+
8
+ let addCategoryElectronics = AddCategory({categoryId: "c1", name: "Electronics"})
9
+ let electronicsCategoryAdded = CategoryAdded({categoryId: "c1", name: "Electronics"})
@@ -0,0 +1,20 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ let addCategoryElectronics = {
5
+ TAG: "AddCategory",
6
+ categoryId: "c1",
7
+ name: "Electronics"
8
+ };
9
+
10
+ let electronicsCategoryAdded = {
11
+ TAG: "CategoryAdded",
12
+ categoryId: "c1",
13
+ name: "Electronics"
14
+ };
15
+
16
+ export {
17
+ addCategoryElectronics,
18
+ electronicsCategoryAdded,
19
+ }
20
+ /* No side effect */
@@ -0,0 +1,23 @@
1
+ // Companion-fixtures auto-open verification test for @@reventless.gwt.
2
+ //
3
+ // Zero payload, no local module, no manual open. The PPX resolves:
4
+ // - Kind from the folder segment "StateChange" -> StateChangeSlice
5
+ // - Spec from the filename stem (strip "_GWT") -> WithFixtures
6
+ // - Companion fixtures: sibling [WithFixtures_Fixtures.res] is detected on
7
+ // disk, so the PPX emits both [open WithFixtures] and
8
+ // [open WithFixtures_Fixtures] before the [include].
9
+ //
10
+ // The test body uses [addCategoryElectronics] / [electronicsCategoryAdded]
11
+ // unqualified — resolution only succeeds if the companion fixtures open was
12
+ // injected.
13
+
14
+ @@reventless.gwt
15
+
16
+ describe("WithFixtures StateChangeSlice (companion fixtures auto-open)", () => {
17
+ test("uses companion fixtures unqualified", () =>
18
+ givenEvents([])
19
+ ->whenCmd(addCategoryElectronics)
20
+ ->thenEvent((electronicsCategoryAdded :> event))
21
+ )
22
+ })
23
+
@@ -0,0 +1,56 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Behavior_GWT$ReventlessGwt from "../../src/Behavior_GWT.res.mjs";
4
+ import * as WithFixtures$ReventlessGwt from "../../src/StateChange/WithFixtures.res.mjs";
5
+ import * as WithFixtures_Behavior$ReventlessGwt from "../../src/StateChange/WithFixtures_Behavior.res.mjs";
6
+ import * as WithFixtures_Fixtures$ReventlessGwt from "./WithFixtures_Fixtures.res.mjs";
7
+
8
+ let include = Behavior_GWT$ReventlessGwt.Make(WithFixtures$ReventlessGwt)(WithFixtures_Behavior$ReventlessGwt);
9
+
10
+ let describe = include.describe;
11
+
12
+ let test = include.test;
13
+
14
+ let givenEvents = include.givenEvents;
15
+
16
+ let whenCmd = include.whenCmd;
17
+
18
+ let thenEvent = include.thenEvent;
19
+
20
+ describe("WithFixtures StateChangeSlice (companion fixtures auto-open)", () => test("uses companion fixtures unqualified", () => thenEvent(whenCmd(givenEvents([]), WithFixtures_Fixtures$ReventlessGwt.addCategoryElectronics), WithFixtures_Fixtures$ReventlessGwt.electronicsCategoryAdded)));
21
+
22
+ let Spec = include.Spec;
23
+
24
+ let todo = include.todo;
25
+
26
+ let thenEvents = include.thenEvents;
27
+
28
+ let thenNoEvent = include.thenNoEvent;
29
+
30
+ let thenEventWithError = include.thenEventWithError;
31
+
32
+ let thenEventsWithError = include.thenEventsWithError;
33
+
34
+ let thenError = include.thenError;
35
+
36
+ let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
37
+
38
+ let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
39
+
40
+ export {
41
+ Spec,
42
+ describe,
43
+ todo,
44
+ test,
45
+ givenEvents,
46
+ whenCmd,
47
+ thenEvent,
48
+ thenEvents,
49
+ thenNoEvent,
50
+ thenEventWithError,
51
+ thenEventsWithError,
52
+ thenError,
53
+ thenAppendsConditionedOn,
54
+ thenAppendsConditionedOnExactly,
55
+ }
56
+ /* include Not a pure module */
@@ -0,0 +1,7 @@
1
+ // Companion fixtures module for [WithManualOpen_GWT.res]. The test file
2
+ // opens this module manually, so the PPX must NOT duplicate the open.
3
+
4
+ open WithManualOpen
5
+
6
+ let addCategoryBooks = AddCategory({categoryId: "c2", name: "Books"})
7
+ let booksCategoryAdded = CategoryAdded({categoryId: "c2", name: "Books"})
@@ -0,0 +1,20 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ let addCategoryBooks = {
5
+ TAG: "AddCategory",
6
+ categoryId: "c2",
7
+ name: "Books"
8
+ };
9
+
10
+ let booksCategoryAdded = {
11
+ TAG: "CategoryAdded",
12
+ categoryId: "c2",
13
+ name: "Books"
14
+ };
15
+
16
+ export {
17
+ addCategoryBooks,
18
+ booksCategoryAdded,
19
+ }
20
+ /* No side effect */
@@ -0,0 +1,17 @@
1
+ // Manual-open dedup verification for @@reventless.gwt companion fixtures.
2
+ //
3
+ // The sibling [WithManualOpen_Fixtures.res] exists AND the test body opens
4
+ // it manually. The PPX detects the existing open via [Util.has_open] and
5
+ // skips the duplicate injection — the file compiles without shadow warnings.
6
+
7
+ @@reventless.gwt
8
+
9
+ open WithManualOpen_Fixtures
10
+
11
+ describe("WithManualOpen (manual open not duplicated by PPX)", () => {
12
+ test("manual open resolves fixture identifiers", () =>
13
+ givenEvents([])
14
+ ->whenCmd(addCategoryBooks)
15
+ ->thenEvent((booksCategoryAdded :> event))
16
+ )
17
+ })
@@ -0,0 +1,56 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Behavior_GWT$ReventlessGwt from "../../src/Behavior_GWT.res.mjs";
4
+ import * as WithManualOpen$ReventlessGwt from "../../src/StateChange/WithManualOpen.res.mjs";
5
+ import * as WithManualOpen_Behavior$ReventlessGwt from "../../src/StateChange/WithManualOpen_Behavior.res.mjs";
6
+ import * as WithManualOpen_Fixtures$ReventlessGwt from "./WithManualOpen_Fixtures.res.mjs";
7
+
8
+ let include = Behavior_GWT$ReventlessGwt.Make(WithManualOpen$ReventlessGwt)(WithManualOpen_Behavior$ReventlessGwt);
9
+
10
+ let describe = include.describe;
11
+
12
+ let test = include.test;
13
+
14
+ let givenEvents = include.givenEvents;
15
+
16
+ let whenCmd = include.whenCmd;
17
+
18
+ let thenEvent = include.thenEvent;
19
+
20
+ describe("WithManualOpen (manual open not duplicated by PPX)", () => test("manual open resolves fixture identifiers", () => thenEvent(whenCmd(givenEvents([]), WithManualOpen_Fixtures$ReventlessGwt.addCategoryBooks), WithManualOpen_Fixtures$ReventlessGwt.booksCategoryAdded)));
21
+
22
+ let Spec = include.Spec;
23
+
24
+ let todo = include.todo;
25
+
26
+ let thenEvents = include.thenEvents;
27
+
28
+ let thenNoEvent = include.thenNoEvent;
29
+
30
+ let thenEventWithError = include.thenEventWithError;
31
+
32
+ let thenEventsWithError = include.thenEventsWithError;
33
+
34
+ let thenError = include.thenError;
35
+
36
+ let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
37
+
38
+ let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
39
+
40
+ export {
41
+ Spec,
42
+ describe,
43
+ todo,
44
+ test,
45
+ givenEvents,
46
+ whenCmd,
47
+ thenEvent,
48
+ thenEvents,
49
+ thenNoEvent,
50
+ thenEventWithError,
51
+ thenEventsWithError,
52
+ thenError,
53
+ thenAppendsConditionedOn,
54
+ thenAppendsConditionedOnExactly,
55
+ }
56
+ /* include Not a pure module */
@@ -0,0 +1,160 @@
1
+ // Worked example for Behavior_GWT (slice flavor).
2
+ // Uses the canonical "AddCategory" DCB slice shape — payload-less consumed
3
+ // events, decide that appends `CategoryAdded` unless the category already
4
+ // exists. Command carries a `@s.matches(DcbTag.string)` annotated ID so the
5
+ // DCB optimistic-concurrency query resolves to a single-entity read.
6
+ //
7
+ // Plan 02 Phase 6: split-form Spec/Behavior; PPX inference resolves the
8
+ // folder kind from the test path's "StateChangeSlice" substring → Behavior
9
+ // DSL → emits [include Behavior_GWT.Make(Spec, Behavior)].
10
+
11
+ @@reventless.gwt
12
+
13
+ module AddCategorySlice = {
14
+ let name = "AddCategory"
15
+
16
+ @schema
17
+ type consumedEvent =
18
+ | CategoryAdded
19
+ | CategoryArchived
20
+
21
+ @schema
22
+ type command =
23
+ AddCategory({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
24
+
25
+ @schema
26
+ type error = CategoryAlreadyExists
27
+
28
+ @schema
29
+ type event =
30
+ CategoryAdded({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
31
+ }
32
+
33
+ module AddCategorySliceBehavior = {
34
+ module Spec = AddCategorySlice
35
+ open AddCategorySlice
36
+
37
+ type state = {exists: bool, archived: bool}
38
+ let initialState: state = {exists: false, archived: false}
39
+
40
+ let evolve = (state: state, event: consumedEvent): state =>
41
+ switch event {
42
+ | CategoryAdded => {exists: true, archived: false}
43
+ | CategoryArchived => {...state, archived: true}
44
+ }
45
+
46
+ let decide = (state: state, command: command): result<array<event>, error> =>
47
+ switch command {
48
+ | AddCategory({categoryId, name}) =>
49
+ state.exists ? Error(CategoryAlreadyExists) : Ok([CategoryAdded({categoryId, name})])
50
+ }
51
+ }
52
+
53
+ describe("AddCategory StateChangeSlice", () => {
54
+ test("on empty event log produces CategoryAdded", () =>
55
+ givenEvents([])
56
+ ->whenCmd(AddCategory({categoryId: "c1", name: "Electronics"}))
57
+ ->thenEvent(CategoryAdded({categoryId: "c1", name: "Electronics"}))
58
+ )
59
+
60
+ test("on existing category returns CategoryAlreadyExists", () =>
61
+ givenEvents([CategoryAdded])
62
+ ->whenCmd(AddCategory({categoryId: "c1", name: "X"}))
63
+ ->thenError(CategoryAlreadyExists)
64
+ )
65
+
66
+ test("append condition is single-entity query over consumed event types", () =>
67
+ givenEvents([])
68
+ ->whenCmd(AddCategory({categoryId: "c1", name: "Electronics"}))
69
+ ->thenAppendsConditionedOn([
70
+ {
71
+ eventTypes: ["CategoryAdded", "CategoryArchived"],
72
+ tags: [{key: "categoryId", value: "c1"}],
73
+ },
74
+ ])
75
+ )
76
+
77
+ test("exact condition matches no prior position (new entity)", () =>
78
+ givenEvents([])
79
+ ->whenCmd(AddCategory({categoryId: "c2", name: "Books"}))
80
+ ->thenAppendsConditionedOnExactly({
81
+ query: [
82
+ {
83
+ eventTypes: ["CategoryAdded", "CategoryArchived"],
84
+ tags: [{key: "categoryId", value: "c2"}],
85
+ },
86
+ ],
87
+ })
88
+ )
89
+ })
90
+
91
+ // A second slice whose command has no `@s.matches(DcbTag.string)` annotation —
92
+ // verifies the implicit Stage 4 check fires with `AppendConditionMismatch`
93
+ // before any subsequent `then*` runs its own assertion.
94
+ module MissingTagSlice = {
95
+ let name = "MissingTagSlice"
96
+
97
+ @schema
98
+ type consumedEvent = Noop
99
+
100
+ @schema
101
+ type command = Do({id: string})
102
+
103
+ @schema
104
+ type error = Rejected
105
+
106
+ @schema
107
+ type event = Done({id: string})
108
+ }
109
+
110
+ module MissingTagBehavior = {
111
+ // Qualify references through [Spec] to avoid shadowing the file-scope
112
+ // [consumedEvent] / [command] / [event] / [error] brought in by the PPX-
113
+ // injected [open AddCategorySlice].
114
+ module Spec = MissingTagSlice
115
+
116
+ type state = unit
117
+ let initialState: state = ()
118
+
119
+ let evolve = (state: state, _event: Spec.consumedEvent): state => state
120
+
121
+ let decide = (
122
+ _state: state,
123
+ cmd: Spec.command,
124
+ ): result<array<Spec.event>, Spec.error> =>
125
+ switch cmd {
126
+ | Do({id}) => Ok([Spec.Done({id: id})])
127
+ }
128
+ }
129
+
130
+ module MissingTagGwt = Behavior_GWT.Make(MissingTagSlice, MissingTagBehavior)
131
+
132
+ MissingTagGwt.describe("MissingTag slice implicit check", () => {
133
+ MissingTagGwt.test("thenEvent surfaces AppendConditionMismatch when command lacks DCB tag", () => {
134
+ let outcome =
135
+ MissingTagGwt.givenEvents([])
136
+ ->MissingTagGwt.whenCmd(Do({id: "x1"}))
137
+ ->MissingTagGwt.thenEvent(Done({id: "x1"}))
138
+ switch outcome {
139
+ | Error(AppendConditionMismatch(_)) => Outcome.pass
140
+ | Error(other) =>
141
+ Outcome.fail(
142
+ Throw({error: "expected AppendConditionMismatch, got: " ++ Outcome.kindName(other), stack: ""}),
143
+ )
144
+ | Ok() =>
145
+ Outcome.fail(
146
+ Throw({error: "expected AppendConditionMismatch, got pass", stack: ""}),
147
+ )
148
+ }
149
+ })
150
+
151
+ MissingTagGwt.test(
152
+ "thenAppendsConditionedOnExactly bypasses implicit check (still passes with derived)",
153
+ () =>
154
+ MissingTagGwt.givenEvents([])
155
+ ->MissingTagGwt.whenCmd(Do({id: "x1"}))
156
+ ->MissingTagGwt.thenAppendsConditionedOnExactly({
157
+ query: [{eventTypes: ["Noop"], tags: []}],
158
+ }),
159
+ )
160
+ })