@reventlessdev/reventless-core 3.0.0-alpha.260 → 3.0.0-alpha.262

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 (208) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/package.json +10 -10
  3. package/src/Behavior.res +1 -4
  4. package/src/Message.res +17 -14
  5. package/src/Projection.res +51 -25
  6. package/src/RequestContext.res +8 -9
  7. package/src/ResourceAttribution.res +28 -14
  8. package/src/adapter/Adapter.res +48 -4
  9. package/src/adapter/EventLogProvisioning/EventLogProvisioning.res +5 -5
  10. package/src/adapter/Messaging/Messaging_Log_Backend.res +10 -7
  11. package/src/adapter/Messaging/Messaging_Log_Backend.res.mjs +2 -4
  12. package/src/adapter/Monitoring/Monitoring.res +16 -17
  13. package/src/adapter/QueryInterception/QueryInterception.res +0 -1
  14. package/src/adapter/Runtime/AggregateRuntime_Builder_Common.res +12 -5
  15. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +11 -7
  16. package/src/adapter/Runtime/PluginRuntime_Builder_Micro.res +1 -7
  17. package/src/adapter/Runtime/PluginRuntime_Builder_Single.res +1 -7
  18. package/src/adapter/RuntimeExtension/RuntimeExtension.res +0 -1
  19. package/src/adapter/Secrets/Secrets_Node_Backend.res +60 -0
  20. package/src/adapter/Secrets/Secrets_Node_Backend.res.mjs +69 -0
  21. package/src/admin/Platform_Admin.res +17 -6
  22. package/src/admin/Platform_AdminApi.res +14 -9
  23. package/src/admin/Platform_Admin_Callback.res +1 -1
  24. package/src/admin/Platform_BakedManifest.res +34 -34
  25. package/src/admin/Platform_ComponentDefinitionsApi.res +9 -21
  26. package/src/admin/Platform_PluginStructuresApi.res +15 -12
  27. package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res +1 -1
  28. package/src/components/Aggregate/Aggregate.res +13 -12
  29. package/src/components/Aggregate/Aggregate_Builder.res +22 -21
  30. package/src/components/Aggregate/Aggregate_Callback.res +91 -86
  31. package/src/components/Api/ApiNoApiHelpers.res +1 -3
  32. package/src/components/Api/Api_Ids.res +0 -1
  33. package/src/components/Api/Api_Naming.res +41 -18
  34. package/src/components/Api/GraphQL_FragmentGenerator.res +38 -51
  35. package/src/components/Api/GraphQL_SchemaInspector.res +5 -3
  36. package/src/components/Api/GraphQL_Stitcher.res +9 -10
  37. package/src/components/Api/MCP_SchemaGenerator.res +8 -12
  38. package/src/components/Api/OwnerScopeDiagnostics.res +1 -2
  39. package/src/components/Api/QueryDbListQuery.res +13 -6
  40. package/src/components/Api/SchemaType.res +28 -23
  41. package/src/components/Api/SuryToJsonSchema.res +9 -10
  42. package/src/components/AutomationSlice/AutomationSlice.res +4 -4
  43. package/src/components/AutomationSlice/AutomationSlice_Builder.res +15 -14
  44. package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -3
  45. package/src/components/Builder_Helpers.res +12 -23
  46. package/src/components/CommandGenerator/CommandGenerator.res +6 -1
  47. package/src/components/CommandGenerator/CommandGenerator_Builder.res +29 -21
  48. package/src/components/CommandGenerator/CommandGenerator_Callback.res +17 -21
  49. package/src/components/CommandTopic/CommandTopic.res +3 -3
  50. package/src/components/CommandTopic/CommandTopic_Builder.res +10 -10
  51. package/src/components/CommandTopic/CommandTopic_Callback.res +1 -4
  52. package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -2
  53. package/src/components/Counter/Counter_Builder.res +1 -6
  54. package/src/components/Counter/Counter_Callback.res +3 -3
  55. package/src/components/Dcb/Dcb_Builder.res +970 -956
  56. package/src/components/DcbEventLog/DcbEventLog.res +4 -4
  57. package/src/components/DcbEventLog/DcbEventLog_Builder.res +0 -1
  58. package/src/components/DcbEventLog/DcbEventLog_Operations.res +17 -18
  59. package/src/components/EventCollector/EventCollector_Builder.res +2 -1
  60. package/src/components/EventLog/EventLog.res +5 -1
  61. package/src/components/EventLog/EventLog_Operations.res +12 -8
  62. package/src/components/EventMapper/EventMapper.res +8 -10
  63. package/src/components/EventMapper/EventMapper_Builder.res +4 -1
  64. package/src/components/EventMapper/EventMapper_Callback.res +46 -31
  65. package/src/components/EventTopic/EventTopic.res +3 -3
  66. package/src/components/EventTopic/EventTopic_Builder.res +1 -1
  67. package/src/components/Extension/Extension.res +4 -1
  68. package/src/components/Extension/Extension_Operations.res +11 -3
  69. package/src/components/ExtensionPoint/ExtensionPoint.res +3 -3
  70. package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +9 -10
  71. package/src/components/ExtensionPoint/ExtensionPoint_Callback.res +2 -6
  72. package/src/components/ExtensionPoint/ExtensionPoint_Operations.res +13 -5
  73. package/src/components/Heartbeat/Heartbeat_Callback.res +3 -3
  74. package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +4 -4
  75. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +7 -10
  76. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice.res +4 -4
  77. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +15 -14
  78. package/src/components/QueryDb/QueryDb_Adapter.res +1 -3
  79. package/src/components/QueryDb/QueryDb_Operations.res +10 -10
  80. package/src/components/ReadModel/ReadModel.res +3 -3
  81. package/src/components/ReadModel/ReadModel_Builder.res +1 -2
  82. package/src/components/ReadModel/ReadModel_Callback.res +2 -1
  83. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +5 -2
  84. package/src/components/StateChangeSlice/StateChangeSlice.res +3 -3
  85. package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +23 -4
  86. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +22 -6
  87. package/src/components/StateTopic.res +1 -3
  88. package/src/components/StateViewSlice/StateViewSlice.res +4 -4
  89. package/src/components/StateViewSlice/StateViewSlice_Builder.res +79 -68
  90. package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
  91. package/src/components/Task/Task.res +3 -3
  92. package/src/components/Task/Task_Adapter.res +4 -1
  93. package/src/components/Task/Task_Builder.res +24 -21
  94. package/src/plugin/api/PluginBaseFragment.res +9 -3
  95. package/src/plugin/component/Capability_Inference.res +8 -10
  96. package/src/plugin/component/Plugin.res +6 -6
  97. package/src/plugin/component/Plugin_Builder.res +54 -50
  98. package/src/plugin/component/Plugin_Callback.res +10 -6
  99. package/src/plugin/component/Plugin_Helpers.res +264 -230
  100. package/src/plugin/component/Plugin_Structure.res +370 -324
  101. package/src/plugin/component/Plugin_SubscriptionSchema.res +8 -6
  102. package/src/plugin/component/SchemaWalker.res +11 -12
  103. package/src/plugin/connect/PluginConnectExtension_Mapping.res +45 -50
  104. package/src/plugin/lifecycle/PluginBehavior.res +3 -4
  105. package/src/plugin/lifecycle/PluginSpec.res +1 -2
  106. package/src/plugin/lifecycle/PluginsProjection.res +16 -5
  107. package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
  108. package/src/util/CommandPublisher.res +9 -2
  109. package/src/util/CsvStream.res +3 -3
  110. package/src/util/Interstack.res +7 -15
  111. package/src/util/Interstack.resi +3 -3
  112. package/src/util/LogFormat.res +5 -9
  113. package/src/util/Logger.res +1 -2
  114. package/src/util/Lru.res +2 -1
  115. package/src/util/Lru.resi +2 -3
  116. package/src/util/ScheduleOps.res +24 -27
  117. package/src/util/StorePrefixCollision.res +6 -9
  118. package/src/util/Util_Promise.res +4 -1
  119. package/src/util/Util_Promise.res.mjs +1 -1
  120. package/tests/IdentityTest.res +166 -113
  121. package/tests/ResourceAttributionTest.res +28 -25
  122. package/tests/Secrets_Node_BackendTest.res +141 -0
  123. package/tests/Secrets_Node_BackendTest.res.mjs +152 -0
  124. package/tests/StorePrefixCollisionTest.res +2 -1
  125. package/tests/TaskBucketNamingTest.res +3 -2
  126. package/tests/adapter/EventLogProvisioningTest.res +51 -48
  127. package/tests/adapter/MonitoringTest.res +81 -38
  128. package/tests/adapter/QueryInterceptionTest.res +2 -2
  129. package/tests/adapter/RefusalVocabularyTest.res +31 -23
  130. package/tests/adapter/RuntimeExtensionHookReachTest.res +9 -8
  131. package/tests/adapter/RuntimeExtensionTest.res +5 -3
  132. package/tests/admin/AdminApiSchemaDriftTest.res +22 -16
  133. package/tests/admin/Platform_Admin_StructureTest.res +22 -14
  134. package/tests/admin/Platform_Admin_StructureTest.res.mjs +4 -3
  135. package/tests/admin/Platform_BakedManifestTest.res +31 -35
  136. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +18 -15
  137. package/tests/admin/Platform_PluginStructuresApiTest.res +5 -3
  138. package/tests/admin/Platform_UIFragmentsApiTest.res +6 -3
  139. package/tests/aggregate/AggregateCacheTest.res +10 -12
  140. package/tests/aggregate/AggregateCallbackTest.res +75 -46
  141. package/tests/aggregate/AggregateCausationTest.res +19 -20
  142. package/tests/aggregate/AggregateConflictTest.res +41 -19
  143. package/tests/aggregate/AggregateFixtures.res +15 -5
  144. package/tests/aggregate/AggregateRejectionTest.res +97 -80
  145. package/tests/aggregate/AggregateSnapshotTest.res +44 -16
  146. package/tests/api/Api_IdsTest.res +10 -4
  147. package/tests/api/BrandedMarkerCompositionTest.res +48 -36
  148. package/tests/api/GeolocationSemanticTest.res +3 -3
  149. package/tests/api/GraphQL_FragmentGeneratorTest.res +66 -54
  150. package/tests/api/OwnerScopeDiagnosticsTest.res +31 -27
  151. package/tests/api/StorageRefFieldsTest.res +30 -18
  152. package/tests/api/SuryToJsonSchemaTest.res +1315 -1014
  153. package/tests/api/TaggedUnionSchemaTest.res +17 -24
  154. package/tests/commandgenerator/CommandGeneratorCallbackTest.res +235 -179
  155. package/tests/commandgenerator/CommandGeneratorFixtures.res +8 -13
  156. package/tests/commandgenerator/OwnerStampingTest.res +91 -76
  157. package/tests/commandtopic/CommandOutcomeHookTest.res +198 -155
  158. package/tests/commandtopic/CommandTopicCallbackFixtures.res +5 -4
  159. package/tests/commandtopic/CommandTopicCallbackTest.res +61 -43
  160. package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +70 -55
  161. package/tests/counter/CounterCallbackTest.res +40 -34
  162. package/tests/counter/CounterFixtures.res +4 -4
  163. package/tests/dcb/DcbDecodeTest.res +115 -84
  164. package/tests/dcb/DcbEventLogOperationsTest.res +194 -143
  165. package/tests/dcb/DcbEventLogSchemaTest.res +13 -13
  166. package/tests/dcb/DcbFixtures.res +4 -5
  167. package/tests/dcb/DcbScopeInferenceTest.res +173 -114
  168. package/tests/dcb/DcbStateChangeSliceTest.res +391 -262
  169. package/tests/dcb/DcbTagTest.res +22 -11
  170. package/tests/dcb/DcbValidationTest.res +288 -218
  171. package/tests/eventlog/EventLogFixtures.res +4 -4
  172. package/tests/eventlog/EventLogOperationsTest.res +102 -67
  173. package/tests/eventlog/EventLogRetryTest.res +104 -65
  174. package/tests/eventmapper/EventMapperCallbackTest.res +118 -83
  175. package/tests/eventmapper/EventMapperFixtures.res +15 -23
  176. package/tests/eventmapper/EventMapperTest.res +54 -38
  177. package/tests/eventtopic/EventTopicFixtures.res +3 -4
  178. package/tests/eventtopic/EventTopicOperationsTest.res +41 -29
  179. package/tests/extensionpoint/ExtensionPointCallbackTest.res +58 -45
  180. package/tests/extensionpoint/ExtensionPointFixtures.res +16 -20
  181. package/tests/extensionpoint/ExtensionPointOperationsTest.res +70 -49
  182. package/tests/extensionpoint/PluginDisconnectScheduleTest.res +2 -1
  183. package/tests/logger/LogFormatTest.res +104 -82
  184. package/tests/logger/LoggerLazyTest.res +21 -12
  185. package/tests/message/MessageTest.res +96 -93
  186. package/tests/message/MetaEnvelopeTest.res +26 -30
  187. package/tests/plugin/HeartbeatDisconnectGraceTest.res +31 -25
  188. package/tests/plugin/ManifestVisibilityTest.res +20 -12
  189. package/tests/plugin/PluginDefinitionScalars.res +1 -2
  190. package/tests/plugin/PluginLifecycleCorpusTest.res +15 -12
  191. package/tests/plugin/PluginSpecExperiment.res +0 -1
  192. package/tests/plugin/PluginStructureTest.res +1529 -1045
  193. package/tests/plugin/Plugin_EventQuerySchemaTest.res +21 -10
  194. package/tests/plugin/SchemaWalkerTest.res +116 -70
  195. package/tests/plugin/StateChange/PsReserveStock.res +4 -4
  196. package/tests/plugin/StateView/PsAnnotatedView.res +4 -4
  197. package/tests/plugin/StateView/PsCustomersView.res +4 -4
  198. package/tests/plugin/StateView/PsShipmentsView.res +3 -2
  199. package/tests/projection/LifecycleTrailTest.res +1 -4
  200. package/tests/projection/ProjectionOptimizeTest.res +13 -9
  201. package/tests/querydb/QueryDbFixtures.res +0 -1
  202. package/tests/querydb/QueryDbOperationsTest.res +99 -64
  203. package/tests/querydb/TaggedUnionStampTest.res +10 -8
  204. package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res +50 -35
  205. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +4 -10
  206. package/tests/util/CommandPublisherTest.res +0 -1
  207. package/tests/util/CsvStreamTest.res +3 -3
  208. package/tests/util/LruTest.res +84 -56
@@ -3,19 +3,18 @@
3
3
 
4
4
  open JestGlobals
5
5
 
6
-
7
6
  module AggSpec = {
8
7
  module Id = Reventless.Id.StringPure
9
8
  let name = "ConflictTestAggregate"
10
9
 
11
10
  @schema
12
- type command = | Create({name: string})
11
+ type command = Create({name: string})
13
12
 
14
13
  @schema
15
- type event = | Created({name: string})
14
+ type event = Created({name: string})
16
15
 
17
16
  @schema
18
- type error = | AlreadyExists
17
+ type error = AlreadyExists
19
18
 
20
19
  let moduleUrl: string = %raw(`import.meta.url`)
21
20
  }
@@ -37,14 +36,21 @@ module TestBehavior = {
37
36
  | Created({name}) => {name: name}
38
37
  }
39
38
 
40
- let decide = (_state: state, command: AggSpec.command): result<array<AggSpec.event>, AggSpec.error> =>
39
+ let decide = (_state: state, command: AggSpec.command): result<
40
+ array<AggSpec.event>,
41
+ AggSpec.error,
42
+ > =>
41
43
  switch command {
42
44
  | Create({name}) => Ok([AggSpec.Created({name: name})])
43
45
  }
44
46
  }
45
47
 
46
48
  type mockEL = {
47
- appendFn: (int, string, array<Message.event'<string, AggSpec.event>>) => promise<result<unit, EventLog.appendError>>,
49
+ appendFn: (
50
+ int,
51
+ string,
52
+ array<Message.event'<string, AggSpec.event>>,
53
+ ) => promise<result<unit, EventLog.appendError>>,
48
54
  replayFn: string => promise<array<AggSpec.event>>,
49
55
  replayStreamFn: string => Stream.t<AggSpec.event, string, unit>,
50
56
  getAll: unit => array<Message.event'<string, AggSpec.event>>,
@@ -67,7 +73,7 @@ let makeMockEL = (): mockEL => {
67
73
  Error(EventLog.Conflict)
68
74
  } else {
69
75
  storedRef := storedRef.contents->Array.concat(newEvents)
70
- Ok(())
76
+ Ok()
71
77
  }
72
78
  }
73
79
 
@@ -117,10 +123,18 @@ module TestOps = {
117
123
  type event = AggSpec.event
118
124
  }
119
125
  type operations = {
120
- append: (int, string, array<Message.event'<string, AggSpec.event>>) => promise<result<unit, EventLog.appendError>>,
126
+ append: (
127
+ int,
128
+ string,
129
+ array<Message.event'<string, AggSpec.event>>,
130
+ ) => promise<result<unit, EventLog.appendError>>,
121
131
  replay: string => promise<array<AggSpec.event>>,
122
132
  replayStream: (string, ~fromSeq: int=?) => Stream.t<AggSpec.event, string, unit>,
123
- appendStream: (int, string, Stream.t<AggSpec.event, string, unit>) => Effect.t<unit, string, unit>,
133
+ appendStream: (
134
+ int,
135
+ string,
136
+ Stream.t<AggSpec.event, string, unit>,
137
+ ) => Effect.t<unit, string, unit>,
124
138
  latestSnapshot: string => promise<result<option<EventLog.snapshot>, string>>,
125
139
  writeSnapshot: (string, EventLog.snapshot) => promise<result<unit, string>>,
126
140
  }
@@ -169,7 +183,9 @@ describe("Aggregate_Callback — conflict retry:", () => {
169
183
  mock.failNextAppendsWithConflict := 1
170
184
  let results = await Stream.fromIterable([
171
185
  makeTopicItem("ref-1", AggSpec.Create({name: "Widget"})),
172
- ])->TestHandler.handleCommands->Effect.runPromise
186
+ ])
187
+ ->TestHandler.handleCommands
188
+ ->Effect.runPromise
173
189
  expect(results)->toEqual([Ok("ref-1")])
174
190
  // 1 initial append (conflict) + 1 retry append (success) = 2
175
191
  expect(mock.appendCallCount.contents)->toBe(2)
@@ -177,9 +193,9 @@ describe("Aggregate_Callback — conflict retry:", () => {
177
193
 
178
194
  testPromise("conflict triggers re-replay", async () => {
179
195
  mock.failNextAppendsWithConflict := 1
180
- let _ = await Stream.fromIterable([
181
- makeTopicItem("ref-1", AggSpec.Create({name: "Widget"})),
182
- ])->TestHandler.handleCommands->Effect.runPromise
196
+ let _ = await Stream.fromIterable([makeTopicItem("ref-1", AggSpec.Create({name: "Widget"}))])
197
+ ->TestHandler.handleCommands
198
+ ->Effect.runPromise
183
199
  // 1 initial replay + 1 retry replay = 2
184
200
  expect(mock.replayCallCount.contents)->toBe(2)
185
201
  })
@@ -188,7 +204,9 @@ describe("Aggregate_Callback — conflict retry:", () => {
188
204
  mock.failNextAppendsWithConflict := 2
189
205
  let results = await Stream.fromIterable([
190
206
  makeTopicItem("ref-1", AggSpec.Create({name: "Widget"})),
191
- ])->TestHandler.handleCommands->Effect.runPromise
207
+ ])
208
+ ->TestHandler.handleCommands
209
+ ->Effect.runPromise
192
210
  expect(results)->toEqual([Ok("ref-1")])
193
211
  expect(mock.appendCallCount.contents)->toBe(3)
194
212
  })
@@ -198,23 +216,27 @@ describe("Aggregate_Callback — conflict retry:", () => {
198
216
  mock.failNextAppendsWithConflict := 4
199
217
  let results = await Stream.fromIterable([
200
218
  makeTopicItem("ref-1", AggSpec.Create({name: "Widget"})),
201
- ])->TestHandler.handleCommands->Effect.runPromise
219
+ ])
220
+ ->TestHandler.handleCommands
221
+ ->Effect.runPromise
202
222
  expect(results)->toEqual([Error("concurrent modification (3 retries exhausted): conflict")])
203
223
  expect(mock.appendCallCount.contents)->toBe(4)
204
224
  })
205
225
 
206
226
  testPromise("successful append after conflict stores event", async () => {
207
227
  mock.failNextAppendsWithConflict := 1
208
- let _ = await Stream.fromIterable([
209
- makeTopicItem("ref-1", AggSpec.Create({name: "Widget"})),
210
- ])->TestHandler.handleCommands->Effect.runPromise
228
+ let _ = await Stream.fromIterable([makeTopicItem("ref-1", AggSpec.Create({name: "Widget"}))])
229
+ ->TestHandler.handleCommands
230
+ ->Effect.runPromise
211
231
  expect(mock.getAll()->Array.length)->toBe(1)
212
232
  })
213
233
 
214
234
  testPromise("no conflict does not retry", async () => {
215
235
  let results = await Stream.fromIterable([
216
236
  makeTopicItem("ref-1", AggSpec.Create({name: "Widget"})),
217
- ])->TestHandler.handleCommands->Effect.runPromise
237
+ ])
238
+ ->TestHandler.handleCommands
239
+ ->Effect.runPromise
218
240
  expect(results)->toEqual([Ok("ref-1")])
219
241
  expect(mock.appendCallCount.contents)->toBe(1)
220
242
  expect(mock.replayCallCount.contents)->toBe(1)
@@ -1,4 +1,3 @@
1
-
2
1
  // ─────────────────────────────────────────────────────────────
3
2
  // Aggregate spec
4
3
  // ─────────────────────────────────────────────────────────────
@@ -49,7 +48,10 @@ module TestBehavior = {
49
48
  | AggSpec.Renamed({newName}) => Created({name: newName})
50
49
  }
51
50
 
52
- let decide = (state: state, command: AggSpec.command): result<array<AggSpec.event>, AggSpec.error> =>
51
+ let decide = (state: state, command: AggSpec.command): result<
52
+ array<AggSpec.event>,
53
+ AggSpec.error,
54
+ > =>
53
55
  switch (state, command) {
54
56
  | (NotCreated, Create({name})) => Ok([AggSpec.Created({name: name})])
55
57
  | (NotCreated, Rename(_)) => Error(NotFound)
@@ -63,7 +65,11 @@ module TestBehavior = {
63
65
  // ─────────────────────────────────────────────────────────────
64
66
 
65
67
  type mockEL = {
66
- appendFn: (int, string, array<Message.event'<string, AggSpec.event>>) => promise<result<unit, EventLog.appendError>>,
68
+ appendFn: (
69
+ int,
70
+ string,
71
+ array<Message.event'<string, AggSpec.event>>,
72
+ ) => promise<result<unit, EventLog.appendError>>,
67
73
  replayFn: string => promise<array<AggSpec.event>>,
68
74
  replayStreamFn: string => Stream.t<AggSpec.event, string, unit>,
69
75
  getAll: unit => array<Message.event'<string, AggSpec.event>>,
@@ -81,7 +87,7 @@ let makeMockEL = (): mockEL => {
81
87
  Error(EventLog.StorageFailure("append failed"))
82
88
  } else {
83
89
  storedRef := storedRef.contents->Array.concat(newEvents)
84
- Ok(())
90
+ Ok()
85
91
  }
86
92
 
87
93
  let replayFn = async id =>
@@ -135,7 +141,11 @@ module TestOps = {
135
141
  ) => promise<result<unit, EventLog.appendError>>,
136
142
  replay: string => promise<array<AggSpec.event>>,
137
143
  replayStream: (string, ~fromSeq: int=?) => Stream.t<AggSpec.event, string, unit>,
138
- appendStream: (int, string, Stream.t<AggSpec.event, string, unit>) => Effect.t<unit, string, unit>,
144
+ appendStream: (
145
+ int,
146
+ string,
147
+ Stream.t<AggSpec.event, string, unit>,
148
+ ) => Effect.t<unit, string, unit>,
139
149
  latestSnapshot: string => promise<result<option<EventLog.snapshot>, string>>,
140
150
  writeSnapshot: (string, EventLog.snapshot) => promise<result<unit, string>>,
141
151
  }
@@ -34,109 +34,126 @@ let uninstallChannels = () => {
34
34
 
35
35
  describe("Aggregate decide-rejection propagation:", () => {
36
36
  describe("single rejected command", () => {
37
- testPromise("Create on existing aggregate reports Rejected with errorCode", async () => {
38
- // Seed: aggregate already created → second Create rejects with AlreadyExists.
39
- let _ =
40
- await Stream.fromIterable([
37
+ testPromise(
38
+ "Create on existing aggregate reports Rejected with errorCode",
39
+ async () => {
40
+ // Seed: aggregate already created → second Create rejects with AlreadyExists.
41
+ let _ = await Stream.fromIterable([
41
42
  makeTopicItem("ref-seed", AggSpec.Create({name: "Widget"})),
42
- ])->TestHandler.handleCommands->Effect.runPromise
43
+ ])
44
+ ->TestHandler.handleCommands
45
+ ->Effect.runPromise
43
46
 
44
- installChannels()
45
- let results =
46
- await Stream.fromIterable([
47
+ installChannels()
48
+ let results = await Stream.fromIterable([
47
49
  makeTopicItem("ref-1", AggSpec.Create({name: "Widget"})),
48
- ])->TestHandler.handleCommands->Effect.runPromise
49
- uninstallChannels()
50
+ ])
51
+ ->TestHandler.handleCommands
52
+ ->Effect.runPromise
53
+ uninstallChannels()
50
54
 
51
- // Producer sees Rejected on the channel; SQS sees Ok (delete — don't redeliver).
52
- expect(results)->toEqual([Ok("ref-1")])
53
- expect(capturedRejections.contents->Array.length)->toBe(1)
54
- let (ref, info) = capturedRejections.contents->Array.getUnsafe(0)
55
- expect(ref)->toEqual("ref-1")
56
- expect(info.errorCode)->toEqual("AlreadyExists")
57
- // No event was appended.
58
- expect(mock.getAll()->Array.length)->toBe(1)
59
- })
55
+ // Producer sees Rejected on the channel; SQS sees Ok (delete — don't redeliver).
56
+ expect(results)->toEqual([Ok("ref-1")])
57
+ expect(capturedRejections.contents->Array.length)->toBe(1)
58
+ let (ref, info) = capturedRejections.contents->Array.getUnsafe(0)
59
+ expect(ref)->toEqual("ref-1")
60
+ expect(info.errorCode)->toEqual("AlreadyExists")
61
+ // No event was appended.
62
+ expect(mock.getAll()->Array.length)->toBe(1)
63
+ },
64
+ )
60
65
  })
61
66
 
62
67
  describe("mixed batch", () => {
63
- testPromise("rejected command does not cancel surviving commands", async () => {
64
- // Seed.
65
- let _ =
66
- await Stream.fromIterable([
68
+ testPromise(
69
+ "rejected command does not cancel surviving commands",
70
+ async () => {
71
+ // Seed.
72
+ let _ = await Stream.fromIterable([
67
73
  makeTopicItem(~aggId="agg-1", "ref-seed", AggSpec.Create({name: "Widget"})),
68
- ])->TestHandler.handleCommands->Effect.runPromise
74
+ ])
75
+ ->TestHandler.handleCommands
76
+ ->Effect.runPromise
69
77
 
70
- installChannels()
71
- // Batch on the same aggregate: Rename (ok) → Create (rejects) → Rename (ok)
72
- let results =
73
- await Stream.fromIterable([
78
+ installChannels()
79
+ // Batch on the same aggregate: Rename (ok) → Create (rejects) → Rename (ok)
80
+ let results = await Stream.fromIterable([
74
81
  makeTopicItem(~aggId="agg-1", "ref-1", AggSpec.Rename({newName: "First"})),
75
82
  makeTopicItem(~aggId="agg-1", "ref-2", AggSpec.Create({name: "Boom"})),
76
83
  makeTopicItem(~aggId="agg-1", "ref-3", AggSpec.Rename({newName: "Last"})),
77
- ])->TestHandler.handleCommands->Effect.runPromise
78
- uninstallChannels()
84
+ ])
85
+ ->TestHandler.handleCommands
86
+ ->Effect.runPromise
87
+ uninstallChannels()
79
88
 
80
- // All three references map to Ok (rejected ones still Ok — SQS deletes).
81
- expect(results->Array.length)->toBe(3)
82
- results->Array.forEach(r =>
83
- switch r {
84
- | Ok(_) => ()
85
- | Error(_) => expect("expected all Ok")->toEqual("got Error")
86
- }
87
- )
88
- // Two Renamed events appended (the rejected Create produced none).
89
- // Seed = 1, plus 2 Renamed = 3 stored.
90
- expect(mock.getAll()->Array.length)->toBe(3)
91
- // ref-2 reported as Rejected with AlreadyExists.
92
- let rejected = capturedRejections.contents
93
- expect(rejected->Array.length)->toBe(1)
94
- let (rRef, rInfo) = rejected->Array.getUnsafe(0)
95
- expect(rRef)->toEqual("ref-2")
96
- expect(rInfo.errorCode)->toEqual("AlreadyExists")
97
- // ref-1 and ref-3 reported as Accepted (eventCount > 0 against the appended batch).
98
- let accepted = capturedAccepts.contents
99
- expect(accepted->Array.length)->toBe(2)
100
- })
89
+ // All three references map to Ok (rejected ones still Ok — SQS deletes).
90
+ expect(results->Array.length)->toBe(3)
91
+ results->Array.forEach(
92
+ r =>
93
+ switch r {
94
+ | Ok(_) => ()
95
+ | Error(_) => expect("expected all Ok")->toEqual("got Error")
96
+ },
97
+ )
98
+ // Two Renamed events appended (the rejected Create produced none).
99
+ // Seed = 1, plus 2 Renamed = 3 stored.
100
+ expect(mock.getAll()->Array.length)->toBe(3)
101
+ // ref-2 reported as Rejected with AlreadyExists.
102
+ let rejected = capturedRejections.contents
103
+ expect(rejected->Array.length)->toBe(1)
104
+ let (rRef, rInfo) = rejected->Array.getUnsafe(0)
105
+ expect(rRef)->toEqual("ref-2")
106
+ expect(rInfo.errorCode)->toEqual("AlreadyExists")
107
+ // ref-1 and ref-3 reported as Accepted (eventCount > 0 against the appended batch).
108
+ let accepted = capturedAccepts.contents
109
+ expect(accepted->Array.length)->toBe(2)
110
+ },
111
+ )
101
112
  })
102
113
 
103
114
  describe("all-reject batch", () => {
104
- testPromise("3 rejecting commands → no append, all reported Rejected, no Accepted", async () => {
105
- // No seed → empty history. Rename on a NotCreated aggregate rejects with NotFound.
106
- installChannels()
107
- let results =
108
- await Stream.fromIterable([
115
+ testPromise(
116
+ "3 rejecting commands → no append, all reported Rejected, no Accepted",
117
+ async () => {
118
+ // No seed → empty history. Rename on a NotCreated aggregate rejects with NotFound.
119
+ installChannels()
120
+ let results = await Stream.fromIterable([
109
121
  makeTopicItem(~aggId="agg-x", "ref-1", AggSpec.Rename({newName: "A"})),
110
122
  makeTopicItem(~aggId="agg-x", "ref-2", AggSpec.Rename({newName: "B"})),
111
123
  makeTopicItem(~aggId="agg-x", "ref-3", AggSpec.Rename({newName: "C"})),
112
- ])->TestHandler.handleCommands->Effect.runPromise
113
- uninstallChannels()
124
+ ])
125
+ ->TestHandler.handleCommands
126
+ ->Effect.runPromise
127
+ uninstallChannels()
114
128
 
115
- expect(results)->toEqual([Ok("ref-1"), Ok("ref-2"), Ok("ref-3")])
116
- // No events appended.
117
- expect(mock.getAll()->Array.length)->toBe(0)
118
- // All three reported as Rejected.
119
- expect(capturedRejections.contents->Array.length)->toBe(3)
120
- capturedRejections.contents->Array.forEach(((_ref, info)) =>
121
- expect(info.errorCode)->toEqual("NotFound")
122
- )
123
- // No Accepted callbacks fired (when nothing is appended and nothing is OK).
124
- expect(capturedAccepts.contents->Array.length)->toBe(0)
125
- })
129
+ expect(results)->toEqual([Ok("ref-1"), Ok("ref-2"), Ok("ref-3")])
130
+ // No events appended.
131
+ expect(mock.getAll()->Array.length)->toBe(0)
132
+ // All three reported as Rejected.
133
+ expect(capturedRejections.contents->Array.length)->toBe(3)
134
+ capturedRejections.contents->Array.forEach(
135
+ ((_ref, info)) => expect(info.errorCode)->toEqual("NotFound"),
136
+ )
137
+ // No Accepted callbacks fired (when nothing is appended and nothing is OK).
138
+ expect(capturedAccepts.contents->Array.length)->toBe(0)
139
+ },
140
+ )
126
141
  })
127
142
 
128
143
  describe("payload-less variant errors", () => {
129
- testPromise("errorDetail is empty string for unit-payload variants", async () => {
130
- // AggSpec.error variants are all payload-less (NotFound | AlreadyExists).
131
- installChannels()
132
- let _ =
133
- await Stream.fromIterable([
134
- makeTopicItem("ref-1", AggSpec.Rename({newName: "X"})),
135
- ])->TestHandler.handleCommands->Effect.runPromise
136
- uninstallChannels()
144
+ testPromise(
145
+ "errorDetail is empty string for unit-payload variants",
146
+ async () => {
147
+ // AggSpec.error variants are all payload-less (NotFound | AlreadyExists).
148
+ installChannels()
149
+ let _ = await Stream.fromIterable([makeTopicItem("ref-1", AggSpec.Rename({newName: "X"}))])
150
+ ->TestHandler.handleCommands
151
+ ->Effect.runPromise
152
+ uninstallChannels()
137
153
 
138
- let (_ref, info) = capturedRejections.contents->Array.getUnsafe(0)
139
- expect(info.errorDetail)->toEqual("")
140
- })
154
+ let (_ref, info) = capturedRejections.contents->Array.getUnsafe(0)
155
+ expect(info.errorDetail)->toEqual("")
156
+ },
157
+ )
141
158
  })
142
159
  })
@@ -12,7 +12,6 @@
12
12
 
13
13
  open JestGlobals
14
14
 
15
-
16
15
  module AggSpec = {
17
16
  module Id = Reventless.Id.StringPure
18
17
  let name = "SnapshotTestAggregate"
@@ -24,7 +23,7 @@ module AggSpec = {
24
23
  type event = Added({item: string})
25
24
 
26
25
  @schema
27
- type error = | Never
26
+ type error = Never
28
27
 
29
28
  let moduleUrl: string = %raw(`import.meta.url`)
30
29
  }
@@ -161,7 +160,11 @@ let mock = makeMockEL()
161
160
 
162
161
  module OuterEventLog = EventLog
163
162
 
164
- module MakeOps = (B: {let name: string}) => {
163
+ module MakeOps = (
164
+ B: {
165
+ let name: string
166
+ },
167
+ ) => {
165
168
  module Spec = AggSpec
166
169
  module EventLog = {
167
170
  module Spec = {
@@ -178,11 +181,11 @@ module MakeOps = (B: {let name: string}) => {
178
181
  ) => promise<result<unit, EventLog.appendError>>,
179
182
  replay: string => promise<array<AggSpec.event>>,
180
183
  replayStream: (string, ~fromSeq: int=?) => Stream.t<AggSpec.event, string, unit>,
181
- appendStream: (int, string, Stream.t<AggSpec.event, string, unit>) => Effect.t<
182
- unit,
184
+ appendStream: (
185
+ int,
183
186
  string,
184
- unit,
185
- >,
187
+ Stream.t<AggSpec.event, string, unit>,
188
+ ) => Effect.t<unit, string, unit>,
186
189
  latestSnapshot: string => promise<result<option<EventLog.snapshot>, string>>,
187
190
  writeSnapshot: (string, EventLog.snapshot) => promise<result<unit, string>>,
188
191
  }
@@ -199,7 +202,9 @@ module MakeOps = (B: {let name: string}) => {
199
202
  }
200
203
  }
201
204
 
202
- module TestOps = MakeOps({let name = "SnapshotMockEventLog"})
205
+ module TestOps = MakeOps({
206
+ let name = "SnapshotMockEventLog"
207
+ })
203
208
  module TestHandler = Aggregate_Callback.Make(AggSpec, TestBehavior, TestOps)
204
209
 
205
210
  // A second behavior with snapshots DISABLED, sharing the same mock, to prove
@@ -225,7 +230,9 @@ module PlainBehavior = {
225
230
  }
226
231
  }
227
232
 
228
- module PlainOps = MakeOps({let name = "PlainMockEventLog"})
233
+ module PlainOps = MakeOps({
234
+ let name = "PlainMockEventLog"
235
+ })
229
236
  module PlainHandler = Aggregate_Callback.Make(AggSpec, PlainBehavior, PlainOps)
230
237
 
231
238
  let makeItem = (~id="agg-1", reference, command): CommandTopic.topicItem<
@@ -253,7 +260,10 @@ describe("Aggregate_Callback — snapshots:", () => {
253
260
  testPromise("writes a snapshot each time an interval boundary is crossed", async () => {
254
261
  // 25 single-event commands → boundaries at 10 and 20 (not 25).
255
262
  for i in 1 to 25 {
256
- let _ = await run(~handler=TestHandler.handleCommands, AggSpec.Add({item: `i${i->Int.toString}`}))
263
+ let _ = await run(
264
+ ~handler=TestHandler.handleCommands,
265
+ AggSpec.Add({item: `i${i->Int.toString}`}),
266
+ )
257
267
  }
258
268
  let _ = await tick()
259
269
  expect(mock.snapshotWriteCount.contents)->toBe(2)
@@ -266,7 +276,10 @@ describe("Aggregate_Callback — snapshots:", () => {
266
276
 
267
277
  testPromise("cold replay seeds from the snapshot and reads only the delta", async () => {
268
278
  for i in 1 to 12 {
269
- let _ = await run(~handler=TestHandler.handleCommands, AggSpec.Add({item: `i${i->Int.toString}`}))
279
+ let _ = await run(
280
+ ~handler=TestHandler.handleCommands,
281
+ AggSpec.Add({item: `i${i->Int.toString}`}),
282
+ )
270
283
  }
271
284
  let _ = await tick()
272
285
  // A snapshot exists at seq 10. Evict the in-process cache to force a cold read.
@@ -282,7 +295,10 @@ describe("Aggregate_Callback — snapshots:", () => {
282
295
 
283
296
  testPromise("state seeded from snapshot equals a full-replay fold", async () => {
284
297
  for i in 1 to 15 {
285
- let _ = await run(~handler=TestHandler.handleCommands, AggSpec.Add({item: `x${i->Int.toString}`}))
298
+ let _ = await run(
299
+ ~handler=TestHandler.handleCommands,
300
+ AggSpec.Add({item: `x${i->Int.toString}`}),
301
+ )
286
302
  }
287
303
  let _ = await tick()
288
304
  // Snapshot-seeded cold read (snapshot at seq 10, delta 10..15).
@@ -292,7 +308,10 @@ describe("Aggregate_Callback — snapshots:", () => {
292
308
  // Force a boundary to capture the seeded state as a fresh snapshot: add up to
293
309
  // seq 20 and compare its item count to the true event count.
294
310
  for i in 16 to 20 {
295
- let _ = await run(~handler=TestHandler.handleCommands, AggSpec.Add({item: `x${i->Int.toString}`}))
311
+ let _ = await run(
312
+ ~handler=TestHandler.handleCommands,
313
+ AggSpec.Add({item: `x${i->Int.toString}`}),
314
+ )
296
315
  }
297
316
  let _ = await tick()
298
317
  switch mock.getSnapshot("agg-1") {
@@ -312,7 +331,10 @@ describe("Aggregate_Callback — snapshots:", () => {
312
331
 
313
332
  testPromise("schema-drift snapshot is ignored → full replay from 0", async () => {
314
333
  for i in 1 to 12 {
315
- let _ = await run(~handler=TestHandler.handleCommands, AggSpec.Add({item: `i${i->Int.toString}`}))
334
+ let _ = await run(
335
+ ~handler=TestHandler.handleCommands,
336
+ AggSpec.Add({item: `i${i->Int.toString}`}),
337
+ )
316
338
  }
317
339
  let _ = await tick()
318
340
  // Corrupt the stored snapshot's hash to simulate a state-shape change.
@@ -329,7 +351,10 @@ describe("Aggregate_Callback — snapshots:", () => {
329
351
 
330
352
  testPromise("snapshot write failure does not fail the command", async () => {
331
353
  for i in 1 to 9 {
332
- let _ = await run(~handler=TestHandler.handleCommands, AggSpec.Add({item: `i${i->Int.toString}`}))
354
+ let _ = await run(
355
+ ~handler=TestHandler.handleCommands,
356
+ AggSpec.Add({item: `i${i->Int.toString}`}),
357
+ )
333
358
  }
334
359
  // The 10th append crosses the boundary; make its snapshot write fail.
335
360
  mock.failNextWrite := true
@@ -344,7 +369,10 @@ describe("Aggregate_Callback — snapshots:", () => {
344
369
 
345
370
  testPromise("disabled aggregate does zero snapshot reads and writes", async () => {
346
371
  for i in 1 to 12 {
347
- let _ = await run(~handler=PlainHandler.handleCommands, AggSpec.Add({item: `i${i->Int.toString}`}))
372
+ let _ = await run(
373
+ ~handler=PlainHandler.handleCommands,
374
+ AggSpec.Add({item: `i${i->Int.toString}`}),
375
+ )
348
376
  }
349
377
  let _ = await tick()
350
378
  PlainHandler.resetCache()
@@ -40,8 +40,9 @@ describe("Api_Ids.encode / decode", () => {
40
40
 
41
41
  describe("Api_Ids.alternateKey — the fallback a door tries on a miss", () => {
42
42
  testSync("a global id offers the storage key inside it", () =>
43
- expect(Api_Ids.alternateKey(Api_Ids.encode(~typeName="Catalog_Product", ~localId="p-1")))
44
- ->toEqual(Some("p-1"))
43
+ expect(
44
+ Api_Ids.alternateKey(Api_Ids.encode(~typeName="Catalog_Product", ~localId="p-1")),
45
+ )->toEqual(Some("p-1"))
45
46
  )
46
47
 
47
48
  testSync("a plain key offers nothing, so the lookup is never retried", () =>
@@ -83,8 +84,13 @@ describe("stamping an id onto a row leaves the stored row alone", () => {
83
84
  JSON.Encode.object(obj)
84
85
  }
85
86
  let idOf = j =>
86
- j->JSON.Decode.object->Option.flatMap(d => d->Dict.get("id"))->Option.flatMap(JSON.Decode.string)
87
- expect(idOf(stamped))->toEqual(Some(Api_Ids.encode(~typeName="Ordering_Customer", ~localId="cust-42")))
87
+ j
88
+ ->JSON.Decode.object
89
+ ->Option.flatMap(d => d->Dict.get("id"))
90
+ ->Option.flatMap(JSON.Decode.string)
91
+ expect(idOf(stamped))->toEqual(
92
+ Some(Api_Ids.encode(~typeName="Ordering_Customer", ~localId="cust-42")),
93
+ )
88
94
  // The stored row still carries its storage key.
89
95
  expect(idOf(row))->toEqual(Some("cust-42"))
90
96
  })