@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
@@ -16,159 +16,183 @@ let testMeta: Reventless.Message.meta = {
16
16
 
17
17
  describe("LocalCommandTopicChannel", () => {
18
18
  describe("encodeCommandJson", () => {
19
- testPromise("produces {id, meta, command} JSON shape", async () => {
20
- let cmdJson: Reventless.Message.commandJson = {
21
- id: "agg-1",
22
- meta: testMeta,
23
- commandJson: JSON.Encode.string("DoSomething"),
24
- }
25
- let encoded = ReventlessCore.CommandTopic.encodeCommandJson(cmdJson)
26
- let dict = switch encoded {
27
- | JSON.Object(d) => d
28
- | _ => Dict.make()
29
- }
30
- expect(dict->Dict.get("id"))->toEqual(Some(JSON.Encode.string("agg-1")))
31
- expect(dict->Dict.get("command"))->toEqual(Some(JSON.Encode.string("DoSomething")))
32
- expect(dict->Dict.get("meta")->Option.isSome)->toBe(true)
33
- })
19
+ testPromise(
20
+ "produces {id, meta, command} JSON shape",
21
+ async () => {
22
+ let cmdJson: Reventless.Message.commandJson = {
23
+ id: "agg-1",
24
+ meta: testMeta,
25
+ commandJson: JSON.Encode.string("DoSomething"),
26
+ }
27
+ let encoded = ReventlessCore.CommandTopic.encodeCommandJson(cmdJson)
28
+ let dict = switch encoded {
29
+ | JSON.Object(d) => d
30
+ | _ => Dict.make()
31
+ }
32
+ expect(dict->Dict.get("id"))->toEqual(Some(JSON.Encode.string("agg-1")))
33
+ expect(dict->Dict.get("command"))->toEqual(Some(JSON.Encode.string("DoSomething")))
34
+ expect(dict->Dict.get("meta")->Option.isSome)->toBe(true)
35
+ },
36
+ )
34
37
  })
35
38
 
36
39
  describe("decodeId", () => {
37
- testPromise("extracts id string from encoded message body", async () => {
38
- module TestBus = LocalBus.Make()
39
- module TestChannel = LocalCommandTopicChannel.Make(TestBus)
40
- let body = JSON.Encode.object(
41
- Dict.fromArray([("id", JSON.Encode.string("agg-42"))]),
42
- )
43
- expect(TestChannel.decodeId(body))->toBe("agg-42")
44
- })
40
+ testPromise(
41
+ "extracts id string from encoded message body",
42
+ async () => {
43
+ module TestBus = LocalBus.Make()
44
+ module TestChannel = LocalCommandTopicChannel.Make(TestBus)
45
+ let body = JSON.Encode.object(Dict.fromArray([("id", JSON.Encode.string("agg-42"))]))
46
+ expect(TestChannel.decodeId(body))->toBe("agg-42")
47
+ },
48
+ )
45
49
 
46
- testPromise("returns empty string for non-object input", async () => {
47
- module TestBus = LocalBus.Make()
48
- module TestChannel = LocalCommandTopicChannel.Make(TestBus)
49
- expect(TestChannel.decodeId(JSON.Encode.string("not-an-object")))->toBe("")
50
- })
50
+ testPromise(
51
+ "returns empty string for non-object input",
52
+ async () => {
53
+ module TestBus = LocalBus.Make()
54
+ module TestChannel = LocalCommandTopicChannel.Make(TestBus)
55
+ expect(TestChannel.decodeId(JSON.Encode.string("not-an-object")))->toBe("")
56
+ },
57
+ )
51
58
 
52
- testPromise("returns empty string when id field is missing", async () => {
53
- module TestBus = LocalBus.Make()
54
- module TestChannel = LocalCommandTopicChannel.Make(TestBus)
55
- let body = JSON.Encode.object(Dict.fromArray([("other", JSON.Encode.int(1))]))
56
- expect(TestChannel.decodeId(body))->toBe("")
57
- })
59
+ testPromise(
60
+ "returns empty string when id field is missing",
61
+ async () => {
62
+ module TestBus = LocalBus.Make()
63
+ module TestChannel = LocalCommandTopicChannel.Make(TestBus)
64
+ let body = JSON.Encode.object(Dict.fromArray([("other", JSON.Encode.int(1))]))
65
+ expect(TestChannel.decodeId(body))->toBe("")
66
+ },
67
+ )
58
68
 
59
- testPromise("returns empty string when id field is not a string", async () => {
60
- module TestBus = LocalBus.Make()
61
- module TestChannel = LocalCommandTopicChannel.Make(TestBus)
62
- let body = JSON.Encode.object(Dict.fromArray([("id", JSON.Encode.int(99))]))
63
- expect(TestChannel.decodeId(body))->toBe("")
64
- })
69
+ testPromise(
70
+ "returns empty string when id field is not a string",
71
+ async () => {
72
+ module TestBus = LocalBus.Make()
73
+ module TestChannel = LocalCommandTopicChannel.Make(TestBus)
74
+ let body = JSON.Encode.object(Dict.fromArray([("id", JSON.Encode.int(99))]))
75
+ expect(TestChannel.decodeId(body))->toBe("")
76
+ },
77
+ )
65
78
  })
66
79
 
67
80
  describe("publishJsons", () => {
68
- testPromise("dispatches each command to the bus with full {id, meta, command} body", async () => {
69
- module TestBus = LocalBus.Make()
70
- module TestChannel = LocalCommandTopicChannel.Make(TestBus)
71
- let received: ref<option<JSON.t>> = ref(None)
72
- TestBus.registerCommandHandler("myCmd", async (json, _) => {
73
- received := Some(json)
74
- })
75
- let ch = TestChannel.make(~name="myCmd", ~owner=None)
76
- let publishFn = await ch.publishJsons->TestRunner.resolve
77
- await publishFn([
78
- {
79
- Reventless.Message.id: "item-1",
80
- meta: testMeta,
81
- commandJson: JSON.Encode.string("CreateItem"),
82
- },
83
- ])
84
- let receivedJson = received.contents->Option.getOr(JSON.Null)
85
- let receivedDict = switch receivedJson {
86
- | JSON.Object(d) => d
87
- | _ => Dict.make()
88
- }
89
- expect(receivedDict->Dict.get("id"))->toEqual(Some(JSON.Encode.string("item-1")))
90
- })
81
+ testPromise(
82
+ "dispatches each command to the bus with full {id, meta, command} body",
83
+ async () => {
84
+ module TestBus = LocalBus.Make()
85
+ module TestChannel = LocalCommandTopicChannel.Make(TestBus)
86
+ let received: ref<option<JSON.t>> = ref(None)
87
+ TestBus.registerCommandHandler(
88
+ "myCmd",
89
+ async (json, _) => {
90
+ received := Some(json)
91
+ },
92
+ )
93
+ let ch = TestChannel.make(~name="myCmd", ~owner=None)
94
+ let publishFn = await ch.publishJsons->TestRunner.resolve
95
+ await publishFn([
96
+ {
97
+ Reventless.Message.id: "item-1",
98
+ meta: testMeta,
99
+ commandJson: JSON.Encode.string("CreateItem"),
100
+ },
101
+ ])
102
+ let receivedJson = received.contents->Option.getOr(JSON.Null)
103
+ let receivedDict = switch receivedJson {
104
+ | JSON.Object(d) => d
105
+ | _ => Dict.make()
106
+ }
107
+ expect(receivedDict->Dict.get("id"))->toEqual(Some(JSON.Encode.string("item-1")))
108
+ },
109
+ )
91
110
 
92
- testPromise("dispatches multiple commands in order", async () => {
93
- module TestBus = LocalBus.Make()
94
- module TestChannel = LocalCommandTopicChannel.Make(TestBus)
95
- let count: ref<int> = ref(0)
96
- TestBus.registerCommandHandler("multiCmd", async (_, _) => {
97
- count := count.contents + 1
98
- })
99
- let ch = TestChannel.make(~name="multiCmd", ~owner=None)
100
- let publishFn = await ch.publishJsons->TestRunner.resolve
101
- await publishFn([
102
- {
103
- Reventless.Message.id: "id-1",
104
- meta: testMeta,
105
- commandJson: JSON.Null,
106
- },
107
- {
108
- Reventless.Message.id: "id-2",
109
- meta: testMeta,
110
- commandJson: JSON.Null,
111
- },
112
- ])
113
- expect(count.contents)->toBe(2)
114
- })
111
+ testPromise(
112
+ "dispatches multiple commands in order",
113
+ async () => {
114
+ module TestBus = LocalBus.Make()
115
+ module TestChannel = LocalCommandTopicChannel.Make(TestBus)
116
+ let count: ref<int> = ref(0)
117
+ TestBus.registerCommandHandler(
118
+ "multiCmd",
119
+ async (_, _) => {
120
+ count := count.contents + 1
121
+ },
122
+ )
123
+ let ch = TestChannel.make(~name="multiCmd", ~owner=None)
124
+ let publishFn = await ch.publishJsons->TestRunner.resolve
125
+ await publishFn([
126
+ {
127
+ Reventless.Message.id: "id-1",
128
+ meta: testMeta,
129
+ commandJson: JSON.Null,
130
+ },
131
+ {
132
+ Reventless.Message.id: "id-2",
133
+ meta: testMeta,
134
+ commandJson: JSON.Null,
135
+ },
136
+ ])
137
+ expect(count.contents)->toBe(2)
138
+ },
139
+ )
115
140
  })
116
141
 
117
142
  describe("connect", () => {
118
- testPromise("registers handler; dispatch reaches the runtime handlerDeferred", async () => {
119
- module TestBus = LocalBus.Make()
120
- module TestChannel = LocalCommandTopicChannel.Make(TestBus)
121
- let received: ref<option<JSON.t>> = ref(None)
122
- let handlerDeferred: Deferred.t<LocalRuntimeEnvironment.handler, unit> =
123
- Deferred.make()->Effect.runSync
124
- Deferred.succeed(handlerDeferred, async (json, _ctx) => {
125
- received := Some(json)
126
- })->Effect.runSync->ignore
127
- let runtime: ReventlessCore.Runtime.environment<LocalRuntimeEnvironment.parts> = {
128
- parts: {
143
+ testPromise(
144
+ "registers handler; dispatch reaches the runtime handlerDeferred",
145
+ async () => {
146
+ module TestBus = LocalBus.Make()
147
+ module TestChannel = LocalCommandTopicChannel.Make(TestBus)
148
+ let received: ref<option<JSON.t>> = ref(None)
149
+ let handlerDeferred: Deferred.t<LocalRuntimeEnvironment.handler, unit> =
150
+ Deferred.make()->Effect.runSync
151
+ Deferred.succeed(
129
152
  handlerDeferred,
130
- subscriptionLatch: Effect.makeLatch(false)->Effect.runSync,
131
- },
132
- resources: [],
133
- }
134
- let ch = TestChannel.make(~name="testCmd", ~owner=None)
135
- let _ = ch.connect(
136
- ~name="testCmd",
137
- ~channel=ch,
138
- ~runtime,
139
- ~resources=[],
140
- ~opts={},
141
- )
142
- await TestBus.dispatchCommand("testCmd", JSON.Encode.string("test-payload"))
143
- expect(received.contents)->toEqual(Some(JSON.Encode.string("test-payload")))
144
- })
153
+ async (json, _ctx) => {
154
+ received := Some(json)
155
+ },
156
+ )
157
+ ->Effect.runSync
158
+ ->ignore
159
+ let runtime: ReventlessCore.Runtime.environment<LocalRuntimeEnvironment.parts> = {
160
+ parts: {
161
+ handlerDeferred,
162
+ subscriptionLatch: Effect.makeLatch(false)->Effect.runSync,
163
+ },
164
+ resources: [],
165
+ }
166
+ let ch = TestChannel.make(~name="testCmd", ~owner=None)
167
+ let _ = ch.connect(~name="testCmd", ~channel=ch, ~runtime, ~resources=[], ~opts={})
168
+ await TestBus.dispatchCommand("testCmd", JSON.Encode.string("test-payload"))
169
+ expect(received.contents)->toEqual(Some(JSON.Encode.string("test-payload")))
170
+ },
171
+ )
145
172
 
146
- testPromise("unresolved Deferred does not crash on dispatch", async () => {
147
- module TestBus = LocalBus.Make()
148
- module TestChannel = LocalCommandTopicChannel.Make(TestBus)
149
- // Deferred left incomplete — dispatch starts a pending Effect (no crash)
150
- let handlerDeferred: Deferred.t<LocalRuntimeEnvironment.handler, unit> =
151
- Deferred.make()->Effect.runSync
152
- let runtime: ReventlessCore.Runtime.environment<LocalRuntimeEnvironment.parts> = {
153
- parts: {
154
- handlerDeferred,
155
- subscriptionLatch: Effect.makeLatch(false)->Effect.runSync,
156
- },
157
- resources: [],
158
- }
159
- let ch = TestChannel.make(~name="noHandlerCmd", ~owner=None)
160
- let _ = ch.connect(
161
- ~name="noHandlerCmd",
162
- ~channel=ch,
163
- ~runtime,
164
- ~resources=[],
165
- ~opts={},
166
- )
167
- // Fire-and-forget the Deferred.await_ will stay pending
168
- TestBus.dispatchCommand("noHandlerCmd", JSON.Null)->ignore
169
- expect(true)->toBe(true)
170
- // Complete the Deferred to avoid open handle
171
- Deferred.succeed(handlerDeferred, async (_, _) => ())->Effect.runSync->ignore
172
- })
173
+ testPromise(
174
+ "unresolved Deferred does not crash on dispatch",
175
+ async () => {
176
+ module TestBus = LocalBus.Make()
177
+ module TestChannel = LocalCommandTopicChannel.Make(TestBus)
178
+ // Deferred left incomplete — dispatch starts a pending Effect (no crash)
179
+ let handlerDeferred: Deferred.t<LocalRuntimeEnvironment.handler, unit> =
180
+ Deferred.make()->Effect.runSync
181
+ let runtime: ReventlessCore.Runtime.environment<LocalRuntimeEnvironment.parts> = {
182
+ parts: {
183
+ handlerDeferred,
184
+ subscriptionLatch: Effect.makeLatch(false)->Effect.runSync,
185
+ },
186
+ resources: [],
187
+ }
188
+ let ch = TestChannel.make(~name="noHandlerCmd", ~owner=None)
189
+ let _ = ch.connect(~name="noHandlerCmd", ~channel=ch, ~runtime, ~resources=[], ~opts={})
190
+ // Fire-and-forget — the Deferred.await_ will stay pending
191
+ TestBus.dispatchCommand("noHandlerCmd", JSON.Null)->ignore
192
+ expect(true)->toBe(true)
193
+ // Complete the Deferred to avoid open handle
194
+ Deferred.succeed(handlerDeferred, async (_, _) => ())->Effect.runSync->ignore
195
+ },
196
+ )
173
197
  })
174
198
  })
@@ -14,10 +14,10 @@ let makeHandler = () =>
14
14
  LocalCounterHandler.make(
15
15
  ~name="counter",
16
16
  ~referencesName="refs",
17
- ~referencesDb=Obj.magic(()),
17
+ ~referencesDb=Obj.magic(),
18
18
  ~countsName="counts",
19
- ~countsDb=Obj.magic(()),
20
- ~counterHandler=Obj.magic(()),
19
+ ~countsDb=Obj.magic(),
20
+ ~counterHandler=Obj.magic(),
21
21
  ~specModulePath="",
22
22
  ~mappingsModulePath="",
23
23
  ~publishChannelId=Pulumi.Output.make(""),
@@ -26,88 +26,106 @@ let makeHandler = () =>
26
26
 
27
27
  describe("LocalCounterHandler", () => {
28
28
  describe("addToCounterTarget", () => {
29
- testPromise("increments counter by target amount", async () => {
30
- let handler = makeHandler()
31
- await handler.addToCounterTarget({
32
- ReventlessInfra.Counter.counterId: "likes",
33
- target: 3,
34
- targetRef: "ref-1",
35
- })
36
- expect(LocalCounterHandler.getCount("likes"))->toBe(3)
37
- })
29
+ testPromise(
30
+ "increments counter by target amount",
31
+ async () => {
32
+ let handler = makeHandler()
33
+ await handler.addToCounterTarget({
34
+ ReventlessInfra.Counter.counterId: "likes",
35
+ target: 3,
36
+ targetRef: "ref-1",
37
+ })
38
+ expect(LocalCounterHandler.getCount("likes"))->toBe(3)
39
+ },
40
+ )
38
41
 
39
- testPromise("same (counterId, targetRef) pair is counted only once", async () => {
40
- let handler = makeHandler()
41
- await handler.addToCounterTarget({
42
- ReventlessInfra.Counter.counterId: "likes",
43
- target: 5,
44
- targetRef: "ref-dup",
45
- })
46
- // Duplicate — should be ignored
47
- await handler.addToCounterTarget({
48
- ReventlessInfra.Counter.counterId: "likes",
49
- target: 5,
50
- targetRef: "ref-dup",
51
- })
52
- expect(LocalCounterHandler.getCount("likes"))->toBe(5)
53
- })
42
+ testPromise(
43
+ "same (counterId, targetRef) pair is counted only once",
44
+ async () => {
45
+ let handler = makeHandler()
46
+ await handler.addToCounterTarget({
47
+ ReventlessInfra.Counter.counterId: "likes",
48
+ target: 5,
49
+ targetRef: "ref-dup",
50
+ })
51
+ // Duplicate — should be ignored
52
+ await handler.addToCounterTarget({
53
+ ReventlessInfra.Counter.counterId: "likes",
54
+ target: 5,
55
+ targetRef: "ref-dup",
56
+ })
57
+ expect(LocalCounterHandler.getCount("likes"))->toBe(5)
58
+ },
59
+ )
54
60
 
55
- testPromise("different targetRef values for same counterId accumulate", async () => {
56
- let handler = makeHandler()
57
- await handler.addToCounterTarget({
58
- ReventlessInfra.Counter.counterId: "views",
59
- target: 2,
60
- targetRef: "ref-a",
61
- })
62
- await handler.addToCounterTarget({
63
- ReventlessInfra.Counter.counterId: "views",
64
- target: 3,
65
- targetRef: "ref-b",
66
- })
67
- expect(LocalCounterHandler.getCount("views"))->toBe(5)
68
- })
61
+ testPromise(
62
+ "different targetRef values for same counterId accumulate",
63
+ async () => {
64
+ let handler = makeHandler()
65
+ await handler.addToCounterTarget({
66
+ ReventlessInfra.Counter.counterId: "views",
67
+ target: 2,
68
+ targetRef: "ref-a",
69
+ })
70
+ await handler.addToCounterTarget({
71
+ ReventlessInfra.Counter.counterId: "views",
72
+ target: 3,
73
+ targetRef: "ref-b",
74
+ })
75
+ expect(LocalCounterHandler.getCount("views"))->toBe(5)
76
+ },
77
+ )
69
78
 
70
- testPromise("multiple counters are tracked independently", async () => {
71
- let handler = makeHandler()
72
- await handler.addToCounterTarget({
73
- ReventlessInfra.Counter.counterId: "alpha",
74
- target: 10,
75
- targetRef: "r1",
76
- })
77
- await handler.addToCounterTarget({
78
- ReventlessInfra.Counter.counterId: "beta",
79
- target: 7,
80
- targetRef: "r1",
81
- })
82
- expect(LocalCounterHandler.getCount("alpha"))->toBe(10)
83
- expect(LocalCounterHandler.getCount("beta"))->toBe(7)
84
- })
79
+ testPromise(
80
+ "multiple counters are tracked independently",
81
+ async () => {
82
+ let handler = makeHandler()
83
+ await handler.addToCounterTarget({
84
+ ReventlessInfra.Counter.counterId: "alpha",
85
+ target: 10,
86
+ targetRef: "r1",
87
+ })
88
+ await handler.addToCounterTarget({
89
+ ReventlessInfra.Counter.counterId: "beta",
90
+ target: 7,
91
+ targetRef: "r1",
92
+ })
93
+ expect(LocalCounterHandler.getCount("alpha"))->toBe(10)
94
+ expect(LocalCounterHandler.getCount("beta"))->toBe(7)
95
+ },
96
+ )
85
97
  })
86
98
 
87
99
  describe("getCount", () => {
88
- testPromise("returns 0 for unknown counterId", async () => {
89
- expect(LocalCounterHandler.getCount("no-such-counter"))->toBe(0)
90
- })
100
+ testPromise(
101
+ "returns 0 for unknown counterId",
102
+ async () => {
103
+ expect(LocalCounterHandler.getCount("no-such-counter"))->toBe(0)
104
+ },
105
+ )
91
106
  })
92
107
 
93
108
  describe("reset", () => {
94
- testPromise("clears all counter values and deduplication state", async () => {
95
- let handler = makeHandler()
96
- await handler.addToCounterTarget({
97
- ReventlessInfra.Counter.counterId: "x",
98
- target: 99,
99
- targetRef: "ref-x",
100
- })
101
- expect(LocalCounterHandler.getCount("x"))->toBe(99)
102
- LocalCounterHandler.reset()
103
- expect(LocalCounterHandler.getCount("x"))->toBe(0)
104
- // After reset, the same (counterId, targetRef) pair should count again
105
- await handler.addToCounterTarget({
106
- ReventlessInfra.Counter.counterId: "x",
107
- target: 99,
108
- targetRef: "ref-x",
109
- })
110
- expect(LocalCounterHandler.getCount("x"))->toBe(99)
111
- })
109
+ testPromise(
110
+ "clears all counter values and deduplication state",
111
+ async () => {
112
+ let handler = makeHandler()
113
+ await handler.addToCounterTarget({
114
+ ReventlessInfra.Counter.counterId: "x",
115
+ target: 99,
116
+ targetRef: "ref-x",
117
+ })
118
+ expect(LocalCounterHandler.getCount("x"))->toBe(99)
119
+ LocalCounterHandler.reset()
120
+ expect(LocalCounterHandler.getCount("x"))->toBe(0)
121
+ // After reset, the same (counterId, targetRef) pair should count again
122
+ await handler.addToCounterTarget({
123
+ ReventlessInfra.Counter.counterId: "x",
124
+ target: 99,
125
+ targetRef: "ref-x",
126
+ })
127
+ expect(LocalCounterHandler.getCount("x"))->toBe(99)
128
+ },
129
+ )
112
130
  })
113
131
  })