@reventlessdev/reventless-core 3.0.0-alpha.259 → 3.0.0-alpha.261

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 (209) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +11 -11
  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/admin/Platform_Admin.res +17 -6
  20. package/src/admin/Platform_AdminApi.res +14 -9
  21. package/src/admin/Platform_Admin_Callback.res +1 -1
  22. package/src/admin/Platform_BakedManifest.res +34 -34
  23. package/src/admin/Platform_ComponentDefinitionsApi.res +9 -21
  24. package/src/admin/Platform_PluginStructuresApi.res +15 -12
  25. package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res +1 -1
  26. package/src/components/Aggregate/Aggregate.res +13 -12
  27. package/src/components/Aggregate/Aggregate_Builder.res +22 -21
  28. package/src/components/Aggregate/Aggregate_Callback.res +91 -86
  29. package/src/components/Api/ApiNoApiHelpers.res +1 -3
  30. package/src/components/Api/Api_Ids.res +0 -1
  31. package/src/components/Api/Api_Naming.res +41 -18
  32. package/src/components/Api/GraphQL_FragmentGenerator.res +42 -52
  33. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +11 -4
  34. package/src/components/Api/GraphQL_SchemaInspector.res +5 -3
  35. package/src/components/Api/GraphQL_Stitcher.res +9 -10
  36. package/src/components/Api/MCP_SchemaGenerator.res +8 -12
  37. package/src/components/Api/OwnerScopeDiagnostics.res +1 -2
  38. package/src/components/Api/QueryDbListQuery.res +13 -6
  39. package/src/components/Api/SchemaType.res +40 -20
  40. package/src/components/Api/SchemaType.res.mjs +16 -0
  41. package/src/components/Api/SuryToJsonSchema.res +18 -10
  42. package/src/components/Api/SuryToJsonSchema.res.mjs +135 -121
  43. package/src/components/AutomationSlice/AutomationSlice.res +4 -4
  44. package/src/components/AutomationSlice/AutomationSlice_Builder.res +15 -14
  45. package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -3
  46. package/src/components/Builder_Helpers.res +12 -23
  47. package/src/components/CommandGenerator/CommandGenerator.res +6 -1
  48. package/src/components/CommandGenerator/CommandGenerator_Builder.res +29 -21
  49. package/src/components/CommandGenerator/CommandGenerator_Callback.res +17 -21
  50. package/src/components/CommandTopic/CommandTopic.res +3 -3
  51. package/src/components/CommandTopic/CommandTopic_Builder.res +10 -10
  52. package/src/components/CommandTopic/CommandTopic_Callback.res +1 -4
  53. package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -2
  54. package/src/components/Counter/Counter_Builder.res +1 -6
  55. package/src/components/Counter/Counter_Callback.res +3 -3
  56. package/src/components/Dcb/Dcb_Builder.res +970 -956
  57. package/src/components/DcbEventLog/DcbEventLog.res +4 -4
  58. package/src/components/DcbEventLog/DcbEventLog_Builder.res +0 -1
  59. package/src/components/DcbEventLog/DcbEventLog_Operations.res +17 -18
  60. package/src/components/EventCollector/EventCollector_Builder.res +2 -1
  61. package/src/components/EventLog/EventLog.res +5 -1
  62. package/src/components/EventLog/EventLog_Operations.res +12 -8
  63. package/src/components/EventMapper/EventMapper.res +8 -10
  64. package/src/components/EventMapper/EventMapper_Builder.res +4 -1
  65. package/src/components/EventMapper/EventMapper_Callback.res +46 -31
  66. package/src/components/EventTopic/EventTopic.res +3 -3
  67. package/src/components/EventTopic/EventTopic_Builder.res +1 -1
  68. package/src/components/Extension/Extension.res +4 -1
  69. package/src/components/Extension/Extension_Operations.res +11 -3
  70. package/src/components/ExtensionPoint/ExtensionPoint.res +3 -3
  71. package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +9 -10
  72. package/src/components/ExtensionPoint/ExtensionPoint_Callback.res +2 -6
  73. package/src/components/ExtensionPoint/ExtensionPoint_Operations.res +13 -5
  74. package/src/components/Heartbeat/Heartbeat_Callback.res +3 -3
  75. package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +4 -4
  76. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +7 -10
  77. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice.res +4 -4
  78. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +15 -14
  79. package/src/components/QueryDb/QueryDb_Adapter.res +1 -3
  80. package/src/components/QueryDb/QueryDb_Operations.res +10 -10
  81. package/src/components/ReadModel/ReadModel.res +3 -3
  82. package/src/components/ReadModel/ReadModel_Builder.res +1 -2
  83. package/src/components/ReadModel/ReadModel_Callback.res +2 -1
  84. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +5 -2
  85. package/src/components/StateChangeSlice/StateChangeSlice.res +3 -3
  86. package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +23 -4
  87. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +22 -6
  88. package/src/components/StateTopic.res +1 -3
  89. package/src/components/StateViewSlice/StateViewSlice.res +4 -4
  90. package/src/components/StateViewSlice/StateViewSlice_Builder.res +79 -68
  91. package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
  92. package/src/components/Task/Task.res +3 -3
  93. package/src/components/Task/Task_Adapter.res +4 -1
  94. package/src/components/Task/Task_Builder.res +24 -21
  95. package/src/plugin/api/PluginBaseFragment.res +9 -3
  96. package/src/plugin/component/Capability_Inference.res +8 -10
  97. package/src/plugin/component/Plugin.res +6 -6
  98. package/src/plugin/component/Plugin_Builder.res +54 -50
  99. package/src/plugin/component/Plugin_Callback.res +10 -6
  100. package/src/plugin/component/Plugin_Helpers.res +264 -230
  101. package/src/plugin/component/Plugin_Structure.res +370 -324
  102. package/src/plugin/component/Plugin_SubscriptionSchema.res +8 -6
  103. package/src/plugin/component/SchemaWalker.res +11 -12
  104. package/src/plugin/connect/PluginConnectExtension_Mapping.res +45 -50
  105. package/src/plugin/lifecycle/PluginBehavior.res +3 -4
  106. package/src/plugin/lifecycle/PluginSpec.res +1 -2
  107. package/src/plugin/lifecycle/PluginsProjection.res +16 -5
  108. package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
  109. package/src/util/CommandPublisher.res +9 -2
  110. package/src/util/CsvStream.res +3 -3
  111. package/src/util/Interstack.res +7 -15
  112. package/src/util/Interstack.resi +3 -3
  113. package/src/util/LogFormat.res +5 -9
  114. package/src/util/Logger.res +1 -2
  115. package/src/util/Lru.res +2 -1
  116. package/src/util/Lru.resi +2 -3
  117. package/src/util/ScheduleOps.res +24 -27
  118. package/src/util/StorePrefixCollision.res +6 -9
  119. package/src/util/Util_Promise.res +4 -1
  120. package/src/util/Util_Promise.res.mjs +1 -1
  121. package/tests/IdentityTest.res +166 -113
  122. package/tests/ResourceAttributionTest.res +28 -25
  123. package/tests/StorePrefixCollisionTest.res +2 -1
  124. package/tests/TaskBucketNamingTest.res +3 -2
  125. package/tests/adapter/EventLogProvisioningTest.res +51 -48
  126. package/tests/adapter/MonitoringTest.res +81 -38
  127. package/tests/adapter/QueryInterceptionTest.res +2 -2
  128. package/tests/adapter/RefusalVocabularyTest.res +31 -23
  129. package/tests/adapter/RuntimeExtensionHookReachTest.res +9 -8
  130. package/tests/adapter/RuntimeExtensionTest.res +5 -3
  131. package/tests/admin/AdminApiSchemaDriftTest.res +22 -16
  132. package/tests/admin/Platform_Admin_StructureTest.res +10 -11
  133. package/tests/admin/Platform_BakedManifestTest.res +31 -35
  134. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +18 -15
  135. package/tests/admin/Platform_PluginStructuresApiTest.res +5 -3
  136. package/tests/admin/Platform_UIFragmentsApiTest.res +6 -3
  137. package/tests/aggregate/AggregateCacheTest.res +10 -12
  138. package/tests/aggregate/AggregateCallbackTest.res +75 -46
  139. package/tests/aggregate/AggregateCausationTest.res +19 -20
  140. package/tests/aggregate/AggregateConflictTest.res +41 -19
  141. package/tests/aggregate/AggregateFixtures.res +15 -5
  142. package/tests/aggregate/AggregateRejectionTest.res +97 -80
  143. package/tests/aggregate/AggregateSnapshotTest.res +44 -16
  144. package/tests/api/Api_IdsTest.res +10 -4
  145. package/tests/api/BrandedMarkerCompositionTest.res +48 -36
  146. package/tests/api/GeolocationSemanticTest.res +3 -3
  147. package/tests/api/GraphQL_FragmentGeneratorTest.res +66 -54
  148. package/tests/api/OwnerScopeDiagnosticsTest.res +31 -27
  149. package/tests/api/StorageRefFieldsTest.res +30 -18
  150. package/tests/api/SuryToJsonSchemaTest.res +1338 -981
  151. package/tests/api/SuryToJsonSchemaTest.res.mjs +31 -0
  152. package/tests/api/TaggedUnionSchemaTest.res +17 -24
  153. package/tests/commandgenerator/CommandGeneratorCallbackTest.res +235 -179
  154. package/tests/commandgenerator/CommandGeneratorFixtures.res +8 -13
  155. package/tests/commandgenerator/OwnerStampingTest.res +91 -76
  156. package/tests/commandtopic/CommandOutcomeHookTest.res +198 -155
  157. package/tests/commandtopic/CommandTopicCallbackFixtures.res +5 -4
  158. package/tests/commandtopic/CommandTopicCallbackTest.res +61 -43
  159. package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +70 -55
  160. package/tests/counter/CounterCallbackTest.res +40 -34
  161. package/tests/counter/CounterFixtures.res +4 -4
  162. package/tests/dcb/DcbDecodeTest.res +115 -84
  163. package/tests/dcb/DcbEventLogOperationsTest.res +194 -143
  164. package/tests/dcb/DcbEventLogSchemaTest.res +13 -13
  165. package/tests/dcb/DcbFixtures.res +4 -5
  166. package/tests/dcb/DcbScopeInferenceTest.res +173 -114
  167. package/tests/dcb/DcbStateChangeSliceTest.res +391 -262
  168. package/tests/dcb/DcbTagTest.res +22 -11
  169. package/tests/dcb/DcbValidationTest.res +288 -218
  170. package/tests/eventlog/EventLogFixtures.res +4 -4
  171. package/tests/eventlog/EventLogOperationsTest.res +102 -67
  172. package/tests/eventlog/EventLogRetryTest.res +104 -65
  173. package/tests/eventmapper/EventMapperCallbackTest.res +118 -83
  174. package/tests/eventmapper/EventMapperFixtures.res +15 -23
  175. package/tests/eventmapper/EventMapperTest.res +54 -38
  176. package/tests/eventtopic/EventTopicFixtures.res +3 -4
  177. package/tests/eventtopic/EventTopicOperationsTest.res +41 -29
  178. package/tests/extensionpoint/ExtensionPointCallbackTest.res +58 -45
  179. package/tests/extensionpoint/ExtensionPointFixtures.res +16 -20
  180. package/tests/extensionpoint/ExtensionPointOperationsTest.res +70 -49
  181. package/tests/extensionpoint/PluginDisconnectScheduleTest.res +2 -1
  182. package/tests/logger/LogFormatTest.res +104 -82
  183. package/tests/logger/LoggerLazyTest.res +21 -12
  184. package/tests/message/MessageTest.res +96 -93
  185. package/tests/message/MetaEnvelopeTest.res +26 -30
  186. package/tests/plugin/HeartbeatDisconnectGraceTest.res +31 -25
  187. package/tests/plugin/ManifestVisibilityTest.res +20 -12
  188. package/tests/plugin/PluginDefinitionScalars.res +1 -2
  189. package/tests/plugin/PluginLifecycleCorpusTest.res +15 -12
  190. package/tests/plugin/PluginSpecExperiment.res +0 -1
  191. package/tests/plugin/PluginStructureTest.res +1533 -1030
  192. package/tests/plugin/PluginStructureTest.res.mjs +6 -0
  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 +6 -4
  197. package/tests/plugin/StateView/PsAnnotatedView.res.mjs +7 -3
  198. package/tests/plugin/StateView/PsCustomersView.res +4 -4
  199. package/tests/plugin/StateView/PsShipmentsView.res +3 -2
  200. package/tests/projection/LifecycleTrailTest.res +1 -4
  201. package/tests/projection/ProjectionOptimizeTest.res +13 -9
  202. package/tests/querydb/QueryDbFixtures.res +0 -1
  203. package/tests/querydb/QueryDbOperationsTest.res +99 -64
  204. package/tests/querydb/TaggedUnionStampTest.res +10 -8
  205. package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res +50 -35
  206. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +4 -10
  207. package/tests/util/CommandPublisherTest.res +0 -1
  208. package/tests/util/CsvStreamTest.res +3 -3
  209. package/tests/util/LruTest.res +84 -56
@@ -5,62 +5,80 @@ let _ = beforeEach(() => reset())
5
5
 
6
6
  describe("CommandTopic_Callback.handleJsonCommands:", () => {
7
7
  describe("valid command JSON", () => {
8
- testPromise("decoded command passed to commandsHandler", async () => {
9
- let item = makeTopicItem("agg-1", TestSpec.CreateItem({itemId: "item-1"}))
10
- let _ = await TestHandler.handleJsonCommands(Stream.fromIterable([item]))->Effect.runPromise
11
- expect(capturedItems.contents->Array.length)->toBe(1)
12
- let captured = capturedItems.contents->Array.getUnsafe(0)
13
- expect(captured.reference)->toBe("agg-1")
14
- expect(captured.command)->toEqual(TestSpec.CreateItem({itemId: "item-1"}))
15
- })
8
+ testPromise(
9
+ "decoded command passed to commandsHandler",
10
+ async () => {
11
+ let item = makeTopicItem("agg-1", TestSpec.CreateItem({itemId: "item-1"}))
12
+ let _ = await TestHandler.handleJsonCommands(Stream.fromIterable([item]))->Effect.runPromise
13
+ expect(capturedItems.contents->Array.length)->toBe(1)
14
+ let captured = capturedItems.contents->Array.getUnsafe(0)
15
+ expect(captured.reference)->toBe("agg-1")
16
+ expect(captured.command)->toEqual(TestSpec.CreateItem({itemId: "item-1"}))
17
+ },
18
+ )
16
19
  })
17
20
 
18
21
  describe("results returned from commandsHandler", () => {
19
- testPromise("handler results propagated as return value", async () => {
20
- commandHandlerResults := [Ok("agg-1"), Error("agg-2")]
21
- let item = makeTopicItem("agg-1", TestSpec.CreateItem({itemId: "item-1"}))
22
- let results = await TestHandler.handleJsonCommands(Stream.fromIterable([item]))->Effect.runPromise
23
- expect(results)->toEqual([Ok("agg-1"), Error("agg-2")])
24
- })
22
+ testPromise(
23
+ "handler results propagated as return value",
24
+ async () => {
25
+ commandHandlerResults := [Ok("agg-1"), Error("agg-2")]
26
+ let item = makeTopicItem("agg-1", TestSpec.CreateItem({itemId: "item-1"}))
27
+ let results = await TestHandler.handleJsonCommands(
28
+ Stream.fromIterable([item]),
29
+ )->Effect.runPromise
30
+ expect(results)->toEqual([Ok("agg-1"), Error("agg-2")])
31
+ },
32
+ )
25
33
  })
26
34
 
27
35
  describe("malformed command JSON", () => {
28
- testPromise("skipped gracefully — commandsHandler called with empty array", async () => {
29
- let invalidItem: CommandTopic.topicItem<JSON.t> = {
30
- reference: "ref-1",
31
- command: JSON.Encode.string("not-a-command-object"),
32
- }
33
- let _ = await TestHandler.handleJsonCommands(Stream.fromIterable([invalidItem]))->Effect.runPromise
34
- // Decode fails — commandsHandler called with empty list, returns default [Ok("ref-1")]
35
- expect(capturedItems.contents->Array.length)->toBe(0)
36
- })
36
+ testPromise(
37
+ "skipped gracefully — commandsHandler called with empty array",
38
+ async () => {
39
+ let invalidItem: CommandTopic.topicItem<JSON.t> = {
40
+ reference: "ref-1",
41
+ command: JSON.Encode.string("not-a-command-object"),
42
+ }
43
+ let _ = await TestHandler.handleJsonCommands(
44
+ Stream.fromIterable([invalidItem]),
45
+ )->Effect.runPromise
46
+ // Decode fails — commandsHandler called with empty list, returns default [Ok("ref-1")]
47
+ expect(capturedItems.contents->Array.length)->toBe(0)
48
+ },
49
+ )
37
50
  })
38
51
 
39
52
  describe("multiple commands in batch", () => {
40
- testPromise("all decodable commands reach commandsHandler", async () => {
41
- let items = [
42
- makeTopicItem("agg-1", TestSpec.CreateItem({itemId: "item-1"})),
43
- makeTopicItem("agg-2", TestSpec.DeleteItem({itemId: "item-2"})),
44
- makeTopicItem("agg-3", TestSpec.CreateItem({itemId: "item-3"})),
45
- ]
46
- let _ = await TestHandler.handleJsonCommands(Stream.fromIterable(items))->Effect.runPromise
47
- expect(capturedItems.contents->Array.length)->toBe(3)
48
- })
53
+ testPromise(
54
+ "all decodable commands reach commandsHandler",
55
+ async () => {
56
+ let items = [
57
+ makeTopicItem("agg-1", TestSpec.CreateItem({itemId: "item-1"})),
58
+ makeTopicItem("agg-2", TestSpec.DeleteItem({itemId: "item-2"})),
59
+ makeTopicItem("agg-3", TestSpec.CreateItem({itemId: "item-3"})),
60
+ ]
61
+ let _ = await TestHandler.handleJsonCommands(Stream.fromIterable(items))->Effect.runPromise
62
+ expect(capturedItems.contents->Array.length)->toBe(3)
63
+ },
64
+ )
49
65
  })
50
66
 
51
67
  describe("mixed valid and invalid commands", () => {
52
- testPromise("only valid commands reach commandsHandler", async () => {
53
- let validItem = makeTopicItem("agg-1", TestSpec.CreateItem({itemId: "item-1"}))
54
- let invalidItem: CommandTopic.topicItem<JSON.t> = {
55
- reference: "ref-bad",
56
- command: JSON.Encode.null,
57
- }
58
- let _ =
59
- await TestHandler.handleJsonCommands(
68
+ testPromise(
69
+ "only valid commands reach commandsHandler",
70
+ async () => {
71
+ let validItem = makeTopicItem("agg-1", TestSpec.CreateItem({itemId: "item-1"}))
72
+ let invalidItem: CommandTopic.topicItem<JSON.t> = {
73
+ reference: "ref-bad",
74
+ command: JSON.Encode.null,
75
+ }
76
+ let _ = await TestHandler.handleJsonCommands(
60
77
  Stream.fromIterable([validItem, invalidItem]),
61
78
  )->Effect.runPromise
62
- expect(capturedItems.contents->Array.length)->toBe(1)
63
- expect((capturedItems.contents->Array.getUnsafe(0)).reference)->toBe("agg-1")
64
- })
79
+ expect(capturedItems.contents->Array.length)->toBe(1)
80
+ expect((capturedItems.contents->Array.getUnsafe(0)).reference)->toBe("agg-1")
81
+ },
82
+ )
65
83
  })
66
84
  })
@@ -19,68 +19,83 @@ let cmdJson = (msgId): Reventless.Message.commandJson => {
19
19
  }
20
20
 
21
21
  describe("CommandTopic_Helpers.runInlineAndCollect:", () => {
22
- testPromise("Rejected from rejectedResultChannel beats Accepted from acceptedResultChannel", async () => {
23
- let handler: CommandTopic_Helpers.jsonCommandsHandler = stream =>
24
- stream
25
- ->Stream.runCollect
26
- ->Effect.map(items => {
27
- items->Array.forEach(item => {
28
- // Both channels report for the same reference; rejected wins.
29
- CommandTopic_Helpers.reportAccepted(
30
- ~component="TestService",
31
- item.reference,
32
- {entityId: "agg-1", eventCount: 0},
33
- )
34
- CommandTopic_Helpers.reportRejected(
35
- ~component="TestService",
36
- ~cause=DomainRejection,
37
- item.reference,
38
- {errorCode: "AlreadyExists", errorDetail: ""},
39
- )
40
- })
41
- items->Array.map(item => Ok(item.reference))
42
- })
22
+ testPromise(
23
+ "Rejected from rejectedResultChannel beats Accepted from acceptedResultChannel",
24
+ async () => {
25
+ let handler: CommandTopic_Helpers.jsonCommandsHandler = stream =>
26
+ stream
27
+ ->Stream.runCollect
28
+ ->Effect.map(
29
+ items => {
30
+ items->Array.forEach(
31
+ item => {
32
+ // Both channels report for the same reference; rejected wins.
33
+ CommandTopic_Helpers.reportAccepted(
34
+ ~component="TestService",
35
+ item.reference,
36
+ {entityId: "agg-1", eventCount: 0},
37
+ )
38
+ CommandTopic_Helpers.reportRejected(
39
+ ~component="TestService",
40
+ ~cause=DomainRejection,
41
+ item.reference,
42
+ {errorCode: "AlreadyExists", errorDetail: ""},
43
+ )
44
+ },
45
+ )
46
+ items->Array.map(item => Ok(item.reference))
47
+ },
48
+ )
43
49
 
44
- let outcomes = await CommandTopic_Helpers.runInlineAndCollect([cmdJson("msg-1")], handler)
45
- expect(outcomes->Array.length)->toBe(1)
46
- switch outcomes->Array.getUnsafe(0) {
47
- | Rejected({msgId, errorCode, errorDetail}) =>
48
- expect(msgId)->toEqual("msg-1")
49
- expect(errorCode)->toEqual("AlreadyExists")
50
- expect(errorDetail)->toEqual(None)
51
- | Accepted(_) | Pending(_) => expect("expected Rejected")->toEqual("got other")
52
- }
53
- })
50
+ let outcomes = await CommandTopic_Helpers.runInlineAndCollect([cmdJson("msg-1")], handler)
51
+ expect(outcomes->Array.length)->toBe(1)
52
+ switch outcomes->Array.getUnsafe(0) {
53
+ | Rejected({msgId, errorCode, errorDetail}) =>
54
+ expect(msgId)->toEqual("msg-1")
55
+ expect(errorCode)->toEqual("AlreadyExists")
56
+ expect(errorDetail)->toEqual(None)
57
+ | Accepted(_) | Pending(_) => expect("expected Rejected")->toEqual("got other")
58
+ }
59
+ },
60
+ )
54
61
 
55
- testPromise("Rejected from rejectedResultChannel beats Error result (synthesized Conflict)", async () => {
56
- let handler: CommandTopic_Helpers.jsonCommandsHandler = stream =>
57
- stream
58
- ->Stream.runCollect
59
- ->Effect.map(items => {
60
- items->Array.forEach(item =>
61
- CommandTopic_Helpers.reportRejected(
62
- ~component="TestService",
63
- ~cause=DomainRejection,
64
- item.reference,
65
- {errorCode: "BusinessRuleViolated", errorDetail: "{\"reason\":\"x\"}"},
66
- )
62
+ testPromise(
63
+ "Rejected from rejectedResultChannel beats Error result (synthesized Conflict)",
64
+ async () => {
65
+ let handler: CommandTopic_Helpers.jsonCommandsHandler = stream =>
66
+ stream
67
+ ->Stream.runCollect
68
+ ->Effect.map(
69
+ items => {
70
+ items->Array.forEach(
71
+ item =>
72
+ CommandTopic_Helpers.reportRejected(
73
+ ~component="TestService",
74
+ ~cause=DomainRejection,
75
+ item.reference,
76
+ {errorCode: "BusinessRuleViolated", errorDetail: "{\"reason\":\"x\"}"},
77
+ ),
78
+ )
79
+ // Handler still returns Error — rejectedChannel takes precedence.
80
+ items->Array.map(item => Error(item.reference))
81
+ },
67
82
  )
68
- // Handler still returns Error — rejectedChannel takes precedence.
69
- items->Array.map(item => Error(item.reference))
70
- })
71
83
 
72
- let outcomes = await CommandTopic_Helpers.runInlineAndCollect([cmdJson("msg-1")], handler)
73
- switch outcomes->Array.getUnsafe(0) {
74
- | Rejected({errorCode, errorDetail}) =>
75
- expect(errorCode)->toEqual("BusinessRuleViolated")
76
- expect(errorDetail)->toEqual(Some("{\"reason\":\"x\"}"))
77
- | Accepted(_) | Pending(_) => expect("expected Rejected")->toEqual("got other")
78
- }
79
- })
84
+ let outcomes = await CommandTopic_Helpers.runInlineAndCollect([cmdJson("msg-1")], handler)
85
+ switch outcomes->Array.getUnsafe(0) {
86
+ | Rejected({errorCode, errorDetail}) =>
87
+ expect(errorCode)->toEqual("BusinessRuleViolated")
88
+ expect(errorDetail)->toEqual(Some("{\"reason\":\"x\"}"))
89
+ | Accepted(_) | Pending(_) => expect("expected Rejected")->toEqual("got other")
90
+ }
91
+ },
92
+ )
80
93
 
81
94
  testPromise("Error result without rejectedChannel still synthesizes Conflict", async () => {
82
95
  let handler: CommandTopic_Helpers.jsonCommandsHandler = stream =>
83
- stream->Stream.runCollect->Effect.map(items => items->Array.map(item => Error(item.reference)))
96
+ stream
97
+ ->Stream.runCollect
98
+ ->Effect.map(items => items->Array.map(item => Error(item.reference)))
84
99
 
85
100
  let outcomes = await CommandTopic_Helpers.runInlineAndCollect([cmdJson("msg-1")], handler)
86
101
  switch outcomes->Array.getUnsafe(0) {
@@ -39,45 +39,51 @@ describe("Counter_Callback.groupByCounterId:", () => {
39
39
 
40
40
  describe("Counter_Callback.counterHandler:", () => {
41
41
  describe("count reaches zero", () => {
42
- testPromise("CountFinished event dispatched via jsonEventsHandler", async () => {
43
- let ref1 = Counter.makeId(("counter-1", "ref-a"))
44
- let counts = [makeCountsJson(ref1, 0)]
45
- await TestCounterHandler.counterHandler(~references=[(ref1, 1)], ~counts)
46
- // jsonEventsHandler should have been called with 1 event batch containing 1 event
47
- let batches = capturedEventBatches.contents
48
- expect((batches->Array.length, batches->Array.getUnsafe(0)->Array.length))->toEqual((1, 1))
49
- })
42
+ testPromise(
43
+ "CountFinished event dispatched via jsonEventsHandler",
44
+ async () => {
45
+ let ref1 = Counter.makeId(("counter-1", "ref-a"))
46
+ let counts = [makeCountsJson(ref1, 0)]
47
+ await TestCounterHandler.counterHandler(~references=[(ref1, 1)], ~counts)
48
+ // jsonEventsHandler should have been called with 1 event batch containing 1 event
49
+ let batches = capturedEventBatches.contents
50
+ expect((batches->Array.length, batches->Array.getUnsafe(0)->Array.length))->toEqual((1, 1))
51
+ },
52
+ )
50
53
  })
51
54
 
52
55
  describe("count above zero", () => {
53
- testPromise("no CountFinished event, countsDbCount still called", async () => {
54
- let ref1 = Counter.makeId(("counter-1", "ref-a"))
55
- let counts = [makeCountsJson(ref1, 3)]
56
- await TestCounterHandler.counterHandler(~references=[(ref1, 1)], ~counts)
57
- // jsonEventsHandler called with empty array (no finished counters)
58
- let batches = capturedEventBatches.contents
59
- let events = batches->Array.getUnsafe(0)
60
- expect(events->Array.length)->toBe(0)
61
- // countsDbCount was called once
62
- expect(capturedCountCalls.contents->Array.length)->toBe(1)
63
- })
56
+ testPromise(
57
+ "no CountFinished event, countsDbCount still called",
58
+ async () => {
59
+ let ref1 = Counter.makeId(("counter-1", "ref-a"))
60
+ let counts = [makeCountsJson(ref1, 3)]
61
+ await TestCounterHandler.counterHandler(~references=[(ref1, 1)], ~counts)
62
+ // jsonEventsHandler called with empty array (no finished counters)
63
+ let batches = capturedEventBatches.contents
64
+ let events = batches->Array.getUnsafe(0)
65
+ expect(events->Array.length)->toBe(0)
66
+ // countsDbCount was called once
67
+ expect(capturedCountCalls.contents->Array.length)->toBe(1)
68
+ },
69
+ )
64
70
  })
65
71
 
66
72
  describe("multiple counters in batch", () => {
67
- testPromise("each decremented independently", async () => {
68
- let refA = Counter.makeId(("counter-a", "ref-1"))
69
- let refB = Counter.makeId(("counter-b", "ref-2"))
70
- let counts = [makeCountsJson(refA, 2), makeCountsJson(refB, 0)]
71
- await TestCounterHandler.counterHandler(
72
- ~references=[(refA, 1), (refB, 1)],
73
- ~counts,
74
- )
75
- // countsDbCount called twice (once per counter ID)
76
- expect(capturedCountCalls.contents->Array.length)->toBe(2)
77
- // One CountFinished event (counter-b reached 0)
78
- let batches = capturedEventBatches.contents
79
- let events = batches->Array.getUnsafe(0)
80
- expect(events->Array.length)->toBe(1)
81
- })
73
+ testPromise(
74
+ "each decremented independently",
75
+ async () => {
76
+ let refA = Counter.makeId(("counter-a", "ref-1"))
77
+ let refB = Counter.makeId(("counter-b", "ref-2"))
78
+ let counts = [makeCountsJson(refA, 2), makeCountsJson(refB, 0)]
79
+ await TestCounterHandler.counterHandler(~references=[(refA, 1), (refB, 1)], ~counts)
80
+ // countsDbCount called twice (once per counter ID)
81
+ expect(capturedCountCalls.contents->Array.length)->toBe(2)
82
+ // One CountFinished event (counter-b reached 0)
83
+ let batches = capturedEventBatches.contents
84
+ let events = batches->Array.getUnsafe(0)
85
+ expect(events->Array.length)->toBe(1)
86
+ },
87
+ )
82
88
  })
83
89
  })
@@ -1,4 +1,3 @@
1
-
2
1
  // ─────────────────────────────────────────────────────────────
3
2
  // Shared captures — reset in beforeEach
4
3
  // ─────────────────────────────────────────────────────────────
@@ -14,7 +13,9 @@ let capturedEventBatches: ref<array<array<JSON.t>>> = ref([])
14
13
 
15
14
  let mockCountsDbCount: QueryDb.count<string> = async (id, field, delta) => {
16
15
  capturedCountCalls :=
17
- capturedCountCalls.contents->Array.concat([(id->Reventless.Id.StringPure.toString, field, delta)])
16
+ capturedCountCalls.contents->Array.concat([
17
+ (id->Reventless.Id.StringPure.toString, field, delta),
18
+ ])
18
19
  Ok(0)
19
20
  }
20
21
 
@@ -26,8 +27,7 @@ let mockJsonEventsHandler: Counter.jsonEventsHandler = stream =>
26
27
  stream
27
28
  ->Stream.runCollect
28
29
  ->Effect.map(chunk => {
29
- capturedEventBatches :=
30
- capturedEventBatches.contents->Array.concat([chunk])
30
+ capturedEventBatches := capturedEventBatches.contents->Array.concat([chunk])
31
31
  })
32
32
 
33
33
  // ─────────────────────────────────────────────────────────────
@@ -28,95 +28,126 @@ type consumedFull =
28
28
 
29
29
  describe("DcbDecode:", () => {
30
30
  describe("makeDecoder — payload-less", () => {
31
- let {Reventless.DcbDecode.decode: decode, eventTypes} =
32
- Reventless.DcbDecode.makeDecoder(consumedPayloadLessSchema)
33
-
34
- testSync("returns correct eventTypes", () => {
35
- let sorted = eventTypes->Array.toSorted(String.compare)
36
- expect(sorted)->toEqual(["ItemArchived", "ItemCreated"])
37
- })
38
-
39
- testSync("decodes ItemCreated as payload-less variant", () => {
40
- let result = decode(
41
- ~eventType="ItemCreated",
42
- ~data=Dict.fromArray([("itemId", JSON.String("i-1")), ("name", JSON.String("Test"))]),
43
- )
44
- let expected: option<consumedPayloadLess> = Some(ItemCreated)
45
- expect(result)->toEqual(expected)
46
- })
47
-
48
- testSync("decodes ItemArchived as payload-less variant", () => {
49
- let result = decode(
50
- ~eventType="ItemArchived",
51
- ~data=Dict.fromArray([("itemId", JSON.String("i-1"))]),
52
- )
53
- let expected: option<consumedPayloadLess> = Some(ItemArchived)
54
- expect(result)->toEqual(expected)
55
- })
56
-
57
- testSync("returns None for unknown event type", () => {
58
- let result = decode(
59
- ~eventType="ItemRenamed",
60
- ~data=Dict.fromArray([("itemId", JSON.String("i-1"))]),
61
- )
62
- expect(result)->toEqual(None)
63
- })
31
+ let {Reventless.DcbDecode.decode: decode, eventTypes} = Reventless.DcbDecode.makeDecoder(
32
+ consumedPayloadLessSchema,
33
+ )
34
+
35
+ testSync(
36
+ "returns correct eventTypes",
37
+ () => {
38
+ let sorted = eventTypes->Array.toSorted(String.compare)
39
+ expect(sorted)->toEqual(["ItemArchived", "ItemCreated"])
40
+ },
41
+ )
42
+
43
+ testSync(
44
+ "decodes ItemCreated as payload-less variant",
45
+ () => {
46
+ let result = decode(
47
+ ~eventType="ItemCreated",
48
+ ~data=Dict.fromArray([("itemId", JSON.String("i-1")), ("name", JSON.String("Test"))]),
49
+ )
50
+ let expected: option<consumedPayloadLess> = Some(ItemCreated)
51
+ expect(result)->toEqual(expected)
52
+ },
53
+ )
54
+
55
+ testSync(
56
+ "decodes ItemArchived as payload-less variant",
57
+ () => {
58
+ let result = decode(
59
+ ~eventType="ItemArchived",
60
+ ~data=Dict.fromArray([("itemId", JSON.String("i-1"))]),
61
+ )
62
+ let expected: option<consumedPayloadLess> = Some(ItemArchived)
63
+ expect(result)->toEqual(expected)
64
+ },
65
+ )
66
+
67
+ testSync(
68
+ "returns None for unknown event type",
69
+ () => {
70
+ let result = decode(
71
+ ~eventType="ItemRenamed",
72
+ ~data=Dict.fromArray([("itemId", JSON.String("i-1"))]),
73
+ )
74
+ expect(result)->toEqual(None)
75
+ },
76
+ )
64
77
  })
65
78
 
66
79
  describe("makeDecoder — partial projection", () => {
67
- let {Reventless.DcbDecode.decode: decode} =
68
- Reventless.DcbDecode.makeDecoder(consumedPartialSchema)
69
-
70
- testSync("decodes ItemCreated with only itemId field", () => {
71
- let result = decode(
72
- ~eventType="ItemCreated",
73
- ~data=Dict.fromArray([("itemId", JSON.String("i-1")), ("name", JSON.String("Test"))]),
74
- )
75
- let expected: option<consumedPartial> = Some(ItemCreated({itemId: "i-1"}))
76
- expect(result)->toEqual(expected)
77
- })
78
-
79
- testSync("decodes ItemRenamed with only itemId field", () => {
80
- let result = decode(
81
- ~eventType="ItemRenamed",
82
- ~data=Dict.fromArray([("itemId", JSON.String("i-1")), ("newName", JSON.String("New"))]),
83
- )
84
- let expected: option<consumedPartial> = Some(ItemRenamed({itemId: "i-1"}))
85
- expect(result)->toEqual(expected)
86
- })
87
-
88
- testSync("returns None for unknown event type", () => {
89
- let result = decode(~eventType="ItemArchived", ~data=Dict.fromArray([]))
90
- expect(result)->toEqual(None)
91
- })
80
+ let {Reventless.DcbDecode.decode: decode} = Reventless.DcbDecode.makeDecoder(
81
+ consumedPartialSchema,
82
+ )
83
+
84
+ testSync(
85
+ "decodes ItemCreated with only itemId field",
86
+ () => {
87
+ let result = decode(
88
+ ~eventType="ItemCreated",
89
+ ~data=Dict.fromArray([("itemId", JSON.String("i-1")), ("name", JSON.String("Test"))]),
90
+ )
91
+ let expected: option<consumedPartial> = Some(ItemCreated({itemId: "i-1"}))
92
+ expect(result)->toEqual(expected)
93
+ },
94
+ )
95
+
96
+ testSync(
97
+ "decodes ItemRenamed with only itemId field",
98
+ () => {
99
+ let result = decode(
100
+ ~eventType="ItemRenamed",
101
+ ~data=Dict.fromArray([("itemId", JSON.String("i-1")), ("newName", JSON.String("New"))]),
102
+ )
103
+ let expected: option<consumedPartial> = Some(ItemRenamed({itemId: "i-1"}))
104
+ expect(result)->toEqual(expected)
105
+ },
106
+ )
107
+
108
+ testSync(
109
+ "returns None for unknown event type",
110
+ () => {
111
+ let result = decode(~eventType="ItemArchived", ~data=Dict.fromArray([]))
112
+ expect(result)->toEqual(None)
113
+ },
114
+ )
92
115
  })
93
116
 
94
117
  describe("makeDecoder — full shape", () => {
95
- let {Reventless.DcbDecode.decode: decode} =
96
- Reventless.DcbDecode.makeDecoder(consumedFullSchema)
97
-
98
- testSync("decodes ItemCreated with all fields", () => {
99
- let result = decode(
100
- ~eventType="ItemCreated",
101
- ~data=Dict.fromArray([("itemId", JSON.String("i-1")), ("name", JSON.String("Test"))]),
102
- )
103
- expect(result)->toEqual(Some(ItemCreated({itemId: "i-1", name: "Test"})))
104
- })
105
-
106
- testSync("decodes ItemRenamed with all fields", () => {
107
- let result = decode(
108
- ~eventType="ItemRenamed",
109
- ~data=Dict.fromArray([("itemId", JSON.String("i-1")), ("newName", JSON.String("New"))]),
110
- )
111
- expect(result)->toEqual(Some(ItemRenamed({itemId: "i-1", newName: "New"})))
112
- })
113
-
114
- testSync("decodes ItemArchived with all fields", () => {
115
- let result = decode(
116
- ~eventType="ItemArchived",
117
- ~data=Dict.fromArray([("itemId", JSON.String("i-1"))]),
118
- )
119
- expect(result)->toEqual(Some(ItemArchived({itemId: "i-1"})))
120
- })
118
+ let {Reventless.DcbDecode.decode: decode} = Reventless.DcbDecode.makeDecoder(consumedFullSchema)
119
+
120
+ testSync(
121
+ "decodes ItemCreated with all fields",
122
+ () => {
123
+ let result = decode(
124
+ ~eventType="ItemCreated",
125
+ ~data=Dict.fromArray([("itemId", JSON.String("i-1")), ("name", JSON.String("Test"))]),
126
+ )
127
+ expect(result)->toEqual(Some(ItemCreated({itemId: "i-1", name: "Test"})))
128
+ },
129
+ )
130
+
131
+ testSync(
132
+ "decodes ItemRenamed with all fields",
133
+ () => {
134
+ let result = decode(
135
+ ~eventType="ItemRenamed",
136
+ ~data=Dict.fromArray([("itemId", JSON.String("i-1")), ("newName", JSON.String("New"))]),
137
+ )
138
+ expect(result)->toEqual(Some(ItemRenamed({itemId: "i-1", newName: "New"})))
139
+ },
140
+ )
141
+
142
+ testSync(
143
+ "decodes ItemArchived with all fields",
144
+ () => {
145
+ let result = decode(
146
+ ~eventType="ItemArchived",
147
+ ~data=Dict.fromArray([("itemId", JSON.String("i-1"))]),
148
+ )
149
+ expect(result)->toEqual(Some(ItemArchived({itemId: "i-1"})))
150
+ },
151
+ )
121
152
  })
122
153
  })