@reventlessdev/reventless-local 3.0.0-alpha.249 → 3.0.0-alpha.251

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 (174) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/package.json +13 -13
  3. package/src/BakedManifest.res +5 -6
  4. package/src/EmitCapabilities.res +3 -1
  5. package/src/LocalPlatformRegistry.res +3 -1
  6. package/src/LocalPlatformStart.res +7 -6
  7. package/src/LocalSeedTarget.res +3 -1
  8. package/src/Platform.res +291 -215
  9. package/src/ShellConfig.res +10 -17
  10. package/src/UiHints.res +8 -14
  11. package/src/UiSlots.res +10 -17
  12. package/src/adapter/Api/LocalEvents_Server.res +5 -3
  13. package/src/adapter/Api/LocalGraphQL_Adapter.res +4 -4
  14. package/src/adapter/Auth/LocalAuth.res +12 -16
  15. package/src/adapter/Auth/LocalAuth.res.mjs +2 -1
  16. package/src/adapter/Auth/UserStore.res +13 -40
  17. package/src/adapter/Auth/UserStore.res.mjs +11 -67
  18. package/src/adapter/Backend.res +1 -2
  19. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +42 -26
  20. package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +13 -3
  21. package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +15 -17
  22. package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +18 -22
  23. package/src/adapter/Counter/LocalCounterHandler.res +1 -1
  24. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +33 -22
  25. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +57 -57
  26. package/src/adapter/DomainGraphQL_Server.res +35 -34
  27. package/src/adapter/DomainMCP_Server.res +45 -41
  28. package/src/adapter/EventCollector/LocalEventCollectorChannel.res +47 -36
  29. package/src/adapter/EventHistory/EventHistoryResolvers_GraphQL.res +15 -12
  30. package/src/adapter/EventLog/EventLogStorage_InMemory.res +9 -4
  31. package/src/adapter/EventLog/EventLogStorage_Sqlite.res +36 -24
  32. package/src/adapter/EventTopic/LocalEventTopicPublisher.res +9 -3
  33. package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +9 -14
  34. package/src/adapter/LocalBus.res +12 -12
  35. package/src/adapter/LocalCapabilities.res +6 -0
  36. package/src/adapter/LocalCapabilities.res.mjs +5 -2
  37. package/src/adapter/LocalGeocodeResolvers.res +1 -4
  38. package/src/adapter/LocalSideEffectHandler.res +1 -1
  39. package/src/adapter/LocalStateChangeDescriptor.res +2 -1
  40. package/src/adapter/MCP_ServerInstance.res +51 -39
  41. package/src/adapter/ObjectStore/LocalObjectStore.res +5 -2
  42. package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -2
  43. package/src/adapter/PlatformGraphQL_Server.res +10 -6
  44. package/src/adapter/PlatformGraphQL_Server.res.mjs +2 -1
  45. package/src/adapter/ProjectionCheckpoint.res +8 -1
  46. package/src/adapter/ProjectionPending.res +1 -3
  47. package/src/adapter/QueryDb/LocalQueryDbStorage.res +2 -1
  48. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +367 -269
  49. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +5 -3
  50. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +63 -40
  51. package/src/adapter/QueryDb/SortKey_Filter.res +14 -6
  52. package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +12 -10
  53. package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +8 -2
  54. package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +1 -4
  55. package/src/adapter/Scheduler/LocalScheduledPublisher.res +12 -12
  56. package/src/adapter/SqliteDriver.res +16 -4
  57. package/src/adapter/Task/TaskBucket_Sqlite.res +5 -6
  58. package/src/components/Aggregate_Builder.res +6 -1
  59. package/src/components/Counter_Builder.res +1 -5
  60. package/src/components/DcbEventLog_Builder.res +5 -3
  61. package/src/components/ExtensionPoint_Builder.res +13 -15
  62. package/src/components/InboundTranslationSlice_Builder.res +3 -2
  63. package/src/components/OutboundTranslationSlice_Builder.res +3 -2
  64. package/src/components/ReadModel_Builder.res +18 -20
  65. package/src/components/StateViewSlice_Builder.res +3 -2
  66. package/src/components/Task_Builder.res +12 -13
  67. package/src/reset/LocalSeedReset.res +2 -5
  68. package/src/test/Mocks/MockDcbEventLogStorage.res +5 -1
  69. package/src/test/Mocks/MockEventLogStorage.res +8 -3
  70. package/src/test/Mocks/MockQueryDbStorage.res +3 -2
  71. package/src/test/TestRunner.res +1 -1
  72. package/tests/BakedManifestFilesTest.res +7 -10
  73. package/tests/LocalEventTapTest.res +26 -21
  74. package/tests/LocalPlatformStartTest.res +33 -28
  75. package/tests/PluginEventDecodeTest.res +3 -3
  76. package/tests/ShellConfigTest.res +30 -21
  77. package/tests/SplitApiFixtures.res +34 -10
  78. package/tests/SplitApiTest.res +92 -62
  79. package/tests/adapter/BackendParityTest.res +180 -130
  80. package/tests/adapter/CollectNEventsTest.res +6 -4
  81. package/tests/adapter/CommandAuthorizationTest.res +47 -41
  82. package/tests/adapter/CommandTopicChannelTest.res +163 -139
  83. package/tests/adapter/CounterHandlerTest.res +95 -77
  84. package/tests/adapter/DcbEventLogStorageSqliteTest.res +171 -168
  85. package/tests/adapter/DcbEventLogStorageTest.res +264 -207
  86. package/tests/adapter/DomainGraphQL_ServerTest.res +53 -43
  87. package/tests/adapter/EventCollectorChannelTest.res +153 -124
  88. package/tests/adapter/EventHistoryResolverTest.res +46 -18
  89. package/tests/adapter/EventLogSnapshotParityTest.res +57 -45
  90. package/tests/adapter/EventLogStorageSqliteTest.res +9 -10
  91. package/tests/adapter/EventTapTest.res +37 -20
  92. package/tests/adapter/EventTopicPublisherTest.res +74 -50
  93. package/tests/adapter/GraphQLSubscriptionBridgeTest.res +4 -3
  94. package/tests/adapter/GraphQL_SchemaInspectorTest.res +666 -568
  95. package/tests/adapter/GraphQL_SubscriptionResolversTest.res +105 -96
  96. package/tests/adapter/HeartbeatRunnerTest.res +99 -81
  97. package/tests/adapter/InboundTranslationMutationTest.res +4 -1
  98. package/tests/adapter/LocalAuthHttpTest.res +79 -85
  99. package/tests/adapter/LocalAuthLoginTest.res +42 -50
  100. package/tests/adapter/LocalAuthTest.res +8 -16
  101. package/tests/adapter/LocalAuthUserStoreTest.res +2 -7
  102. package/tests/adapter/LocalBusBoundedTest.res +187 -117
  103. package/tests/adapter/LocalBusPubSubTest.res +118 -76
  104. package/tests/adapter/LocalDispatchContextTest.res +1 -2
  105. package/tests/adapter/LocalEvents_ServerTest.res +242 -187
  106. package/tests/adapter/PgProjectionCatchupTest.res +44 -10
  107. package/tests/adapter/ProjectionCheckpointTest.res +261 -206
  108. package/tests/adapter/QueryDbGsiTtlTest.res +123 -83
  109. package/tests/adapter/QueryDbListPushdownParityTest.res +107 -67
  110. package/tests/adapter/QueryDbListResolverTest.res +225 -208
  111. package/tests/adapter/QueryDbResolvedFieldsTest.res +17 -14
  112. package/tests/adapter/QueryDbStorageSqliteTest.res +33 -49
  113. package/tests/adapter/QueryDbStorageTest.res +163 -117
  114. package/tests/adapter/QueryDbUnionRoundTripTest.res +8 -10
  115. package/tests/adapter/QueryEngineTest.res +179 -126
  116. package/tests/adapter/ScheduledPublisherTest.res +171 -135
  117. package/tests/adapter/ServedBucketHttpTest.res +65 -59
  118. package/tests/adapter/SortKey_FilterTest.res +19 -3
  119. package/tests/adapter/SqliteDriverTest.res +13 -10
  120. package/tests/adapter/TaskBucketTest.res +94 -80
  121. package/tests/components/aggregate/AggregateTest.res +31 -18
  122. package/tests/components/automationslice/AutomationSliceCallbackTest.res +259 -222
  123. package/tests/components/automationslice/AutomationSliceFixtures.res +4 -2
  124. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +38 -38
  125. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +12 -14
  126. package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +8 -32
  127. package/tests/components/commandgenerator/CommandGeneratorFixtures.res +7 -5
  128. package/tests/components/commandgenerator/CommandGeneratorTest.res +40 -31
  129. package/tests/components/commandtopic/CommandTopicFixtures.res +3 -4
  130. package/tests/components/commandtopic/CommandTopicStreamFixtures.res +7 -5
  131. package/tests/components/commandtopic/CommandTopicTest.res +24 -15
  132. package/tests/components/counter/CounterFixtures.res +6 -8
  133. package/tests/components/dcb/DcbCrossPartitionFixtures.res +8 -2
  134. package/tests/components/dcb/DcbCrossPartitionReadTest.res +20 -13
  135. package/tests/components/dcb/DcbEventLogAppendStreamTest.res +12 -8
  136. package/tests/components/dcb/DcbEventLogStreamTest.res +88 -61
  137. package/tests/components/dcb/DcbFixtures.res +4 -1
  138. package/tests/components/dcb/DcbTest.res +9 -6
  139. package/tests/components/eventcollector/EventCollectorFixtures.res +0 -1
  140. package/tests/components/eventcollector/EventCollectorTest.res +6 -3
  141. package/tests/components/eventlog/EventLogAppendStreamTest.res +88 -68
  142. package/tests/components/eventlog/EventLogConflictTest.res +8 -4
  143. package/tests/components/eventlog/EventLogFixtures.res +3 -3
  144. package/tests/components/eventlog/EventLogProvisioningSeamTest.res +26 -13
  145. package/tests/components/eventlog/EventLogStreamTest.res +119 -56
  146. package/tests/components/eventlog/EventLogTest.res +20 -4
  147. package/tests/components/eventtopic/EventTopicFixtures.res +3 -3
  148. package/tests/components/eventtopic/EventTopicStreamFixtures.res +1 -2
  149. package/tests/components/eventtopic/EventTopicStreamTest.res +7 -1
  150. package/tests/components/extensionpoint/ExtensionPointFixtures.res +14 -9
  151. package/tests/components/extensionpoint/ExtensionPointTest.res +3 -2
  152. package/tests/components/heartbeat/HeartbeatFixtures.res +5 -6
  153. package/tests/components/heartbeat/HeartbeatTest.res +56 -47
  154. package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +271 -249
  155. package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -2
  156. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +361 -276
  157. package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -10
  158. package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +37 -43
  159. package/tests/components/querydb/QueryDbFixtures.res +1 -5
  160. package/tests/components/querydb/QueryDbTest.res +2 -4
  161. package/tests/components/readmodel/DcbReadModelE2EFixtures.res +15 -18
  162. package/tests/components/readmodel/MixedSourceReadModelFixtures.res +5 -7
  163. package/tests/components/readmodel/ReadModelFixtures.res +8 -8
  164. package/tests/components/scheduler/SchedulerTest.res +76 -58
  165. package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +4 -4
  166. package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +5 -7
  167. package/tests/components/stateviewslice/StateViewSliceFixtures.res +8 -6
  168. package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +17 -11
  169. package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +12 -2
  170. package/tests/components/task/TaskFixtures.res +5 -5
  171. package/tests/components/task/TaskTest.res +95 -80
  172. package/tests/plugin/PluginsProjection_GWT.res +3 -1
  173. package/tests/plugin/UiFragments_GWT.res +6 -1
  174. package/tests/reset/LocalSeedResetTest.res +1 -0
@@ -18,9 +18,9 @@ let testMeta: Reventless.Message.meta = {
18
18
  // Helper: build a minimal runtime with a completed handler Deferred
19
19
  // ─────────────────────────────────────────────────────────────
20
20
 
21
- let makeRuntime = (
22
- handler: LocalRuntimeEnvironment.handler,
23
- ): ReventlessCore.Runtime.environment<LocalRuntimeEnvironment.parts> => {
21
+ let makeRuntime = (handler: LocalRuntimeEnvironment.handler): ReventlessCore.Runtime.environment<
22
+ LocalRuntimeEnvironment.parts,
23
+ > => {
24
24
  let handlerDeferred: Deferred.t<LocalRuntimeEnvironment.handler, unit> =
25
25
  Deferred.make()->Effect.runSync
26
26
  Deferred.succeed(handlerDeferred, handler)->Effect.runSync->ignore
@@ -35,132 +35,161 @@ let makeRuntime = (
35
35
 
36
36
  describe("LocalEventCollectorChannel", () => {
37
37
  describe("make", () => {
38
- testPromise("collects all event topic resources as channel resources", async () => {
39
- module TestBus = LocalBus.Make()
40
- module TestPublisher = LocalEventTopicPublisher.Make(TestBus)
41
- module TestCollector = LocalEventCollectorChannel.Make(TestBus)
42
- let pubA = TestPublisher.make(~name="topicA", ~storageResources=[], ~owner=None, ~opts={})
43
- let pubB = TestPublisher.make(~name="topicB", ~storageResources=[], ~owner=None, ~opts={})
44
- let eventTopics = Dict.fromArray([
45
- ("topicA", {ReventlessInfra.EventTopic.resources: pubA.resources}),
46
- ("topicB", {ReventlessInfra.EventTopic.resources: pubB.resources}),
47
- ])
48
- let ch = TestCollector.make(~name="collector", ~eventTopics, ~owner=None, ~opts={})
49
- // Each publisher returns 1 resource, so 2 total
50
- expect(ch.resources->Array.length)->toBe(2)
51
- })
38
+ testPromise(
39
+ "collects all event topic resources as channel resources",
40
+ async () => {
41
+ module TestBus = LocalBus.Make()
42
+ module TestPublisher = LocalEventTopicPublisher.Make(TestBus)
43
+ module TestCollector = LocalEventCollectorChannel.Make(TestBus)
44
+ let pubA = TestPublisher.make(~name="topicA", ~storageResources=[], ~owner=None, ~opts={})
45
+ let pubB = TestPublisher.make(~name="topicB", ~storageResources=[], ~owner=None, ~opts={})
46
+ let eventTopics = Dict.fromArray([
47
+ ("topicA", {ReventlessInfra.EventTopic.resources: pubA.resources}),
48
+ ("topicB", {ReventlessInfra.EventTopic.resources: pubB.resources}),
49
+ ])
50
+ let ch = TestCollector.make(~name="collector", ~eventTopics, ~owner=None, ~opts={})
51
+ // Each publisher returns 1 resource, so 2 total
52
+ expect(ch.resources->Array.length)->toBe(2)
53
+ },
54
+ )
52
55
 
53
- testPromise("empty eventTopics produces empty resources", async () => {
54
- module TestBus = LocalBus.Make()
55
- module TestCollector = LocalEventCollectorChannel.Make(TestBus)
56
- let ch = TestCollector.make(~name="collector", ~eventTopics=Dict.make(), ~owner=None, ~opts={})
57
- expect(ch.resources->Array.length)->toBe(0)
58
- })
56
+ testPromise(
57
+ "empty eventTopics produces empty resources",
58
+ async () => {
59
+ module TestBus = LocalBus.Make()
60
+ module TestCollector = LocalEventCollectorChannel.Make(TestBus)
61
+ let ch = TestCollector.make(
62
+ ~name="collector",
63
+ ~eventTopics=Dict.make(),
64
+ ~owner=None,
65
+ ~opts={},
66
+ )
67
+ expect(ch.resources->Array.length)->toBe(0)
68
+ },
69
+ )
59
70
  })
60
71
 
61
72
  describe("connect", () => {
62
- testPromise("subscribes to event topic; published events reach the handler", async () => {
63
- module TestBus = LocalBus.Make()
64
- module TestPublisher = LocalEventTopicPublisher.Make(TestBus)
65
- module TestCollector = LocalEventCollectorChannel.Make(TestBus)
66
- let received: ref<int> = ref(0)
67
- let runtime = makeRuntime(async (_, _ctx) => {
68
- received := received.contents + 1
69
- })
70
- let pub = TestPublisher.make(~name="eventA", ~storageResources=[], ~owner=None, ~opts={})
71
- let eventTopics = Dict.fromArray([
72
- ("eventA", {ReventlessInfra.EventTopic.resources: pub.resources}),
73
- ])
74
- let ch = TestCollector.make(~name="collector", ~eventTopics, ~owner=None, ~opts={})
75
- let channelSpec: ReventlessCore.EventCollector_Adapter.channelSpec<JSON.t, unit, unit> = {
76
- channel: ch,
77
- eventTopics,
78
- resources: [],
79
- }
80
- let _ = TestCollector.connect(
81
- ~name="collector",
82
- ~channelSpecs=[channelSpec],
83
- ~runtime,
84
- ~opts={},
85
- )
86
- // Await the subscriptionLatch — opens after Output.apply fires and subscription registers
87
- await runtime.parts.subscriptionLatch->Latch.await_->Effect.runPromise
88
- let publishFn = await pub.publishJson->TestRunner.resolve
89
- await publishFn("svc", testMeta, JSON.Null)
90
- expect(received.contents)->toBe(1)
91
- })
73
+ testPromise(
74
+ "subscribes to event topic; published events reach the handler",
75
+ async () => {
76
+ module TestBus = LocalBus.Make()
77
+ module TestPublisher = LocalEventTopicPublisher.Make(TestBus)
78
+ module TestCollector = LocalEventCollectorChannel.Make(TestBus)
79
+ let received: ref<int> = ref(0)
80
+ let runtime = makeRuntime(
81
+ async (_, _ctx) => {
82
+ received := received.contents + 1
83
+ },
84
+ )
85
+ let pub = TestPublisher.make(~name="eventA", ~storageResources=[], ~owner=None, ~opts={})
86
+ let eventTopics = Dict.fromArray([
87
+ ("eventA", {ReventlessInfra.EventTopic.resources: pub.resources}),
88
+ ])
89
+ let ch = TestCollector.make(~name="collector", ~eventTopics, ~owner=None, ~opts={})
90
+ let channelSpec: ReventlessCore.EventCollector_Adapter.channelSpec<JSON.t, unit, unit> = {
91
+ channel: ch,
92
+ eventTopics,
93
+ resources: [],
94
+ }
95
+ let _ = TestCollector.connect(
96
+ ~name="collector",
97
+ ~channelSpecs=[channelSpec],
98
+ ~runtime,
99
+ ~opts={},
100
+ )
101
+ // Await the subscriptionLatch — opens after Output.apply fires and subscription registers
102
+ await runtime.parts.subscriptionLatch->Latch.await_->Effect.runPromise
103
+ let publishFn = await pub.publishJson->TestRunner.resolve
104
+ await publishFn("svc", testMeta, JSON.Null)
105
+ expect(received.contents)->toBe(1)
106
+ },
107
+ )
92
108
 
93
- testPromise("multiple event topics all deliver to the same handler", async () => {
94
- module TestBus = LocalBus.Make()
95
- module TestPublisher = LocalEventTopicPublisher.Make(TestBus)
96
- module TestCollector = LocalEventCollectorChannel.Make(TestBus)
97
- let received: ref<int> = ref(0)
98
- let runtime = makeRuntime(async (_, _) => {
99
- received := received.contents + 1
100
- })
101
- let pub1 = TestPublisher.make(~name="topicX", ~storageResources=[], ~owner=None, ~opts={})
102
- let pub2 = TestPublisher.make(~name="topicY", ~storageResources=[], ~owner=None, ~opts={})
103
- let eventTopics = Dict.fromArray([
104
- ("topicX", {ReventlessInfra.EventTopic.resources: pub1.resources}),
105
- ("topicY", {ReventlessInfra.EventTopic.resources: pub2.resources}),
106
- ])
107
- let ch = TestCollector.make(~name="collector", ~eventTopics, ~owner=None, ~opts={})
108
- let channelSpec: ReventlessCore.EventCollector_Adapter.channelSpec<JSON.t, unit, unit> = {
109
- channel: ch,
110
- eventTopics,
111
- resources: [],
112
- }
113
- let _ = TestCollector.connect(
114
- ~name="collector",
115
- ~channelSpecs=[channelSpec],
116
- ~runtime,
117
- ~opts={},
118
- )
119
- // Wait for both subscriptions via the publish functions (each Output resolves per-topic)
120
- let pub1Fn = await pub1.publishJson->TestRunner.resolve
121
- let pub2Fn = await pub2.publishJson->TestRunner.resolve
122
- await pub1Fn("svc", testMeta, JSON.Null)
123
- await pub2Fn("svc", testMeta, JSON.Null)
124
- expect(received.contents)->toBe(2)
125
- })
109
+ testPromise(
110
+ "multiple event topics all deliver to the same handler",
111
+ async () => {
112
+ module TestBus = LocalBus.Make()
113
+ module TestPublisher = LocalEventTopicPublisher.Make(TestBus)
114
+ module TestCollector = LocalEventCollectorChannel.Make(TestBus)
115
+ let received: ref<int> = ref(0)
116
+ let runtime = makeRuntime(
117
+ async (_, _) => {
118
+ received := received.contents + 1
119
+ },
120
+ )
121
+ let pub1 = TestPublisher.make(~name="topicX", ~storageResources=[], ~owner=None, ~opts={})
122
+ let pub2 = TestPublisher.make(~name="topicY", ~storageResources=[], ~owner=None, ~opts={})
123
+ let eventTopics = Dict.fromArray([
124
+ ("topicX", {ReventlessInfra.EventTopic.resources: pub1.resources}),
125
+ ("topicY", {ReventlessInfra.EventTopic.resources: pub2.resources}),
126
+ ])
127
+ let ch = TestCollector.make(~name="collector", ~eventTopics, ~owner=None, ~opts={})
128
+ let channelSpec: ReventlessCore.EventCollector_Adapter.channelSpec<JSON.t, unit, unit> = {
129
+ channel: ch,
130
+ eventTopics,
131
+ resources: [],
132
+ }
133
+ let _ = TestCollector.connect(
134
+ ~name="collector",
135
+ ~channelSpecs=[channelSpec],
136
+ ~runtime,
137
+ ~opts={},
138
+ )
139
+ // Wait for both subscriptions via the publish functions (each Output resolves per-topic)
140
+ let pub1Fn = await pub1.publishJson->TestRunner.resolve
141
+ let pub2Fn = await pub2.publishJson->TestRunner.resolve
142
+ await pub1Fn("svc", testMeta, JSON.Null)
143
+ await pub2Fn("svc", testMeta, JSON.Null)
144
+ expect(received.contents)->toBe(2)
145
+ },
146
+ )
126
147
 
127
- testPromise("unresolved Deferred does not crash on event delivery", async () => {
128
- module TestBus = LocalBus.Make()
129
- module TestPublisher = LocalEventTopicPublisher.Make(TestBus)
130
- module TestCollector = LocalEventCollectorChannel.Make(TestBus)
131
- // Deferred left incomplete — bus callback starts a pending Effect (no crash)
132
- let handlerDeferred: Deferred.t<LocalRuntimeEnvironment.handler, unit> =
133
- Deferred.make()->Effect.runSync
134
- let runtime: ReventlessCore.Runtime.environment<LocalRuntimeEnvironment.parts> = {
135
- parts: {
136
- handlerDeferred,
137
- subscriptionLatch: Effect.makeLatch(false)->Effect.runSync,
138
- },
139
- resources: [],
140
- }
141
- let pub = TestPublisher.make(~name="noHandlerTopic", ~storageResources=[], ~owner=None, ~opts={})
142
- let eventTopics = Dict.fromArray([
143
- ("noHandlerTopic", {ReventlessInfra.EventTopic.resources: pub.resources}),
144
- ])
145
- let ch = TestCollector.make(~name="collector", ~eventTopics, ~owner=None, ~opts={})
146
- let channelSpec: ReventlessCore.EventCollector_Adapter.channelSpec<JSON.t, unit, unit> = {
147
- channel: ch,
148
- eventTopics,
149
- resources: [],
150
- }
151
- let _ = TestCollector.connect(
152
- ~name="collector",
153
- ~channelSpecs=[channelSpec],
154
- ~runtime,
155
- ~opts={},
156
- )
157
- await runtime.parts.subscriptionLatch->Latch.await_->Effect.runPromise
158
- let publishFn = await pub.publishJson->TestRunner.resolve
159
- // Fire-and-forget — the Deferred.await_ will stay pending
160
- publishFn("svc", testMeta, JSON.Null)->ignore
161
- expect(true)->toBe(true)
162
- // Complete the Deferred to avoid open handle
163
- Deferred.succeed(handlerDeferred, async (_, _) => ())->Effect.runSync->ignore
164
- })
148
+ testPromise(
149
+ "unresolved Deferred does not crash on event delivery",
150
+ async () => {
151
+ module TestBus = LocalBus.Make()
152
+ module TestPublisher = LocalEventTopicPublisher.Make(TestBus)
153
+ module TestCollector = LocalEventCollectorChannel.Make(TestBus)
154
+ // Deferred left incomplete — bus callback starts a pending Effect (no crash)
155
+ let handlerDeferred: Deferred.t<LocalRuntimeEnvironment.handler, unit> =
156
+ Deferred.make()->Effect.runSync
157
+ let runtime: ReventlessCore.Runtime.environment<LocalRuntimeEnvironment.parts> = {
158
+ parts: {
159
+ handlerDeferred,
160
+ subscriptionLatch: Effect.makeLatch(false)->Effect.runSync,
161
+ },
162
+ resources: [],
163
+ }
164
+ let pub = TestPublisher.make(
165
+ ~name="noHandlerTopic",
166
+ ~storageResources=[],
167
+ ~owner=None,
168
+ ~opts={},
169
+ )
170
+ let eventTopics = Dict.fromArray([
171
+ ("noHandlerTopic", {ReventlessInfra.EventTopic.resources: pub.resources}),
172
+ ])
173
+ let ch = TestCollector.make(~name="collector", ~eventTopics, ~owner=None, ~opts={})
174
+ let channelSpec: ReventlessCore.EventCollector_Adapter.channelSpec<JSON.t, unit, unit> = {
175
+ channel: ch,
176
+ eventTopics,
177
+ resources: [],
178
+ }
179
+ let _ = TestCollector.connect(
180
+ ~name="collector",
181
+ ~channelSpecs=[channelSpec],
182
+ ~runtime,
183
+ ~opts={},
184
+ )
185
+ await runtime.parts.subscriptionLatch->Latch.await_->Effect.runPromise
186
+ let publishFn = await pub.publishJson->TestRunner.resolve
187
+ // Fire-and-forget — the Deferred.await_ will stay pending
188
+ publishFn("svc", testMeta, JSON.Null)->ignore
189
+ expect(true)->toBe(true)
190
+ // Complete the Deferred to avoid open handle
191
+ Deferred.succeed(handlerDeferred, async (_, _) => ())->Effect.runSync->ignore
192
+ },
193
+ )
165
194
  })
166
195
  })
@@ -18,7 +18,14 @@ let metaOf = (~user=?, ~time="2026-01-01T00:00:00Z", ()) =>
18
18
  ("causationId", JSON.Encode.null),
19
19
  ])->JSON.Encode.object
20
20
 
21
- let rec_ = (~position, ~eventType="OrderPlaced", ~tags=[], ~user=?, ~time="2026-01-01T00:00:00Z", ()): EH.record => {
21
+ let rec_ = (
22
+ ~position,
23
+ ~eventType="OrderPlaced",
24
+ ~tags=[],
25
+ ~user=?,
26
+ ~time="2026-01-01T00:00:00Z",
27
+ (),
28
+ ): EH.record => {
22
29
  position,
23
30
  eventType,
24
31
  payload: JSON.Encode.object(Dict.make()),
@@ -86,9 +93,8 @@ describe("EventHistoryResolvers_GraphQL — position ordering", () => {
86
93
  })
87
94
 
88
95
  testSync("a cursor bound crossing a digit boundary skips nothing", () => {
89
- let records = Array.make(~length=12, 0)->Array.mapWithIndex((_, i) =>
90
- rec_(~position=i->Int.toString, ())
91
- )
96
+ let records =
97
+ Array.make(~length=12, 0)->Array.mapWithIndex((_, i) => rec_(~position=i->Int.toString, ()))
92
98
  // Page 1 ends at position 9; page 2 must start at 10, not at 1.
93
99
  let page1 = EH.paginate(~records, ~args=args(~first=10, ()))
94
100
  expect(page1->positionsOf->Array.length)->toBe(10)
@@ -101,9 +107,30 @@ describe("EventHistoryResolvers_GraphQL — position ordering", () => {
101
107
 
102
108
  describe("EventHistoryResolvers_GraphQL — filtering", () => {
103
109
  let records = [
104
- rec_(~position="1", ~eventType="OrderPlaced", ~tags=[tag("orderId", "o-1")], ~user="alice", ~time="2026-01-01T00:00:00Z", ()),
105
- rec_(~position="2", ~eventType="OrderShipped", ~tags=[tag("orderId", "o-1")], ~user="bob", ~time="2026-01-05T00:00:00Z", ()),
106
- rec_(~position="3", ~eventType="OrderPlaced", ~tags=[tag("orderId", "o-2")], ~user="alice", ~time="2026-01-09T00:00:00Z", ()),
110
+ rec_(
111
+ ~position="1",
112
+ ~eventType="OrderPlaced",
113
+ ~tags=[tag("orderId", "o-1")],
114
+ ~user="alice",
115
+ ~time="2026-01-01T00:00:00Z",
116
+ (),
117
+ ),
118
+ rec_(
119
+ ~position="2",
120
+ ~eventType="OrderShipped",
121
+ ~tags=[tag("orderId", "o-1")],
122
+ ~user="bob",
123
+ ~time="2026-01-05T00:00:00Z",
124
+ (),
125
+ ),
126
+ rec_(
127
+ ~position="3",
128
+ ~eventType="OrderPlaced",
129
+ ~tags=[tag("orderId", "o-2")],
130
+ ~user="alice",
131
+ ~time="2026-01-09T00:00:00Z",
132
+ (),
133
+ ),
107
134
  ]
108
135
  let filtered = f => records->Array.filter(r => EH.matchesFilter(r, f))->Array.map(r => r.position)
109
136
 
@@ -129,9 +156,9 @@ describe("EventHistoryResolvers_GraphQL — filtering", () => {
129
156
  testSync("time range is inclusive on both bounds", () => {
130
157
  expect(filtered({timeFrom: "2026-01-05T00:00:00Z"}))->toEqual(["2", "3"])
131
158
  expect(filtered({timeTo: "2026-01-05T00:00:00Z"}))->toEqual(["1", "2"])
132
- expect(
133
- filtered({timeFrom: "2026-01-05T00:00:00Z", timeTo: "2026-01-05T00:00:00Z"}),
134
- )->toEqual(["2"])
159
+ expect(filtered({timeFrom: "2026-01-05T00:00:00Z", timeTo: "2026-01-05T00:00:00Z"}))->toEqual([
160
+ "2",
161
+ ])
135
162
  })
136
163
 
137
164
  testSync("an empty filter keeps everything", () => {
@@ -154,10 +181,7 @@ describe("EventHistoryResolvers_GraphQL — argument decoding", () => {
154
181
  ("entityId", JSON.Encode.string("o-1")),
155
182
  ("tagKey", JSON.Encode.string("orderId")),
156
183
  ("tagValue", JSON.Encode.string("o-1")),
157
- (
158
- "eventTypes",
159
- [JSON.Encode.string("OrderPlaced")]->JSON.Encode.array,
160
- ),
184
+ ("eventTypes", [JSON.Encode.string("OrderPlaced")]->JSON.Encode.array),
161
185
  ("user", JSON.Encode.string("alice")),
162
186
  ("timeFrom", JSON.Encode.string("2026-01-01")),
163
187
  ("timeTo", JSON.Encode.string("2026-02-01")),
@@ -183,9 +207,10 @@ describe("EventHistoryResolvers_GraphQL — argument decoding", () => {
183
207
  })
184
208
 
185
209
  describe("EventHistoryResolvers_GraphQL — pagination", () => {
186
- let records = Array.make(~length=5, 0)->Array.mapWithIndex((_, i) =>
187
- rec_(~position=(i + 1)->Int.toString, ())
188
- )
210
+ let records =
211
+ Array.make(~length=5, 0)->Array.mapWithIndex((_, i) =>
212
+ rec_(~position=(i + 1)->Int.toString, ())
213
+ )
189
214
 
190
215
  testSync("forward paging reports hasNextPage until exhausted", () => {
191
216
  let page = EH.paginate(~records, ~args=args(~first=2, ()))
@@ -242,7 +267,10 @@ describe("EventHistoryResolvers_GraphQL — naming the caller", () => {
242
267
 
243
268
  testSync("say so when the caller sent no filter at all", () =>
244
269
  expect(
245
- EH.describeCaller(~ctx=ctxOf(~operationName="AuditTrail", ~userId="local-admin", ()), ~filter={}),
270
+ EH.describeCaller(
271
+ ~ctx=ctxOf(~operationName="AuditTrail", ~userId="local-admin", ()),
272
+ ~filter={},
273
+ ),
246
274
  )->toBe(`operation "AuditTrail" as local-admin, with no filter`)
247
275
  )
248
276
 
@@ -28,54 +28,66 @@ let backends = [("memory", makeMemoryOps), ("sqlite", makeSqliteOps)]
28
28
 
29
29
  describe("EventLog snapshot backend parity", () => {
30
30
  backends->Array.forEach(((label, makeOps)) => {
31
- testPromise(`${label}: latestSnapshot is None before any write`, async () => {
32
- let ops = await makeOps()
33
- expect(await ops.latestSnapshot("id-1"))->toEqual(Ok(None))
34
- })
31
+ testPromise(
32
+ `${label}: latestSnapshot is None before any write`,
33
+ async () => {
34
+ let ops = await makeOps()
35
+ expect(await ops.latestSnapshot("id-1"))->toEqual(Ok(None))
36
+ },
37
+ )
35
38
 
36
- testPromise(`${label}: snapshot round-trips and keep-one overwrites`, async () => {
37
- let ops = await makeOps()
38
- let state = JSON.Encode.object(Dict.fromArray([("n", JSON.Encode.int(1))]))
39
- expect(
40
- await ops.writeSnapshot(
39
+ testPromise(
40
+ `${label}: snapshot round-trips and keep-one overwrites`,
41
+ async () => {
42
+ let ops = await makeOps()
43
+ let state = JSON.Encode.object(Dict.fromArray([("n", JSON.Encode.int(1))]))
44
+ expect(
45
+ await ops.writeSnapshot(
46
+ "id-1",
47
+ {ReventlessCore.EventLog.seqNr: 2, state, schemaHash: "h1"},
48
+ ),
49
+ )->toEqual(Ok())
50
+ expect(await ops.latestSnapshot("id-1"))->toEqual(
51
+ Ok(Some({ReventlessCore.EventLog.seqNr: 2, state, schemaHash: "h1"})),
52
+ )
53
+ let state2 = JSON.Encode.object(Dict.fromArray([("n", JSON.Encode.int(9))]))
54
+ let _ = await ops.writeSnapshot(
41
55
  "id-1",
42
- {ReventlessCore.EventLog.seqNr: 2, state, schemaHash: "h1"},
43
- ),
44
- )->toEqual(Ok())
45
- expect(await ops.latestSnapshot("id-1"))->toEqual(
46
- Ok(Some({ReventlessCore.EventLog.seqNr: 2, state, schemaHash: "h1"})),
47
- )
48
- let state2 = JSON.Encode.object(Dict.fromArray([("n", JSON.Encode.int(9))]))
49
- let _ = await ops.writeSnapshot(
50
- "id-1",
51
- {ReventlessCore.EventLog.seqNr: 9, state: state2, schemaHash: "h2"},
52
- )
53
- expect(await ops.latestSnapshot("id-1"))->toEqual(
54
- Ok(Some({ReventlessCore.EventLog.seqNr: 9, state: state2, schemaHash: "h2"})),
55
- )
56
- })
56
+ {ReventlessCore.EventLog.seqNr: 9, state: state2, schemaHash: "h2"},
57
+ )
58
+ expect(await ops.latestSnapshot("id-1"))->toEqual(
59
+ Ok(Some({ReventlessCore.EventLog.seqNr: 9, state: state2, schemaHash: "h2"})),
60
+ )
61
+ },
62
+ )
57
63
 
58
- testPromise(`${label}: snapshots are per-aggregate-id`, async () => {
59
- let ops = await makeOps()
60
- let state = JSON.Encode.object(Dict.fromArray([("n", JSON.Encode.int(1))]))
61
- let _ = await ops.writeSnapshot(
62
- "id-a",
63
- {ReventlessCore.EventLog.seqNr: 1, state, schemaHash: "h"},
64
- )
65
- expect(await ops.latestSnapshot("id-b"))->toEqual(Ok(None))
66
- })
64
+ testPromise(
65
+ `${label}: snapshots are per-aggregate-id`,
66
+ async () => {
67
+ let ops = await makeOps()
68
+ let state = JSON.Encode.object(Dict.fromArray([("n", JSON.Encode.int(1))]))
69
+ let _ = await ops.writeSnapshot(
70
+ "id-a",
71
+ {ReventlessCore.EventLog.seqNr: 1, state, schemaHash: "h"},
72
+ )
73
+ expect(await ops.latestSnapshot("id-b"))->toEqual(Ok(None))
74
+ },
75
+ )
67
76
 
68
- testPromise(`${label}: replayStream ~fromSeq equals the tail of a full replay`, async () => {
69
- let ops = await makeOps()
70
- let ev = i => JSON.Encode.object(Dict.fromArray([("i", JSON.Encode.int(i))]))
71
- let _ = await ops.append(0, "id-1", [ev(0), ev(1), ev(2), ev(3), ev(4)])
72
- let delta = await ops.replayStream("id-1", ~fromSeq=3)->Stream.runCollect->Effect.runPromise
73
- expect(delta)->toEqual([ev(3), ev(4)])
74
- // fromSeq at the head yields nothing; omitted fromSeq yields everything.
75
- let empty = await ops.replayStream("id-1", ~fromSeq=5)->Stream.runCollect->Effect.runPromise
76
- expect(empty)->toEqual([])
77
- let full = await ops.replayStream("id-1")->Stream.runCollect->Effect.runPromise
78
- expect(full->Array.length)->toBe(5)
79
- })
77
+ testPromise(
78
+ `${label}: replayStream ~fromSeq equals the tail of a full replay`,
79
+ async () => {
80
+ let ops = await makeOps()
81
+ let ev = i => JSON.Encode.object(Dict.fromArray([("i", JSON.Encode.int(i))]))
82
+ let _ = await ops.append(0, "id-1", [ev(0), ev(1), ev(2), ev(3), ev(4)])
83
+ let delta = await ops.replayStream("id-1", ~fromSeq=3)->Stream.runCollect->Effect.runPromise
84
+ expect(delta)->toEqual([ev(3), ev(4)])
85
+ // fromSeq at the head yields nothing; omitted fromSeq yields everything.
86
+ let empty = await ops.replayStream("id-1", ~fromSeq=5)->Stream.runCollect->Effect.runPromise
87
+ expect(empty)->toEqual([])
88
+ let full = await ops.replayStream("id-1")->Stream.runCollect->Effect.runPromise
89
+ expect(full->Array.length)->toBe(5)
90
+ },
91
+ )
80
92
  })
81
93
  })
@@ -19,12 +19,8 @@ describe("EventLogStorage_Sqlite", () => {
19
19
  let s = Storage.make(~name="agg", ~owner=None, ~opts)
20
20
  let ops = await s.operations->TestRunner.resolve
21
21
 
22
- let event1 = JSON.Encode.object(
23
- Dict.fromArray([("tag", JSON.Encode.string("Created"))]),
24
- )
25
- let event2 = JSON.Encode.object(
26
- Dict.fromArray([("tag", JSON.Encode.string("Renamed"))]),
27
- )
22
+ let event1 = JSON.Encode.object(Dict.fromArray([("tag", JSON.Encode.string("Created"))]))
23
+ let event2 = JSON.Encode.object(Dict.fromArray([("tag", JSON.Encode.string("Renamed"))]))
28
24
 
29
25
  let r1 = await ops.append(0, "id-1", [event1])
30
26
  expect(r1)->toEqual(Ok())
@@ -98,9 +94,11 @@ describe("EventLogStorage_Sqlite", () => {
98
94
  let ev = i => JSON.Encode.object(Dict.fromArray([("i", JSON.Encode.int(i))]))
99
95
  // Seed one event, then stream three more starting at seq 1.
100
96
  let _ = await ops.append(0, "id-s", [ev(0)])
101
- let _ =
102
- await ops.appendStream(1, "id-s", Stream.fromIterable([ev(1), ev(2), ev(3)]))
103
- ->Effect.runPromise
97
+ let _ = await ops.appendStream(
98
+ 1,
99
+ "id-s",
100
+ Stream.fromIterable([ev(1), ev(2), ev(3)]),
101
+ )->Effect.runPromise
104
102
  let replayed = await ops.replay("id-s")
105
103
  expect(replayed->Array.length)->toBe(4)
106
104
  expect(replayed->Array.getUnsafe(0))->toEqual(ev(0))
@@ -206,8 +204,9 @@ describe("EventLogStorage_Sqlite", () => {
206
204
  testPromise("events persist across a reopen of the database file", async () => {
207
205
  let path = `/tmp/reventless-test-eventlog-${Float.toString(Date.now())}.db`
208
206
 
209
- // Session 1: write
210
207
  {
208
+ // Session 1: write
209
+
211
210
  module TestBus = LocalBus.Make()
212
211
  module DbProvider = {
213
212
  let db = SqliteDriver.openDb(~path)