@reventlessdev/reventless-gwt 1.0.0-alpha.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/CHANGELOG.md +916 -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 +569 -0
  10. package/src/Behavior_GWT.res.mjs +523 -0
  11. package/src/Bind.res +29 -0
  12. package/src/Bind.res.mjs +22 -0
  13. package/src/BuildClassifier.res +147 -0
  14. package/src/BuildClassifier.res.mjs +136 -0
  15. package/src/Cancellation.res +37 -0
  16. package/src/Cancellation.res.mjs +46 -0
  17. package/src/ChildProcess.res +102 -0
  18. package/src/ChildProcess.res.mjs +107 -0
  19. package/src/Cli.res +905 -0
  20. package/src/Cli.res.mjs +952 -0
  21. package/src/Collector.res +212 -0
  22. package/src/Collector.res.mjs +218 -0
  23. package/src/ComponentMeta.res +77 -0
  24. package/src/ComponentMeta.res.mjs +91 -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 +99 -0
  33. package/src/DomainDeadCode.res +98 -0
  34. package/src/DomainDeadCode.res.mjs +72 -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 +45 -0
  40. package/src/Filter.res.mjs +47 -0
  41. package/src/Flow_GWT.res +664 -0
  42. package/src/Flow_GWT.res.mjs +604 -0
  43. package/src/FormatterHuman.res +109 -0
  44. package/src/FormatterHuman.res.mjs +115 -0
  45. package/src/FormatterJson.res +264 -0
  46. package/src/FormatterJson.res.mjs +322 -0
  47. package/src/FormatterJunit.res +93 -0
  48. package/src/FormatterJunit.res.mjs +90 -0
  49. package/src/FormatterTap.res +100 -0
  50. package/src/FormatterTap.res.mjs +131 -0
  51. package/src/FormatterVsCode.res +352 -0
  52. package/src/FormatterVsCode.res.mjs +546 -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 +119 -0
  62. package/src/LocalHost.res.mjs +99 -0
  63. package/src/Mapping_GWT.res +474 -0
  64. package/src/Mapping_GWT.res.mjs +322 -0
  65. package/src/MismatchRender.res +72 -0
  66. package/src/MismatchRender.res.mjs +127 -0
  67. package/src/MultiSourceProjection_GWT.res +316 -0
  68. package/src/MultiSourceProjection_GWT.res.mjs +354 -0
  69. package/src/OutboundTranslation_GWT.res +238 -0
  70. package/src/OutboundTranslation_GWT.res.mjs +186 -0
  71. package/src/Outcome.res +97 -0
  72. package/src/Outcome.res.mjs +93 -0
  73. package/src/PackageScan.res +80 -0
  74. package/src/PackageScan.res.mjs +81 -0
  75. package/src/PlatformRunner.res +181 -0
  76. package/src/PlatformRunner.res.mjs +176 -0
  77. package/src/PlatformScan.res +142 -0
  78. package/src/PlatformScan.res.mjs +132 -0
  79. package/src/ProcessManager.res +135 -0
  80. package/src/ProcessManager.res.mjs +119 -0
  81. package/src/Projection_GWT.res +312 -0
  82. package/src/Projection_GWT.res.mjs +332 -0
  83. package/src/Query_GWT.res +379 -0
  84. package/src/Query_GWT.res.mjs +367 -0
  85. package/src/RenderRescript.res +125 -0
  86. package/src/RenderRescript.res.mjs +141 -0
  87. package/src/RunWorker.res +24 -0
  88. package/src/RunWorker.res.mjs +16 -0
  89. package/src/RunnerTypes.res +68 -0
  90. package/src/RunnerTypes.res.mjs +60 -0
  91. package/src/SideEffect_GWT.res +157 -0
  92. package/src/SideEffect_GWT.res.mjs +113 -0
  93. package/src/StateChange/ExternalAddCategorySlice.res +25 -0
  94. package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
  95. package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
  96. package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
  97. package/src/StateChange/WithFixtures.res +22 -0
  98. package/src/StateChange/WithFixtures.res.mjs +31 -0
  99. package/src/StateChange/WithFixtures_Behavior.res +17 -0
  100. package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
  101. package/src/StateChange/WithManualOpen.res +23 -0
  102. package/src/StateChange/WithManualOpen.res.mjs +31 -0
  103. package/src/StateChange/WithManualOpen_Behavior.res +17 -0
  104. package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
  105. package/src/StubRuntime.res +54 -0
  106. package/src/StubRuntime.res.mjs +55 -0
  107. package/src/TestFixtures.res +16 -0
  108. package/src/TestFixtures.res.mjs +34 -0
  109. package/src/Watch.res +108 -0
  110. package/src/Watch.res.mjs +85 -0
  111. package/src/WatcherProbe.res +56 -0
  112. package/src/WatcherProbe.res.mjs +59 -0
  113. package/src/Worker.res +24 -0
  114. package/src/Worker.res.mjs +9 -0
  115. package/tests/AutomationGwtTest.res +71 -0
  116. package/tests/AutomationGwtTest.res.mjs +189 -0
  117. package/tests/BuildClassifierTest.res +133 -0
  118. package/tests/BuildClassifierTest.res.mjs +176 -0
  119. package/tests/CliRunEntryTest.res +57 -0
  120. package/tests/CliRunEntryTest.res.mjs +65 -0
  121. package/tests/CliWatchScopeTest.res +62 -0
  122. package/tests/CliWatchScopeTest.res.mjs +95 -0
  123. package/tests/ComponentMetaTest.res +67 -0
  124. package/tests/ComponentMetaTest.res.mjs +71 -0
  125. package/tests/DelegateGwtTest.res +487 -0
  126. package/tests/DelegateGwtTest.res.mjs +891 -0
  127. package/tests/DiscoveryTest.res +42 -0
  128. package/tests/DiscoveryTest.res.mjs +43 -0
  129. package/tests/DomainDeadCodeTest.res +119 -0
  130. package/tests/DomainDeadCodeTest.res.mjs +158 -0
  131. package/tests/DomainGraphTest.res +448 -0
  132. package/tests/DomainGraphTest.res.mjs +505 -0
  133. package/tests/FlowAggregateGwtTest.res +171 -0
  134. package/tests/FlowAggregateGwtTest.res.mjs +273 -0
  135. package/tests/FlowCrossPluginGwtTest.res +340 -0
  136. package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
  137. package/tests/FlowGwtTest.res +222 -0
  138. package/tests/FlowGwtTest.res.mjs +433 -0
  139. package/tests/FormatterGoldenTest.res +125 -0
  140. package/tests/FormatterGoldenTest.res.mjs +162 -0
  141. package/tests/FormatterVsCodeMessageTest.res +41 -0
  142. package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
  143. package/tests/InboundTranslationGwtTest.res +34 -0
  144. package/tests/InboundTranslationGwtTest.res.mjs +99 -0
  145. package/tests/LocalHostIntegration.res +70 -0
  146. package/tests/LocalHostIntegration.res.mjs +64 -0
  147. package/tests/LocalHostTest.res +71 -0
  148. package/tests/LocalHostTest.res.mjs +63 -0
  149. package/tests/MappingGwtTest.res +350 -0
  150. package/tests/MappingGwtTest.res.mjs +596 -0
  151. package/tests/OutboundTranslationGwtTest.res +59 -0
  152. package/tests/OutboundTranslationGwtTest.res.mjs +139 -0
  153. package/tests/PlatformRunnerTest.res +66 -0
  154. package/tests/PlatformRunnerTest.res.mjs +85 -0
  155. package/tests/PlatformScanTest.res +61 -0
  156. package/tests/PlatformScanTest.res.mjs +56 -0
  157. package/tests/QueryGwtTest.res +161 -0
  158. package/tests/QueryGwtTest.res.mjs +314 -0
  159. package/tests/QueryResolverGwtTest.res +71 -0
  160. package/tests/QueryResolverGwtTest.res.mjs +136 -0
  161. package/tests/RunnerUnitTest.res +219 -0
  162. package/tests/RunnerUnitTest.res.mjs +244 -0
  163. package/tests/SideEffect/FakeOrderNotification.res +23 -0
  164. package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
  165. package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
  166. package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
  167. package/tests/SideEffect/MockEmail.res +18 -0
  168. package/tests/SideEffect/MockEmail.res.mjs +47 -0
  169. package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
  170. package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
  171. package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
  172. package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
  173. package/tests/StateChange/WithFixtures_GWT.res +23 -0
  174. package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
  175. package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
  176. package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
  177. package/tests/StateChange/WithManualOpen_GWT.res +17 -0
  178. package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
  179. package/tests/StateChangeSliceGwtTest.res +160 -0
  180. package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
  181. package/tests/StateViewSliceGwtTest.res +64 -0
  182. package/tests/StateViewSliceGwtTest.res.mjs +161 -0
  183. package/tests/WatchDebounceTest.res +106 -0
  184. package/tests/WatchDebounceTest.res.mjs +128 -0
@@ -0,0 +1,280 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
5
+ import * as Outcome$ReventlessGwt from "../src/Outcome.res.mjs";
6
+ import * as Behavior_GWT$ReventlessGwt from "../src/Behavior_GWT.res.mjs";
7
+
8
+ let consumedEventSchema = S.union([
9
+ S.literal("CategoryAdded"),
10
+ S.literal("CategoryArchived")
11
+ ]);
12
+
13
+ let commandSchema = S.schema(s => ({
14
+ TAG: "AddCategory",
15
+ categoryId: s.m(DcbTag$Reventless.string),
16
+ name: s.m(S.string)
17
+ }));
18
+
19
+ let errorSchema = S.literal("CategoryAlreadyExists");
20
+
21
+ let eventSchema = S.schema(s => ({
22
+ TAG: "CategoryAdded",
23
+ categoryId: s.m(DcbTag$Reventless.string),
24
+ name: s.m(S.string)
25
+ }));
26
+
27
+ function commandAuthorization(param) {
28
+ return "AllowAuthenticated";
29
+ }
30
+
31
+ let AddCategorySlice = {
32
+ name: "AddCategory",
33
+ consumedEventSchema: consumedEventSchema,
34
+ commandSchema: commandSchema,
35
+ errorSchema: errorSchema,
36
+ eventSchema: eventSchema,
37
+ commandAuthorization: commandAuthorization,
38
+ readConsistency: "EscalateOnRetry"
39
+ };
40
+
41
+ let initialState = {
42
+ exists: false,
43
+ archived: false
44
+ };
45
+
46
+ function evolve(state, event) {
47
+ if (event === "CategoryAdded") {
48
+ return {
49
+ exists: true,
50
+ archived: false
51
+ };
52
+ } else {
53
+ return {
54
+ exists: state.exists,
55
+ archived: true
56
+ };
57
+ }
58
+ }
59
+
60
+ function decide(state, command) {
61
+ if (state.exists) {
62
+ return {
63
+ TAG: "Error",
64
+ _0: "CategoryAlreadyExists"
65
+ };
66
+ } else {
67
+ return {
68
+ TAG: "Ok",
69
+ _0: [{
70
+ TAG: "CategoryAdded",
71
+ categoryId: command.categoryId,
72
+ name: command.name
73
+ }]
74
+ };
75
+ }
76
+ }
77
+
78
+ let AddCategorySliceBehavior = {
79
+ Spec: undefined,
80
+ initialState: initialState,
81
+ evolve: evolve,
82
+ decide: decide
83
+ };
84
+
85
+ let include = Behavior_GWT$ReventlessGwt.Make(AddCategorySlice)({
86
+ initialState: initialState,
87
+ evolve: evolve,
88
+ decide: decide
89
+ });
90
+
91
+ let describe = include.describe;
92
+
93
+ let test = include.test;
94
+
95
+ let givenEvents = include.givenEvents;
96
+
97
+ let whenCmd = include.whenCmd;
98
+
99
+ let thenEvent = include.thenEvent;
100
+
101
+ let thenError = include.thenError;
102
+
103
+ let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
104
+
105
+ let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
106
+
107
+ describe("AddCategory StateChangeSlice", () => {
108
+ test("on empty event log produces CategoryAdded", () => thenEvent(whenCmd(givenEvents([]), {
109
+ TAG: "AddCategory",
110
+ categoryId: "c1",
111
+ name: "Electronics"
112
+ }), {
113
+ TAG: "CategoryAdded",
114
+ categoryId: "c1",
115
+ name: "Electronics"
116
+ }));
117
+ test("on existing category returns CategoryAlreadyExists", () => thenError(whenCmd(givenEvents(["CategoryAdded"]), {
118
+ TAG: "AddCategory",
119
+ categoryId: "c1",
120
+ name: "X"
121
+ }), "CategoryAlreadyExists"));
122
+ test("append condition is single-entity query over consumed event types", () => thenAppendsConditionedOn(whenCmd(givenEvents([]), {
123
+ TAG: "AddCategory",
124
+ categoryId: "c1",
125
+ name: "Electronics"
126
+ }), [{
127
+ eventTypes: [
128
+ "CategoryAdded",
129
+ "CategoryArchived"
130
+ ],
131
+ tags: [{
132
+ key: "categoryId",
133
+ value: "c1"
134
+ }]
135
+ }]));
136
+ test("exact condition matches no prior position (new entity)", () => thenAppendsConditionedOnExactly(whenCmd(givenEvents([]), {
137
+ TAG: "AddCategory",
138
+ categoryId: "c2",
139
+ name: "Books"
140
+ }), {
141
+ query: [{
142
+ eventTypes: [
143
+ "CategoryAdded",
144
+ "CategoryArchived"
145
+ ],
146
+ tags: [{
147
+ key: "categoryId",
148
+ value: "c2"
149
+ }]
150
+ }]
151
+ }));
152
+ });
153
+
154
+ let consumedEventSchema$1 = S.literal("Noop");
155
+
156
+ let commandSchema$1 = S.schema(s => ({
157
+ TAG: "Do",
158
+ id: s.m(S.string)
159
+ }));
160
+
161
+ let errorSchema$1 = S.literal("Rejected");
162
+
163
+ let eventSchema$1 = S.schema(s => ({
164
+ TAG: "Done",
165
+ id: s.m(S.string)
166
+ }));
167
+
168
+ function commandAuthorization$1(param) {
169
+ return "AllowAuthenticated";
170
+ }
171
+
172
+ let MissingTagSlice = {
173
+ name: "MissingTagSlice",
174
+ consumedEventSchema: consumedEventSchema$1,
175
+ commandSchema: commandSchema$1,
176
+ errorSchema: errorSchema$1,
177
+ eventSchema: eventSchema$1,
178
+ commandAuthorization: commandAuthorization$1,
179
+ readConsistency: "EscalateOnRetry"
180
+ };
181
+
182
+ function evolve$1(state, _event) {
183
+
184
+ }
185
+
186
+ function decide$1(_state, cmd) {
187
+ return {
188
+ TAG: "Ok",
189
+ _0: [{
190
+ TAG: "Done",
191
+ id: cmd.id
192
+ }]
193
+ };
194
+ }
195
+
196
+ let MissingTagBehavior = {
197
+ Spec: undefined,
198
+ initialState: undefined,
199
+ evolve: evolve$1,
200
+ decide: decide$1
201
+ };
202
+
203
+ let MissingTagGwt = Behavior_GWT$ReventlessGwt.Make(MissingTagSlice)({
204
+ initialState: undefined,
205
+ evolve: evolve$1,
206
+ decide: decide$1
207
+ });
208
+
209
+ MissingTagGwt.describe("MissingTag slice implicit check", () => {
210
+ MissingTagGwt.test("thenEvent surfaces AppendConditionMismatch when command lacks DCB tag", () => {
211
+ let outcome = MissingTagGwt.thenEvent(MissingTagGwt.whenCmd(MissingTagGwt.givenEvents([]), {
212
+ TAG: "Do",
213
+ id: "x1"
214
+ }), {
215
+ TAG: "Done",
216
+ id: "x1"
217
+ });
218
+ if (outcome.TAG === "Ok") {
219
+ return Outcome$ReventlessGwt.fail({
220
+ TAG: "Throw",
221
+ error: "expected AppendConditionMismatch, got pass",
222
+ stack: ""
223
+ });
224
+ }
225
+ let other = outcome._0;
226
+ if (other.TAG === "AppendConditionMismatch") {
227
+ return Outcome$ReventlessGwt.pass;
228
+ } else {
229
+ return Outcome$ReventlessGwt.fail({
230
+ TAG: "Throw",
231
+ error: "expected AppendConditionMismatch, got: " + Outcome$ReventlessGwt.kindName(other),
232
+ stack: ""
233
+ });
234
+ }
235
+ });
236
+ MissingTagGwt.test("thenAppendsConditionedOnExactly bypasses implicit check (still passes with derived)", () => MissingTagGwt.thenAppendsConditionedOnExactly(MissingTagGwt.whenCmd(MissingTagGwt.givenEvents([]), {
237
+ TAG: "Do",
238
+ id: "x1"
239
+ }), {
240
+ query: [{
241
+ eventTypes: ["Noop"],
242
+ tags: []
243
+ }]
244
+ }));
245
+ });
246
+
247
+ let Spec = include.Spec;
248
+
249
+ let todo = include.todo;
250
+
251
+ let thenEvents = include.thenEvents;
252
+
253
+ let thenNoEvent = include.thenNoEvent;
254
+
255
+ let thenEventWithError = include.thenEventWithError;
256
+
257
+ let thenEventsWithError = include.thenEventsWithError;
258
+
259
+ export {
260
+ AddCategorySlice,
261
+ AddCategorySliceBehavior,
262
+ Spec,
263
+ describe,
264
+ todo,
265
+ test,
266
+ givenEvents,
267
+ whenCmd,
268
+ thenEvent,
269
+ thenEvents,
270
+ thenNoEvent,
271
+ thenEventWithError,
272
+ thenEventsWithError,
273
+ thenError,
274
+ thenAppendsConditionedOn,
275
+ thenAppendsConditionedOnExactly,
276
+ MissingTagSlice,
277
+ MissingTagBehavior,
278
+ MissingTagGwt,
279
+ }
280
+ /* consumedEventSchema Not a pure module */
@@ -0,0 +1,64 @@
1
+ // Worked example for Projection_GWT (single-source StateViewSlice flavor).
2
+ // Projects a small category event stream into a `{categoryId, name, archived}`
3
+ // read model, exercising Set / Update actions.
4
+ //
5
+ // Plan 02 Phase 6: split-form Spec/Projection; PPX inference resolves the
6
+ // "StateViewSlice" filename substring → Projection DSL → emits
7
+ // [include Projection_GWT.Make(Spec, Projection)].
8
+
9
+ @@reventless.gwt
10
+
11
+ open Reventless.Projection
12
+
13
+ module CategoriesView = {
14
+ let name = "CategoriesView"
15
+
16
+ @schema
17
+ type state = {categoryId: string, name: string, archived: bool}
18
+
19
+ @schema
20
+ type consumedEvent =
21
+ | CategoryAdded({categoryId: string, name: string})
22
+ | CategoryRenamed({categoryId: string, name: string})
23
+ | CategoryArchived({categoryId: string})
24
+
25
+ let subIdConfig = None
26
+ }
27
+
28
+ module CategoriesViewProjection = {
29
+ module Spec = CategoriesView
30
+ open CategoriesView
31
+
32
+ let project = (event: consumedEvent) =>
33
+ switch event {
34
+ | CategoryAdded({categoryId, name}) => [
35
+ Set(categoryId, {categoryId, name, archived: false}),
36
+ ]
37
+ | CategoryRenamed({categoryId, name}) => [
38
+ Update(categoryId, state => {...state, name}),
39
+ ]
40
+ | CategoryArchived({categoryId}) => [
41
+ Update(categoryId, state => {...state, archived: true}),
42
+ ]
43
+ }
44
+ }
45
+
46
+ describe("CategoriesView StateViewSlice", () => {
47
+ test("projects CategoryAdded into a new row", () =>
48
+ givenEvents([])
49
+ ->whenEvent(CategoryAdded({categoryId: "c1", name: "Electronics"}))
50
+ ->thenStateWithId(
51
+ "c1",
52
+ {categoryId: "c1", name: "Electronics", archived: false},
53
+ )
54
+ )
55
+
56
+ test("CategoryRenamed updates the name", () =>
57
+ givenEvents([CategoryAdded({categoryId: "c1", name: "Electronics"})])
58
+ ->whenEvent(CategoryRenamed({categoryId: "c1", name: "Consumer Electronics"}))
59
+ ->thenStateWithId(
60
+ "c1",
61
+ {categoryId: "c1", name: "Consumer Electronics", archived: false},
62
+ )
63
+ )
64
+ })
@@ -0,0 +1,161 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Projection_GWT$ReventlessGwt from "../src/Projection_GWT.res.mjs";
5
+
6
+ let stateSchema = S.schema(s => ({
7
+ categoryId: s.m(S.string),
8
+ name: s.m(S.string),
9
+ archived: s.m(S.bool)
10
+ }));
11
+
12
+ let consumedEventSchema = S.union([
13
+ S.schema(s => ({
14
+ TAG: "CategoryAdded",
15
+ categoryId: s.m(S.string),
16
+ name: s.m(S.string)
17
+ })),
18
+ S.schema(s => ({
19
+ TAG: "CategoryRenamed",
20
+ categoryId: s.m(S.string),
21
+ name: s.m(S.string)
22
+ })),
23
+ S.schema(s => ({
24
+ TAG: "CategoryArchived",
25
+ categoryId: s.m(S.string)
26
+ }))
27
+ ]);
28
+
29
+ let CategoriesView = {
30
+ name: "CategoriesView",
31
+ stateSchema: stateSchema,
32
+ consumedEventSchema: consumedEventSchema,
33
+ subIdConfig: undefined,
34
+ authorization: "AllowAuthenticated",
35
+ visibility: "Public"
36
+ };
37
+
38
+ function project(event) {
39
+ switch (event.TAG) {
40
+ case "CategoryAdded" :
41
+ let categoryId = event.categoryId;
42
+ return [{
43
+ TAG: "Set",
44
+ _0: categoryId,
45
+ _1: {
46
+ categoryId: categoryId,
47
+ name: event.name,
48
+ archived: false
49
+ }
50
+ }];
51
+ case "CategoryRenamed" :
52
+ let name = event.name;
53
+ return [{
54
+ TAG: "Update",
55
+ _0: event.categoryId,
56
+ _1: state => ({
57
+ categoryId: state.categoryId,
58
+ name: name,
59
+ archived: state.archived
60
+ })
61
+ }];
62
+ case "CategoryArchived" :
63
+ return [{
64
+ TAG: "Update",
65
+ _0: event.categoryId,
66
+ _1: state => ({
67
+ categoryId: state.categoryId,
68
+ name: state.name,
69
+ archived: true
70
+ })
71
+ }];
72
+ }
73
+ }
74
+
75
+ let CategoriesViewProjection = {
76
+ Spec: undefined,
77
+ project: project
78
+ };
79
+
80
+ let include = Projection_GWT$ReventlessGwt.Make(CategoriesView)({
81
+ project: project
82
+ });
83
+
84
+ let describe = include.describe;
85
+
86
+ let test = include.test;
87
+
88
+ let givenEvents = include.givenEvents;
89
+
90
+ let whenEvent = include.whenEvent;
91
+
92
+ let thenStateWithId = include.thenStateWithId;
93
+
94
+ describe("CategoriesView StateViewSlice", () => {
95
+ test("projects CategoryAdded into a new row", undefined, () => thenStateWithId(whenEvent(givenEvents([]), {
96
+ TAG: "CategoryAdded",
97
+ categoryId: "c1",
98
+ name: "Electronics"
99
+ }), "c1", {
100
+ categoryId: "c1",
101
+ name: "Electronics",
102
+ archived: false
103
+ }));
104
+ test("CategoryRenamed updates the name", undefined, () => thenStateWithId(whenEvent(givenEvents([{
105
+ TAG: "CategoryAdded",
106
+ categoryId: "c1",
107
+ name: "Electronics"
108
+ }]), {
109
+ TAG: "CategoryRenamed",
110
+ categoryId: "c1",
111
+ name: "Consumer Electronics"
112
+ }), "c1", {
113
+ categoryId: "c1",
114
+ name: "Consumer Electronics",
115
+ archived: false
116
+ }));
117
+ });
118
+
119
+ let Spec = include.Spec;
120
+
121
+ let describeWithId = include.describeWithId;
122
+
123
+ let todo = include.todo;
124
+
125
+ let whenEvents = include.whenEvents;
126
+
127
+ let thenStates = include.thenStates;
128
+
129
+ let thenStatesWithId = include.thenStatesWithId;
130
+
131
+ let thenAllStates = include.thenAllStates;
132
+
133
+ let thenState = include.thenState;
134
+
135
+ let thenNoState = include.thenNoState;
136
+
137
+ let thenThrow = include.thenThrow;
138
+
139
+ let thenFail = include.thenFail;
140
+
141
+ export {
142
+ CategoriesView,
143
+ CategoriesViewProjection,
144
+ Spec,
145
+ describe,
146
+ describeWithId,
147
+ todo,
148
+ test,
149
+ givenEvents,
150
+ whenEvent,
151
+ whenEvents,
152
+ thenStates,
153
+ thenStatesWithId,
154
+ thenAllStates,
155
+ thenState,
156
+ thenStateWithId,
157
+ thenNoState,
158
+ thenThrow,
159
+ thenFail,
160
+ }
161
+ /* stateSchema Not a pure module */
@@ -0,0 +1,106 @@
1
+ // Unit tests for Watch's event coalescing — the part Phase 12 added so a
2
+ // relocation's structural `unlink` survives a debounce window that also carries
3
+ // the trailing `add`/`change` of the same `mv`. The debounce uses real
4
+ // setTimeout(120ms), so these await past the window.
5
+
6
+ open JestGlobals
7
+
8
+ @val external setTimeout: (unit => unit, int) => unit = "setTimeout"
9
+ let delay = (ms: int): promise<unit> =>
10
+ Promise.make((resolve, _reject) => setTimeout(() => resolve(), ms))
11
+
12
+ describe("Watch.isStructuralSource", () => {
13
+ testPromise("an unlink of a .res source is structural", async () =>
14
+ expect(Watch.isStructuralSource(Unlink, "/p/src/Foo/Bar.res"))->toEqual(true)
15
+ )
16
+ testPromise("a generated .res.mjs unlink is NOT structural (ends in .mjs)", async () =>
17
+ expect(Watch.isStructuralSource(Unlink, "/p/src/Foo/Bar.res.mjs"))->toEqual(false)
18
+ )
19
+ testPromise("an add of a .res source is not structural (incremental handles it)", async () =>
20
+ expect(Watch.isStructuralSource(Add, "/p/src/Foo/Bar.res"))->toEqual(false)
21
+ )
22
+ testPromise("a change of a .res source is not structural", async () =>
23
+ expect(Watch.isStructuralSource(Change, "/p/src/Foo/Bar.res"))->toEqual(false)
24
+ )
25
+ })
26
+
27
+ describe("Watch.debounce coalescing", () => {
28
+ testPromise("a burst collapses to one call carrying the strongest event", async () => {
29
+ let calls = ref([])
30
+ let fire = Watch.debounce(40, (e, p) => calls := Array.concat(calls.contents, [(e, p)]))
31
+ // A move/ungroup: the new file is created, an edit lands, then the old file
32
+ // is unlinked — all inside one window. The unlink must win.
33
+ fire(Watch.Change, "/p/src/A.res")
34
+ fire(Watch.Add, "/p/src/new/A.res")
35
+ fire(Watch.Unlink, "/p/src/old/A.res")
36
+ await delay(120)
37
+ expect(calls.contents->Array.length)->toEqual(1)
38
+ let (e, p) = calls.contents->Array.getUnsafe(0)
39
+ expect(e)->toEqual(Watch.Unlink)
40
+ expect(p)->toEqual("/p/src/old/A.res")
41
+ })
42
+
43
+ testPromise("a later weaker event does not mask an earlier unlink", async () => {
44
+ let calls = ref([])
45
+ let fire = Watch.debounce(40, (e, _p) => calls := Array.concat(calls.contents, [e]))
46
+ fire(Watch.Unlink, "/p/src/old/A.res")
47
+ fire(Watch.Change, "/p/src/A.res")
48
+ await delay(120)
49
+ expect(calls.contents)->toEqual([Watch.Unlink])
50
+ })
51
+
52
+ testPromise("interleaved .res.mjs unlinks never surface; each distinct .res source does", async () => {
53
+ // The real directory-mv burst chokidar emits: source AND generated files
54
+ // unlink interleaved. Only `.res` sources are emitted (never a `.res.mjs`),
55
+ // and every *distinct* source is emitted — two moved modules must both drive
56
+ // a rebuild, not collapse to one representative.
57
+ let calls = ref([])
58
+ let fire = Watch.debounce(40, (e, p) => calls := Array.concat(calls.contents, [(e, p)]))
59
+ fire(Watch.Unlink, "/p/src/old/A.res")
60
+ fire(Watch.Unlink, "/p/src/old/A.res.mjs")
61
+ fire(Watch.Unlink, "/p/src/old/A_Behavior.res")
62
+ fire(Watch.Unlink, "/p/src/old/A_Behavior.res.mjs")
63
+ fire(Watch.Add, "/p/src/new/A.res")
64
+ fire(Watch.Add, "/p/src/new/A.res.mjs")
65
+ await delay(120)
66
+ let paths = calls.contents->Array.map(((_, p)) => p)
67
+ expect(calls.contents->Array.every(((e, p)) => Watch.isStructuralSource(e, p)))->toEqual(true)
68
+ expect(paths->Array.includes("/p/src/old/A.res"))->toEqual(true)
69
+ expect(paths->Array.includes("/p/src/old/A_Behavior.res"))->toEqual(true)
70
+ expect(paths->Array.some(p => p->String.endsWith(".mjs")))->toEqual(false)
71
+ expect(calls.contents->Array.length)->toEqual(2)
72
+ })
73
+
74
+ testPromise("a burst spanning two packages emits an unlink per package (A4.1)", async () => {
75
+ // Regression: the old single-`bestPath` debounce clean-rebuilt only one
76
+ // package on a multi-package burst, stranding the other with stale .res.mjs.
77
+ let calls = ref([])
78
+ let fire = Watch.debounce(40, (e, p) => calls := Array.concat(calls.contents, [(e, p)]))
79
+ fire(Watch.Unlink, "/pkgA/src/Foo.res")
80
+ fire(Watch.Unlink, "/pkgB/src/Bar.res")
81
+ await delay(120)
82
+ let paths = calls.contents->Array.map(((_, p)) => p)
83
+ expect(paths->Array.includes("/pkgA/src/Foo.res"))->toEqual(true)
84
+ expect(paths->Array.includes("/pkgB/src/Bar.res"))->toEqual(true)
85
+ expect(calls.contents->Array.length)->toEqual(2)
86
+ })
87
+
88
+ testPromise("a structural signal suppresses a co-occurring plain change", async () => {
89
+ // A structural rebuild does its own re-run, so a plain edit in the same
90
+ // window must not also emit a separate Change (which would double-run).
91
+ let calls = ref([])
92
+ let fire = Watch.debounce(40, (e, _p) => calls := Array.concat(calls.contents, [e]))
93
+ fire(Watch.Change, "/p/src/Edited.res")
94
+ fire(Watch.Unlink, "/p/src/old/Moved.res")
95
+ await delay(120)
96
+ expect(calls.contents)->toEqual([Watch.Unlink])
97
+ })
98
+
99
+ testPromise("a lone change stays a change (incremental re-run)", async () => {
100
+ let calls = ref([])
101
+ let fire = Watch.debounce(40, (e, _p) => calls := Array.concat(calls.contents, [e]))
102
+ fire(Watch.Change, "/p/src/A.res")
103
+ await delay(120)
104
+ expect(calls.contents)->toEqual([Watch.Change])
105
+ })
106
+ })