@reventlessdev/reventless-local 3.0.0-alpha.113

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 (334) hide show
  1. package/CHANGELOG.md +1197 -0
  2. package/LICENSE +202 -0
  3. package/__mocks__/emptyModule.js +2 -0
  4. package/__mocks__/nodeSqlite.mjs +6 -0
  5. package/package.json +67 -0
  6. package/rescript.json +39 -0
  7. package/src/Platform.res +2162 -0
  8. package/src/Platform.res.mjs +3389 -0
  9. package/src/adapter/Api/LocalGraphQL_Adapter.res +29 -0
  10. package/src/adapter/Api/LocalGraphQL_Adapter.res.mjs +26 -0
  11. package/src/adapter/Api/LocalGraphQL_SubscriptionResolvers.res +146 -0
  12. package/src/adapter/Api/LocalGraphQL_SubscriptionResolvers.res.mjs +88 -0
  13. package/src/adapter/Auth/Auth_GraphqlContext.res +83 -0
  14. package/src/adapter/Auth/Auth_GraphqlContext.res.mjs +76 -0
  15. package/src/adapter/Auth/LocalAuth.res +239 -0
  16. package/src/adapter/Auth/LocalAuth.res.mjs +247 -0
  17. package/src/adapter/Auth/UserStore.res +134 -0
  18. package/src/adapter/Auth/UserStore.res.mjs +146 -0
  19. package/src/adapter/Backend.res +46 -0
  20. package/src/adapter/Backend.res.mjs +66 -0
  21. package/src/adapter/BackendState.res +34 -0
  22. package/src/adapter/BackendState.res.mjs +42 -0
  23. package/src/adapter/Cloner/LocalClonerRunner.res +13 -0
  24. package/src/adapter/Cloner/LocalClonerRunner.res.mjs +12 -0
  25. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +329 -0
  26. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res.mjs +301 -0
  27. package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +76 -0
  28. package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res.mjs +57 -0
  29. package/src/adapter/CommandGenerator/LocalCommandGeneratorResolvers.res +19 -0
  30. package/src/adapter/CommandGenerator/LocalCommandGeneratorResolvers.res.mjs +19 -0
  31. package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +104 -0
  32. package/src/adapter/CommandTopic/LocalCommandTopicChannel.res.mjs +82 -0
  33. package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +32 -0
  34. package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res.mjs +38 -0
  35. package/src/adapter/Counter/LocalCounterHandler.res +42 -0
  36. package/src/adapter/Counter/LocalCounterHandler.res.mjs +45 -0
  37. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +131 -0
  38. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res.mjs +140 -0
  39. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +334 -0
  40. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res.mjs +299 -0
  41. package/src/adapter/DomainGraphQL_Server.res +565 -0
  42. package/src/adapter/DomainGraphQL_Server.res.mjs +576 -0
  43. package/src/adapter/DomainMCP_Server.res +369 -0
  44. package/src/adapter/DomainMCP_Server.res.mjs +358 -0
  45. package/src/adapter/EventCollector/LocalEventCollectorChannel.res +87 -0
  46. package/src/adapter/EventCollector/LocalEventCollectorChannel.res.mjs +45 -0
  47. package/src/adapter/EventLog/EventLogStorage_InMemory.res +105 -0
  48. package/src/adapter/EventLog/EventLogStorage_InMemory.res.mjs +117 -0
  49. package/src/adapter/EventLog/EventLogStorage_Sqlite.res +145 -0
  50. package/src/adapter/EventLog/EventLogStorage_Sqlite.res.mjs +155 -0
  51. package/src/adapter/EventTopic/LocalEventTopicPublisher.res +36 -0
  52. package/src/adapter/EventTopic/LocalEventTopicPublisher.res.mjs +31 -0
  53. package/src/adapter/GraphQL_Server.res +6 -0
  54. package/src/adapter/GraphQL_Server.res.mjs +154 -0
  55. package/src/adapter/GraphQL_ServerInstance.res +310 -0
  56. package/src/adapter/GraphQL_ServerInstance.res.mjs +257 -0
  57. package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +57 -0
  58. package/src/adapter/Heartbeat/LocalHeartbeatRunner.res.mjs +34 -0
  59. package/src/adapter/LocalBus.res +498 -0
  60. package/src/adapter/LocalBus.res.mjs +1011 -0
  61. package/src/adapter/LocalPluginSpec.res +16 -0
  62. package/src/adapter/LocalPluginSpec.res.mjs +33 -0
  63. package/src/adapter/LocalSideEffectHandler.res +44 -0
  64. package/src/adapter/LocalSideEffectHandler.res.mjs +34 -0
  65. package/src/adapter/MCP_Server.res +4 -0
  66. package/src/adapter/MCP_Server.res.mjs +67 -0
  67. package/src/adapter/MCP_ServerInstance.res +362 -0
  68. package/src/adapter/MCP_ServerInstance.res.mjs +313 -0
  69. package/src/adapter/PlatformGraphQL_Server.res +75 -0
  70. package/src/adapter/PlatformGraphQL_Server.res.mjs +84 -0
  71. package/src/adapter/PlatformMCP_Server.res +23 -0
  72. package/src/adapter/PlatformMCP_Server.res.mjs +46 -0
  73. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +793 -0
  74. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +569 -0
  75. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +242 -0
  76. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res.mjs +241 -0
  77. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +339 -0
  78. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res.mjs +318 -0
  79. package/src/adapter/QueryDb/SortKey_Filter.res +48 -0
  80. package/src/adapter/QueryDb/SortKey_Filter.res.mjs +46 -0
  81. package/src/adapter/QueryEngine/LocalQueryEngine.res +57 -0
  82. package/src/adapter/QueryEngine/LocalQueryEngine.res.mjs +56 -0
  83. package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +91 -0
  84. package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res.mjs +50 -0
  85. package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +47 -0
  86. package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res.mjs +37 -0
  87. package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +11 -0
  88. package/src/adapter/Runtime/LocalPluginRuntime_Builder.res.mjs +30 -0
  89. package/src/adapter/Runtime/LocalRuntimeEnvironment.res +54 -0
  90. package/src/adapter/Runtime/LocalRuntimeEnvironment.res.mjs +40 -0
  91. package/src/adapter/Scheduler/LocalScheduledPublisher.res +102 -0
  92. package/src/adapter/Scheduler/LocalScheduledPublisher.res.mjs +84 -0
  93. package/src/adapter/SqliteDriver.res +52 -0
  94. package/src/adapter/SqliteDriver.res.mjs +76 -0
  95. package/src/adapter/SqliteDriver.resi +31 -0
  96. package/src/adapter/Task/LocalTaskBucket.res +75 -0
  97. package/src/adapter/Task/LocalTaskBucket.res.mjs +48 -0
  98. package/src/adapter/Task/TaskBucket_Sqlite.res +47 -0
  99. package/src/adapter/Task/TaskBucket_Sqlite.res.mjs +40 -0
  100. package/src/components/Aggregate_Builder.res +55 -0
  101. package/src/components/Aggregate_Builder.res.mjs +140 -0
  102. package/src/components/AutomationSlice_Builder.res +41 -0
  103. package/src/components/AutomationSlice_Builder.res.mjs +57 -0
  104. package/src/components/Counter_Builder.res +14 -0
  105. package/src/components/Counter_Builder.res.mjs +32 -0
  106. package/src/components/DcbEventLog_Builder.res +18 -0
  107. package/src/components/DcbEventLog_Builder.res.mjs +24 -0
  108. package/src/components/ExtensionPoint_Builder.res +25 -0
  109. package/src/components/ExtensionPoint_Builder.res.mjs +44 -0
  110. package/src/components/InboundTranslationSlice_Builder.res +34 -0
  111. package/src/components/InboundTranslationSlice_Builder.res.mjs +45 -0
  112. package/src/components/OutboundTranslationSlice_Builder.res +43 -0
  113. package/src/components/OutboundTranslationSlice_Builder.res.mjs +59 -0
  114. package/src/components/Plugin_Builder.res +37 -0
  115. package/src/components/Plugin_Builder.res.mjs +67 -0
  116. package/src/components/ReadModel_Builder.res +47 -0
  117. package/src/components/ReadModel_Builder.res.mjs +54 -0
  118. package/src/components/StateChangeSlice_Builder.res +19 -0
  119. package/src/components/StateChangeSlice_Builder.res.mjs +21 -0
  120. package/src/components/StateViewSlice_Builder.res +42 -0
  121. package/src/components/StateViewSlice_Builder.res.mjs +58 -0
  122. package/src/components/Task_Builder.res +28 -0
  123. package/src/components/Task_Builder.res.mjs +41 -0
  124. package/src/reexport/CommandTopic.res +3 -0
  125. package/src/reexport/CommandTopic.res.mjs +55 -0
  126. package/src/test/Mocks/MockDcbEventLogStorage.res +136 -0
  127. package/src/test/Mocks/MockDcbEventLogStorage.res.mjs +146 -0
  128. package/src/test/Mocks/MockEventLogStorage.res +57 -0
  129. package/src/test/Mocks/MockEventLogStorage.res.mjs +65 -0
  130. package/src/test/Mocks/MockPublisher.res +29 -0
  131. package/src/test/Mocks/MockPublisher.res.mjs +28 -0
  132. package/src/test/Mocks/MockQueryDbStorage.res +104 -0
  133. package/src/test/Mocks/MockQueryDbStorage.res.mjs +136 -0
  134. package/src/test/TestRunner.res +96 -0
  135. package/src/test/TestRunner.res.mjs +71 -0
  136. package/tests/SplitApiFixtures.res +84 -0
  137. package/tests/SplitApiFixtures.res.mjs +88 -0
  138. package/tests/SplitApiTest.res +102 -0
  139. package/tests/SplitApiTest.res.mjs +82 -0
  140. package/tests/TestFixtures.res +54 -0
  141. package/tests/TestFixtures.res.mjs +65 -0
  142. package/tests/adapter/BackendParityTest.res +94 -0
  143. package/tests/adapter/BackendParityTest.res.mjs +96 -0
  144. package/tests/adapter/CollectNEventsTest.res +82 -0
  145. package/tests/adapter/CollectNEventsTest.res.mjs +83 -0
  146. package/tests/adapter/CommandAuthorizationTest.res +200 -0
  147. package/tests/adapter/CommandAuthorizationTest.res.mjs +190 -0
  148. package/tests/adapter/CommandTopicChannelTest.res +174 -0
  149. package/tests/adapter/CommandTopicChannelTest.res.mjs +170 -0
  150. package/tests/adapter/CounterHandlerTest.res +113 -0
  151. package/tests/adapter/CounterHandlerTest.res.mjs +96 -0
  152. package/tests/adapter/DcbEventLogStorageSqliteTest.res +157 -0
  153. package/tests/adapter/DcbEventLogStorageSqliteTest.res.mjs +165 -0
  154. package/tests/adapter/DcbEventLogStorageTest.res +187 -0
  155. package/tests/adapter/DcbEventLogStorageTest.res.mjs +195 -0
  156. package/tests/adapter/EventCollectorChannelTest.res +166 -0
  157. package/tests/adapter/EventCollectorChannelTest.res.mjs +178 -0
  158. package/tests/adapter/EventLogStorageSqliteTest.res +105 -0
  159. package/tests/adapter/EventLogStorageSqliteTest.res.mjs +116 -0
  160. package/tests/adapter/EventTapTest.res +102 -0
  161. package/tests/adapter/EventTapTest.res.mjs +94 -0
  162. package/tests/adapter/EventTopicPublisherTest.res +76 -0
  163. package/tests/adapter/EventTopicPublisherTest.res.mjs +86 -0
  164. package/tests/adapter/GraphQL_SchemaInspectorTest.res +763 -0
  165. package/tests/adapter/GraphQL_SchemaInspectorTest.res.mjs +578 -0
  166. package/tests/adapter/GraphQL_SubscriptionResolversTest.res +161 -0
  167. package/tests/adapter/GraphQL_SubscriptionResolversTest.res.mjs +134 -0
  168. package/tests/adapter/HeartbeatRunnerTest.res +136 -0
  169. package/tests/adapter/HeartbeatRunnerTest.res.mjs +96 -0
  170. package/tests/adapter/LocalAuthHttpTest.res +224 -0
  171. package/tests/adapter/LocalAuthHttpTest.res.mjs +237 -0
  172. package/tests/adapter/LocalAuthLoginTest.res +180 -0
  173. package/tests/adapter/LocalAuthLoginTest.res.mjs +207 -0
  174. package/tests/adapter/LocalAuthTest.res +106 -0
  175. package/tests/adapter/LocalAuthTest.res.mjs +145 -0
  176. package/tests/adapter/LocalAuthUserStoreTest.res +172 -0
  177. package/tests/adapter/LocalAuthUserStoreTest.res.mjs +208 -0
  178. package/tests/adapter/LocalBusBoundedTest.res +157 -0
  179. package/tests/adapter/LocalBusBoundedTest.res.mjs +168 -0
  180. package/tests/adapter/LocalBusPubSubTest.res +81 -0
  181. package/tests/adapter/LocalBusPubSubTest.res.mjs +81 -0
  182. package/tests/adapter/QueryDbGsiTtlTest.res +268 -0
  183. package/tests/adapter/QueryDbGsiTtlTest.res.mjs +209 -0
  184. package/tests/adapter/QueryDbListResolverTest.res +361 -0
  185. package/tests/adapter/QueryDbListResolverTest.res.mjs +426 -0
  186. package/tests/adapter/QueryDbStorageSqliteTest.res +159 -0
  187. package/tests/adapter/QueryDbStorageSqliteTest.res.mjs +126 -0
  188. package/tests/adapter/QueryDbStorageTest.res +171 -0
  189. package/tests/adapter/QueryDbStorageTest.res.mjs +170 -0
  190. package/tests/adapter/QueryEngineTest.res +152 -0
  191. package/tests/adapter/QueryEngineTest.res.mjs +148 -0
  192. package/tests/adapter/ScheduledPublisherTest.res +199 -0
  193. package/tests/adapter/ScheduledPublisherTest.res.mjs +203 -0
  194. package/tests/adapter/SortKey_FilterTest.res +103 -0
  195. package/tests/adapter/SortKey_FilterTest.res.mjs +135 -0
  196. package/tests/adapter/SqliteDriverTest.res +86 -0
  197. package/tests/adapter/SqliteDriverTest.res.mjs +101 -0
  198. package/tests/adapter/TaskBucketSqliteTest.res +44 -0
  199. package/tests/adapter/TaskBucketSqliteTest.res.mjs +40 -0
  200. package/tests/adapter/TaskBucketTest.res +106 -0
  201. package/tests/adapter/TaskBucketTest.res.mjs +118 -0
  202. package/tests/components/aggregate/AggregateFixtures.res +78 -0
  203. package/tests/components/aggregate/AggregateFixtures.res.mjs +109 -0
  204. package/tests/components/aggregate/AggregateTest.res +129 -0
  205. package/tests/components/aggregate/AggregateTest.res.mjs +129 -0
  206. package/tests/components/automationslice/AutomationSliceCallbackTest.res +212 -0
  207. package/tests/components/automationslice/AutomationSliceCallbackTest.res.mjs +233 -0
  208. package/tests/components/automationslice/AutomationSliceFixtures.res +189 -0
  209. package/tests/components/automationslice/AutomationSliceFixtures.res.mjs +276 -0
  210. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +289 -0
  211. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res.mjs +453 -0
  212. package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res +47 -0
  213. package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res.mjs +31 -0
  214. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +213 -0
  215. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res.mjs +268 -0
  216. package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +118 -0
  217. package/tests/components/automationslice/MixedSourceAutomationSliceTest.res.mjs +99 -0
  218. package/tests/components/commandgenerator/CommandGeneratorFixtures.res +73 -0
  219. package/tests/components/commandgenerator/CommandGeneratorFixtures.res.mjs +109 -0
  220. package/tests/components/commandgenerator/CommandGeneratorTest.res +43 -0
  221. package/tests/components/commandgenerator/CommandGeneratorTest.res.mjs +46 -0
  222. package/tests/components/commandtopic/CommandTopicFixtures.res +37 -0
  223. package/tests/components/commandtopic/CommandTopicFixtures.res.mjs +68 -0
  224. package/tests/components/commandtopic/CommandTopicStreamFixtures.res +43 -0
  225. package/tests/components/commandtopic/CommandTopicStreamFixtures.res.mjs +91 -0
  226. package/tests/components/commandtopic/CommandTopicStreamTest.res +30 -0
  227. package/tests/components/commandtopic/CommandTopicStreamTest.res.mjs +35 -0
  228. package/tests/components/commandtopic/CommandTopicTest.res +55 -0
  229. package/tests/components/commandtopic/CommandTopicTest.res.mjs +62 -0
  230. package/tests/components/counter/CounterFixtures.res +42 -0
  231. package/tests/components/counter/CounterFixtures.res.mjs +34 -0
  232. package/tests/components/counter/CounterTest.res +46 -0
  233. package/tests/components/counter/CounterTest.res.mjs +61 -0
  234. package/tests/components/dcb/DcbCrossPartitionFixtures.res +170 -0
  235. package/tests/components/dcb/DcbCrossPartitionFixtures.res.mjs +276 -0
  236. package/tests/components/dcb/DcbCrossPartitionReadTest.res +68 -0
  237. package/tests/components/dcb/DcbCrossPartitionReadTest.res.mjs +78 -0
  238. package/tests/components/dcb/DcbEventLogAppendStreamTest.res +72 -0
  239. package/tests/components/dcb/DcbEventLogAppendStreamTest.res.mjs +82 -0
  240. package/tests/components/dcb/DcbEventLogStreamTest.res +103 -0
  241. package/tests/components/dcb/DcbEventLogStreamTest.res.mjs +91 -0
  242. package/tests/components/dcb/DcbFixtures.res +145 -0
  243. package/tests/components/dcb/DcbFixtures.res.mjs +221 -0
  244. package/tests/components/dcb/DcbTest.res +55 -0
  245. package/tests/components/dcb/DcbTest.res.mjs +55 -0
  246. package/tests/components/eventcollector/EventCollectorFixtures.res +42 -0
  247. package/tests/components/eventcollector/EventCollectorFixtures.res.mjs +49 -0
  248. package/tests/components/eventcollector/EventCollectorTest.res +37 -0
  249. package/tests/components/eventcollector/EventCollectorTest.res.mjs +36 -0
  250. package/tests/components/eventlog/EventLogAppendStreamTest.res +93 -0
  251. package/tests/components/eventlog/EventLogAppendStreamTest.res.mjs +113 -0
  252. package/tests/components/eventlog/EventLogConflictTest.res +86 -0
  253. package/tests/components/eventlog/EventLogConflictTest.res.mjs +81 -0
  254. package/tests/components/eventlog/EventLogFixtures.res +52 -0
  255. package/tests/components/eventlog/EventLogFixtures.res.mjs +80 -0
  256. package/tests/components/eventlog/EventLogStreamTest.res +98 -0
  257. package/tests/components/eventlog/EventLogStreamTest.res.mjs +142 -0
  258. package/tests/components/eventlog/EventLogTest.res +53 -0
  259. package/tests/components/eventlog/EventLogTest.res.mjs +74 -0
  260. package/tests/components/eventtopic/EventTopicFixtures.res +47 -0
  261. package/tests/components/eventtopic/EventTopicFixtures.res.mjs +77 -0
  262. package/tests/components/eventtopic/EventTopicStreamFixtures.res +27 -0
  263. package/tests/components/eventtopic/EventTopicStreamFixtures.res.mjs +62 -0
  264. package/tests/components/eventtopic/EventTopicStreamTest.res +35 -0
  265. package/tests/components/eventtopic/EventTopicStreamTest.res.mjs +44 -0
  266. package/tests/components/eventtopic/EventTopicTest.res +35 -0
  267. package/tests/components/eventtopic/EventTopicTest.res.mjs +43 -0
  268. package/tests/components/extensionpoint/ExtensionPointFixtures.res +145 -0
  269. package/tests/components/extensionpoint/ExtensionPointFixtures.res.mjs +188 -0
  270. package/tests/components/extensionpoint/ExtensionPointTest.res +62 -0
  271. package/tests/components/extensionpoint/ExtensionPointTest.res.mjs +55 -0
  272. package/tests/components/heartbeat/HeartbeatFixtures.res +48 -0
  273. package/tests/components/heartbeat/HeartbeatFixtures.res.mjs +46 -0
  274. package/tests/components/heartbeat/HeartbeatTest.res +67 -0
  275. package/tests/components/heartbeat/HeartbeatTest.res.mjs +65 -0
  276. package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +241 -0
  277. package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs +239 -0
  278. package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +36 -0
  279. package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs +73 -0
  280. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +191 -0
  281. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs +297 -0
  282. package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +78 -0
  283. package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res.mjs +137 -0
  284. package/tests/components/querydb/QueryDbFixtures.res +39 -0
  285. package/tests/components/querydb/QueryDbFixtures.res.mjs +73 -0
  286. package/tests/components/querydb/QueryDbSubIdFixtures.res +44 -0
  287. package/tests/components/querydb/QueryDbSubIdFixtures.res.mjs +80 -0
  288. package/tests/components/querydb/QueryDbSubIdTest.res +104 -0
  289. package/tests/components/querydb/QueryDbSubIdTest.res.mjs +104 -0
  290. package/tests/components/querydb/QueryDbTest.res +81 -0
  291. package/tests/components/querydb/QueryDbTest.res.mjs +126 -0
  292. package/tests/components/readmodel/DcbReadModelE2EFixtures.res +215 -0
  293. package/tests/components/readmodel/DcbReadModelE2EFixtures.res.mjs +308 -0
  294. package/tests/components/readmodel/DcbReadModelE2ETest.res +45 -0
  295. package/tests/components/readmodel/DcbReadModelE2ETest.res.mjs +37 -0
  296. package/tests/components/readmodel/DcbReadModelTypoFailFastTest.res +88 -0
  297. package/tests/components/readmodel/DcbReadModelTypoFailFastTest.res.mjs +152 -0
  298. package/tests/components/readmodel/MixedSourceReadModelFixtures.res +181 -0
  299. package/tests/components/readmodel/MixedSourceReadModelFixtures.res.mjs +277 -0
  300. package/tests/components/readmodel/MixedSourceReadModelTest.res +61 -0
  301. package/tests/components/readmodel/MixedSourceReadModelTest.res.mjs +68 -0
  302. package/tests/components/readmodel/ReadModelFixtures.res +138 -0
  303. package/tests/components/readmodel/ReadModelFixtures.res.mjs +169 -0
  304. package/tests/components/readmodel/ReadModelTest.res +46 -0
  305. package/tests/components/readmodel/ReadModelTest.res.mjs +36 -0
  306. package/tests/components/scheduler/SchedulerFixtures.res +59 -0
  307. package/tests/components/scheduler/SchedulerFixtures.res.mjs +63 -0
  308. package/tests/components/scheduler/SchedulerTest.res +76 -0
  309. package/tests/components/scheduler/SchedulerTest.res.mjs +86 -0
  310. package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +133 -0
  311. package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +149 -0
  312. package/tests/components/sideeffecthandler/SideEffectHandlerTest.res +39 -0
  313. package/tests/components/sideeffecthandler/SideEffectHandlerTest.res.mjs +28 -0
  314. package/tests/components/stateviewslice/StateViewSliceFixtures.res +117 -0
  315. package/tests/components/stateviewslice/StateViewSliceFixtures.res.mjs +188 -0
  316. package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +117 -0
  317. package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res.mjs +183 -0
  318. package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +78 -0
  319. package/tests/components/stateviewslice/StateViewSliceSubIdTest.res.mjs +107 -0
  320. package/tests/components/stateviewslice/StateViewSliceTest.res +59 -0
  321. package/tests/components/stateviewslice/StateViewSliceTest.res.mjs +72 -0
  322. package/tests/components/task/TaskFixtures.res +87 -0
  323. package/tests/components/task/TaskFixtures.res.mjs +82 -0
  324. package/tests/components/task/TaskTest.res +98 -0
  325. package/tests/components/task/TaskTest.res.mjs +46 -0
  326. package/tests/plugin/PluginBehavior_GWT.res +146 -0
  327. package/tests/plugin/PluginBehavior_GWT.res.mjs +281 -0
  328. package/tests/plugin/Plugin_Fixtures.res +61 -0
  329. package/tests/plugin/Plugin_Fixtures.res.mjs +105 -0
  330. package/tests/plugin/PluginsProjection_GWT.res +95 -0
  331. package/tests/plugin/PluginsProjection_GWT.res.mjs +120 -0
  332. package/tests/plugin/UIFragmentRegistryProjection_GWT.res +72 -0
  333. package/tests/plugin/UIFragmentRegistryProjection_GWT.res.mjs +117 -0
  334. package/tests/setup/sqliteGlobal.cjs +12 -0
@@ -0,0 +1,498 @@
1
+ // Shared in-memory event and command bus.
2
+ // Each Platform.Make() creates a fresh isolated bus — no global state, no test interference.
3
+ //
4
+ // Event delivery uses Effect PubSub + Stream for fan-out and a countdown Deferred for
5
+ // completion signaling (Phase F of effect-stream-integration plan).
6
+ //
7
+ // subscribeToEvents — creates (or reuses) a PubSub hub per topic (unbounded or bounded based
8
+ // on capacity) and starts a drain fiber (via Effect.runFork). The fiber runs
9
+ // Effect.scoped(PubSub.subscribe(hub)→…) synchronously up to the first Queue.take
10
+ // suspension, registering the subscription before runFork returns. The subscriber count
11
+ // for the topic is incremented synchronously.
12
+ //
13
+ // publishEvent — uses the manually-tracked subscriber count (not PubSub.size, which measures
14
+ // queued messages not subscriber count), creates a countdown done_ Effect and an allDone
15
+ // Deferred. The publish path depends on capacity:
16
+ // Unbounded (capacity=None): Effect.runSync(PubSub.publish) + separate Deferred.await_.
17
+ // Synchronous fan-out; resolves in 2 microtask ticks.
18
+ // Bounded (capacity=Some(n)): PubSub.publish + Deferred.await_ in one runPromise.
19
+ // May suspend when any subscriber queue is full; resolves in 3 microtask ticks.
20
+ //
21
+ // reset — shuts down all hubs (which terminates Stream.fromQueue consumers via PubSub.shutdown),
22
+ // then clears all registries.
23
+ //
24
+ // Timing analysis:
25
+ // Unbounded — Make():
26
+ // tick 0: PubSub.publish (Effect.runSync) + Deferred.await_ runPromise starts
27
+ // tick 1: Effect scheduler: drain fibers wake, call handlers, run done_; last resolves allDone
28
+ // tick 2: Deferred.await_ promise resolves; publishEvent returns
29
+ // → 2 microtask ticks (same as the previous Queue-based implementation)
30
+ //
31
+ // Bounded — MakeBounded({let capacity = n}):
32
+ // tick 0: Effect.runPromise(PubSub.publish → Deferred.await_) starts
33
+ // tick 1: publish completes; drain fibers run; Deferred.succeed called
34
+ // tick 2: Deferred.await_ promise resolves; publishEvent returns
35
+ // → 3 microtask ticks (1 extra vs unbounded due to async publish)
36
+ //
37
+ // NOTE: PubSub.size in Effect measures buffered message count (not subscriber count).
38
+ // Subscriber count is tracked via a separate subscriberCounts dict.
39
+
40
+ // Message fanned out by PubSub to every subscriber's Queue.
41
+ // done_ is run by each subscriber after the callback returns; when the last subscriber
42
+ // finishes, done_ resolves allDone and publishEvent can return.
43
+ type queuedEvent = {
44
+ service: string,
45
+ meta: ReventlessCore.Message.meta,
46
+ json: JSON.t,
47
+ done_: Effect.t<unit, unit, unit>,
48
+ }
49
+
50
+ // ── State-change descriptor helper ───────────────────────────────────────────
51
+ // Mirrors the JSON shape produced by the AWS StateTopic Lambda (Phase 2):
52
+ // `{ changeKind, id, sortKeyValue? }`. Storage adapters call this and pass
53
+ // the result to `publishStateChange(~descriptor)` so dev and prod subscribers
54
+ // see the same payload.
55
+
56
+ let pickSortKeyValue = (state: JSON.t): option<string> =>
57
+ switch state->JSON.Decode.object {
58
+ | Some(obj) =>
59
+ switch obj->Dict.get("updatedAt") {
60
+ | Some(JSON.String(v)) => Some(v)
61
+ | _ =>
62
+ switch obj->Dict.get("createdAt") {
63
+ | Some(JSON.String(v)) => Some(v)
64
+ | _ => None
65
+ }
66
+ }
67
+ | None => None
68
+ }
69
+
70
+ /** Build a state-change descriptor.
71
+ - `changeKind`: one of "Added" | "Updated" | "Removed". In-memory adapters
72
+ typically emit "Updated" for save() (no Added detection — see module-type
73
+ docstring) and "Removed" for delete().
74
+ - `id`: entity key. Single-key projections: the partition-key value.
75
+ Composite projections: `partition ++ "-" ++ subKey`.
76
+ - `state`: present for save() (for `sortKeyValue` extraction);
77
+ `None` for delete() — descriptor will omit `sortKeyValue`. */
78
+ let makeStateChangeDescriptor = (
79
+ ~changeKind: string,
80
+ ~id: string,
81
+ ~state: option<JSON.t>,
82
+ ): JSON.t => {
83
+ let descriptor = Dict.make()
84
+ descriptor->Dict.set("changeKind", JSON.Encode.string(changeKind))
85
+ descriptor->Dict.set("id", JSON.Encode.string(id))
86
+ switch state->Option.flatMap(pickSortKeyValue) {
87
+ | Some(v) => descriptor->Dict.set("sortKeyValue", JSON.Encode.string(v))
88
+ | None => ()
89
+ }
90
+ descriptor->JSON.Encode.object
91
+ }
92
+
93
+ // Opt-in NDJSON domain-event tap for the VS Code local platform runner (features
94
+ // plan Phase 9). Enabled when REVENTLESS_EVENT_TAP is set; off by default so normal
95
+ // runs stay quiet. Emitted from publishEvent so every event is captured with its
96
+ // real topic name (the EventTopic resource name consumers subscribe to) — no need
97
+ // to guess topic names from EventLog registry keys. Each line is sentinel-prefixed
98
+ // so the runner's line parser can pick it out of the platform's ANSI log noise on
99
+ // stdout. The emit is a plain Console.log, so it never perturbs publishEvent's
100
+ // subscriber-countdown delivery semantics.
101
+ @val external tapProcessEnv: dict<string> = "process.env"
102
+ // Read per publish (not once at import) so the runner can toggle it live and so
103
+ // hermetic tests can flip it between cases. The cost is one dict lookup per event.
104
+ let eventTapEnabled = () => tapProcessEnv->Dict.get("REVENTLESS_EVENT_TAP")->Option.isSome
105
+ let eventTapSeq = ref(0)
106
+ // Seed the in-memory tap counter from a persistent store's existing event count
107
+ // so the timeline's #N continues across process restarts / app switches instead
108
+ // of restarting at 1. Called once at platform startup for the sqlite backend.
109
+ let seedEventTapSeq = (n: int) => eventTapSeq := n
110
+ let emitEventTap = (~topic: string, ~service: string, ~payload: JSON.t) => {
111
+ eventTapSeq := eventTapSeq.contents + 1
112
+ let line =
113
+ Dict.fromArray([
114
+ ("event", JSON.Encode.string("domainEvent")),
115
+ ("seq", JSON.Encode.int(eventTapSeq.contents)),
116
+ ("topic", JSON.Encode.string(topic)),
117
+ ("service", JSON.Encode.string(service)),
118
+ ("payload", payload),
119
+ ("ts", JSON.Encode.string(Date.make()->Date.toISOString)),
120
+ ])->JSON.Encode.object
121
+ Console.log("@@RVLESS_EVT@@ " ++ line->JSON.stringify)
122
+ }
123
+
124
+ module type T = {
125
+ // Event fan-out: aggregate EventTopic → read model EventCollector
126
+ let publishEvent: (string, string, ReventlessCore.Message.meta, JSON.t) => promise<unit>
127
+ let subscribeToEvents: (
128
+ string,
129
+ (string, ReventlessCore.Message.meta, JSON.t) => promise<unit>,
130
+ ) => unit
131
+
132
+ /**
133
+ * Stream-based alternative to subscribeToEvents.
134
+ * Returns a scoped Effect that yields a Stream<queuedEvent> for the topic.
135
+ * The subscriber count is incremented on scope open and decremented on scope close.
136
+ * Callers MUST run msg.done_ after processing each message to unblock publishEvent.
137
+ *
138
+ * Use subscribeToEvents for simple fire-and-forget subscriptions.
139
+ */
140
+ let subscribeToEventStream: string => Effect.t<Stream.t<queuedEvent, unit, unit>, unit, unit>
141
+
142
+ // Command dispatch: CommandTopic → aggregate command handler
143
+ // Dispatches a single encoded command JSON {reference, commandJson}
144
+ let dispatchCommand: (string, JSON.t) => promise<unit>
145
+ let registerCommandHandler: (string, (JSON.t, unit) => promise<unit>) => unit
146
+
147
+ // QueryDb registry: read model name → storage ops and scan function
148
+ // Populated by QueryDbStorage_InMemory.Make(Bus) during component construction.
149
+ let registerQueryDb: (string, ReventlessCore.QueryDb_Adapter.operations) => unit
150
+ let getQueryDb: string => option<ReventlessCore.QueryDb_Adapter.operations>
151
+ let registerQueryDbScan: (string, unit => array<JSON.t>) => unit
152
+ let getQueryDbScan: string => option<unit => array<JSON.t>>
153
+ // Stream variant: lazily creates a Stream from current storage contents.
154
+ // Used by LocalQueryEngine to honour ~limit via Stream.take without loading all items.
155
+ let registerQueryDbStream: (string, unit => Stream.t<JSON.t, string, unit>) => unit
156
+ let getQueryDbStream: string => option<unit => Stream.t<JSON.t, string, unit>>
157
+
158
+ // Event log replay registry: aggregate EventLog name → replay function (entityId → events)
159
+ let registerEventLogReplay: (string, string => promise<array<JSON.t>>) => unit
160
+ let getEventLogReplay: string => option<string => promise<array<JSON.t>>>
161
+
162
+ // DCB event log read registry: DCB EventLog name → read function
163
+ let registerDcbEventLogRead: (
164
+ string,
165
+ (
166
+ ~query: Reventless.DcbTag.query,
167
+ ~after: Reventless.DcbTag.sequencePosition=?,
168
+ ) => promise<ReventlessCore.DcbEventLog_Adapter.rawReadResult>,
169
+ ) => unit
170
+ let getDcbEventLogRead: string => option<
171
+ (
172
+ ~query: Reventless.DcbTag.query,
173
+ ~after: Reventless.DcbTag.sequencePosition=?,
174
+ ) => promise<ReventlessCore.DcbEventLog_Adapter.rawReadResult>,
175
+ >
176
+
177
+ // Subscription push hooks — used by LocalGraphQL_SubscriptionResolvers to bridge
178
+ // QueryDb writes and EventTopic publishes into a yoga PubSub for WebSocket delivery.
179
+ //
180
+ // Source B (state changes): QueryDbStorage_InMemory calls publishStateChange after
181
+ // every save/delete so subscription listeners receive a change descriptor matching
182
+ // the AWS StateTopic Lambda output (Phase 2): {changeKind, id, sortKeyValue?}.
183
+ // ~name is the QueryDb/ReadModel Spec.name; ~descriptor is built via
184
+ // `makeStateChangeDescriptor` below.
185
+ //
186
+ // In-memory degradation vs AWS: `changeKind` for save() is always "Updated"
187
+ // (AWS distinguishes "Added" via DynamoDB streams' INSERT eventName; the
188
+ // in-memory adapter doesn't cheaply track first-insert). `delete()` emits
189
+ // "Removed". `position` is omitted (Phase 3 deferred).
190
+ let publishStateChange: (~name: string, ~descriptor: JSON.t) => unit
191
+ let subscribeToStateChanges: (string, JSON.t => unit) => unit
192
+
193
+ // Cross-plugin EP subscription registry.
194
+ // EventCollectors register their handler once resolved; makePlatform subscribes
195
+ // them to external EP EventTopics to wire cross-plugin Extension connections.
196
+ let registerEventCollectorHandler: (string, (JSON.t, unit) => promise<unit>) => unit
197
+ let subscribeEventCollectorToTopic: (string, string) => unit
198
+
199
+ let reset: unit => unit
200
+ }
201
+
202
+ // Internal module type for bus configuration.
203
+ // capacity=None → unbounded hub (synchronous fan-out, no backpressure, 2-tick delivery).
204
+ // capacity=Some(n) → bounded hub (async publish with backpressure, 3-tick delivery).
205
+ // silent — when true, suppresses diagnostic warnings (e.g. missing command handlers).
206
+ module type BusConfig = {
207
+ let capacity: option<int>
208
+ let silent: bool
209
+ }
210
+
211
+ // Full implementation parameterised by BusConfig.
212
+ // Used by both Make (unbounded) and MakeBounded (bounded).
213
+ module Impl = (C: BusConfig): T => {
214
+ let capacity = C.capacity
215
+
216
+ // Per-topic PubSub hub for fan-out to all subscriber Queues.
217
+ // Unbounded (capacity=None): synchronous fan-out, 2-tick delivery guarantee.
218
+ // Bounded (capacity=Some(n)): async publish with backpressure, 3-tick delivery.
219
+ let eventHubs: ref<dict<PubSub.t<queuedEvent>>> = ref(Dict.make())
220
+ // Per-topic subscriber count — tracked manually because PubSub.size measures
221
+ // buffered message count (always 0 for unbounded after delivery), not subscriber count.
222
+ let subscriberCounts: ref<dict<int>> = ref(Dict.make())
223
+
224
+ let commandHandlers: ref<dict<(JSON.t, unit) => promise<unit>>> = ref(Dict.make())
225
+ // Per-channel pending command queues — drained when registerCommandHandler fires.
226
+ let pendingCommandQueues: ref<dict<ref<array<JSON.t>>>> = ref(Dict.make())
227
+ let queryDbRegistry: ref<dict<ReventlessCore.QueryDb_Adapter.operations>> = ref(Dict.make())
228
+ let queryDbScanRegistry: ref<dict<unit => array<JSON.t>>> = ref(Dict.make())
229
+ let queryDbStreamRegistry: ref<dict<unit => Stream.t<JSON.t, string, unit>>> = ref(Dict.make())
230
+ let eventLogReplayRegistry: ref<dict<string => promise<array<JSON.t>>>> = ref(Dict.make())
231
+ let dcbEventLogReadRegistry: ref<
232
+ dict<
233
+ (
234
+ ~query: Reventless.DcbTag.query,
235
+ ~after: Reventless.DcbTag.sequencePosition=?,
236
+ ) => promise<ReventlessCore.DcbEventLog_Adapter.rawReadResult>,
237
+ >,
238
+ > = ref(Dict.make())
239
+
240
+ // Create a new hub using the capacity from BusConfig.
241
+ // None → unbounded (synchronous fan-out, no backpressure).
242
+ // Some(n) → bounded (async publish, exerts backpressure when any subscriber is slow).
243
+ let makeHub = (): PubSub.t<queuedEvent> =>
244
+ switch capacity {
245
+ | None => PubSub.unbounded()->Effect.runSync
246
+ | Some(n) => PubSub.bounded(n)->Effect.runSync
247
+ }
248
+
249
+ let subscribeToEvents = (topicName, handler) => {
250
+ let hub = switch eventHubs.contents->Dict.get(topicName) {
251
+ | Some(h) => h
252
+ | None =>
253
+ let h: PubSub.t<queuedEvent> = makeHub()
254
+ eventHubs.contents->Dict.set(topicName, h)
255
+ h
256
+ }
257
+ // Increment count synchronously before starting the fiber — publishEvent reads
258
+ // this count and must see it even if called immediately after subscribeToEvents.
259
+ let n = subscriberCounts.contents->Dict.get(topicName)->Option.getOr(0)
260
+ subscriberCounts.contents->Dict.set(topicName, n + 1)
261
+ // Drain loop: subscribe to hub, consume messages via Stream.fromQueue → Stream.runForEach.
262
+ // Effect.scoped manages the subscription lifecycle; PubSub.shutdown in reset() closes it.
263
+ // The fiber runs synchronously up to the first Queue.take suspension, so PubSub.subscribe
264
+ // registers before Effect.runFork returns.
265
+ let drainLoop = Effect.scoped(
266
+ PubSub.subscribe(hub)->Effect.flatMap(queue =>
267
+ Stream.fromQueue(queue)->Stream.runForEach(msg =>
268
+ Effect.promise(() => handler(msg.service, msg.meta, msg.json))->Effect.zipRight(msg.done_)
269
+ )
270
+ ),
271
+ )
272
+ let _ = Effect.runFork(drainLoop)
273
+ }
274
+
275
+ let subscribeToEventStream = topicName => {
276
+ let hub = switch eventHubs.contents->Dict.get(topicName) {
277
+ | Some(h) => h
278
+ | None =>
279
+ let h: PubSub.t<queuedEvent> = makeHub()
280
+ eventHubs.contents->Dict.set(topicName, h)
281
+ h
282
+ }
283
+ // Acquire: increment subscriber count + subscribe to hub.
284
+ // Release: decrement subscriber count + shut down the per-subscriber queue.
285
+ // PubSub.subscribe's own scope management also removes the subscriber from the hub
286
+ // when the outer Effect.scoped closes.
287
+ Effect.acquireRelease(
288
+ Effect.sync(() => {
289
+ let n = subscriberCounts.contents->Dict.get(topicName)->Option.getOr(0)
290
+ subscriberCounts.contents->Dict.set(topicName, n + 1)
291
+ })->Effect.flatMap(_ => PubSub.subscribe(hub)),
292
+ (queue, _exit) =>
293
+ Effect.sync(() => {
294
+ let n = subscriberCounts.contents->Dict.get(topicName)->Option.getOr(1)
295
+ subscriberCounts.contents->Dict.set(topicName, n - 1)
296
+ })->Effect.zipRight(Queue.shutdown(queue)),
297
+ )->Effect.map(queue => Stream.fromQueue(queue))
298
+ }
299
+
300
+ let publishEvent = async (topicName, service, meta, json) => {
301
+ if eventTapEnabled() {
302
+ emitEventTap(~topic=topicName, ~service, ~payload=json)
303
+ }
304
+ switch eventHubs.contents->Dict.get(topicName) {
305
+ | None => ()
306
+ | Some(hub) =>
307
+ let n = subscriberCounts.contents->Dict.get(topicName)->Option.getOr(0)
308
+ if n == 0 {
309
+ ()
310
+ } else {
311
+ // One Deferred for the whole publish call. The last subscriber to finish
312
+ // resolves it via the countdown done_ Effect.
313
+ let allDone: Deferred.t<unit, unit> = Deferred.make()->Effect.runSync
314
+ let remaining = ref(n)
315
+ // done_ runs inside each drain fiber's Effect chain (via Effect.zipRight).
316
+ // It decrements remaining and — when reaching zero — resolves allDone.
317
+ // IMPORTANT: done_ must NOT be called via Effect.runSync from a JS callback.
318
+ // It must run within the Effect fiber so that Deferred.succeed operates in the
319
+ // same runtime as Deferred.await_ below.
320
+ let done_: Effect.t<unit, unit, unit> = Effect.sync(() => {
321
+ remaining := remaining.contents - 1
322
+ })->Effect.flatMap(_ =>
323
+ if remaining.contents == 0 {
324
+ Deferred.succeed(allDone, ())->Effect.map(_ => ())
325
+ } else {
326
+ Effect.succeed()
327
+ }
328
+ )
329
+ let msg = {service, meta, json, done_}
330
+ // Publish path is conditional on capacity:
331
+ // None (unbounded): Effect.runSync for publish (synchronous fan-out, 2-tick path).
332
+ // Some(_) (bounded): Effect.runPromise for publish (may suspend when queues full, 3-tick path).
333
+ // Both paths wait for allDone to resolve (all subscribers finished).
334
+ let publishAndWait: Effect.t<unit, unit, unit> = switch capacity {
335
+ | None =>
336
+ // Unbounded: publish synchronously inside Effect.sync so the fiber chain is uniform.
337
+ // Effect.runSync keeps fan-out synchronous, preserving the 2-tick guarantee.
338
+ Effect.sync(() => {
339
+ let _ = PubSub.publish(hub, msg)->Effect.runSync
340
+ })->Effect.zipRight(Deferred.await_(allDone))
341
+ | Some(_) =>
342
+ // Bounded: publish is an Effect that may suspend if any subscriber queue is full.
343
+ // This provides backpressure: publishEvent suspends until a slow subscriber drains.
344
+ PubSub.publish(hub, msg)->Effect.flatMap(_ => Deferred.await_(allDone))
345
+ }
346
+ let _ = await publishAndWait->Effect.runPromise
347
+ }
348
+ }
349
+ }
350
+
351
+ let dispatchCommand = async (channelName, json) => {
352
+ switch commandHandlers.contents->Dict.get(channelName) {
353
+ | Some(handler) => await handler(json, ())
354
+ | None =>
355
+ // Park the command — drained once registerCommandHandler fires for this channel.
356
+ let queue = switch pendingCommandQueues.contents->Dict.get(channelName) {
357
+ | Some(q) => q
358
+ | None =>
359
+ let q = ref([])
360
+ pendingCommandQueues.contents->Dict.set(channelName, q)
361
+ q
362
+ }
363
+ queue.contents->Array.push(json)
364
+ }
365
+ }
366
+
367
+ let registerCommandHandler = (channelName, handler) => {
368
+ commandHandlers.contents->Dict.set(channelName, handler)
369
+ // Drain any commands that arrived before the handler was registered.
370
+ switch pendingCommandQueues.contents->Dict.get(channelName) {
371
+ | Some(queue) =>
372
+ let pending = queue.contents
373
+ queue.contents = []
374
+ pending->Array.forEach(json => {
375
+ let _ = handler(json, ())
376
+ })
377
+ | None => ()
378
+ }
379
+ }
380
+
381
+ let registerQueryDb = (name, ops) => queryDbRegistry.contents->Dict.set(name, ops)
382
+ let getQueryDb = name => queryDbRegistry.contents->Dict.get(name)
383
+ let registerQueryDbScan = (name, scan) => queryDbScanRegistry.contents->Dict.set(name, scan)
384
+ let getQueryDbScan = name => queryDbScanRegistry.contents->Dict.get(name)
385
+ let registerQueryDbStream = (name, streamFn) =>
386
+ queryDbStreamRegistry.contents->Dict.set(name, streamFn)
387
+ let getQueryDbStream = name => queryDbStreamRegistry.contents->Dict.get(name)
388
+
389
+ let registerEventLogReplay = (name, replay) =>
390
+ eventLogReplayRegistry.contents->Dict.set(name, replay)
391
+ let getEventLogReplay = name => eventLogReplayRegistry.contents->Dict.get(name)
392
+ let registerDcbEventLogRead = (name, read) =>
393
+ dcbEventLogReadRegistry.contents->Dict.set(name, read)
394
+ let getDcbEventLogRead = name => dcbEventLogReadRegistry.contents->Dict.get(name)
395
+
396
+ // Source B state-change hook — per QueryDb name, zero or more listeners.
397
+ let stateChangeListeners: ref<dict<array<JSON.t => unit>>> = ref(Dict.make())
398
+
399
+ let subscribeToStateChanges = (name, callback) => {
400
+ let listeners = stateChangeListeners.contents->Dict.get(name)->Option.getOr([])
401
+ stateChangeListeners.contents->Dict.set(name, Array.concat(listeners, [callback]))
402
+ }
403
+
404
+ let publishStateChange = (~name, ~descriptor) =>
405
+ stateChangeListeners.contents
406
+ ->Dict.get(name)
407
+ ->Option.getOr([])
408
+ ->Array.forEach(cb => cb(descriptor))
409
+
410
+ let eventCollectorHandlers: ref<dict<(JSON.t, unit) => promise<unit>>> = ref(Dict.make())
411
+ let eventCollectorPendingTopics: ref<dict<array<string>>> = ref(Dict.make())
412
+
413
+ // Cross-plugin subscriptions are fire-and-forget: the handler is started asynchronously
414
+ // and done_ is called immediately. This prevents a deadlock where publishEvent on the
415
+ // EP topic would block until the downstream aggregate command chain completes.
416
+ let makeFireAndForgetHandler = handler => (_, _, json) => {
417
+ let _ = handler(json, ())
418
+ Promise.resolve()
419
+ }
420
+
421
+ let registerEventCollectorHandler = (ecName, handler) => {
422
+ eventCollectorHandlers.contents->Dict.set(ecName, handler)
423
+ switch eventCollectorPendingTopics.contents->Dict.get(ecName) {
424
+ | Some(topics) =>
425
+ topics->Array.forEach(topicName =>
426
+ subscribeToEvents(topicName, makeFireAndForgetHandler(handler))
427
+ )
428
+ eventCollectorPendingTopics.contents->Dict.delete(ecName)
429
+ | None => ()
430
+ }
431
+ }
432
+
433
+ let subscribeEventCollectorToTopic = (ecName, topicName) => {
434
+ switch eventCollectorHandlers.contents->Dict.get(ecName) {
435
+ | Some(handler) => subscribeToEvents(topicName, makeFireAndForgetHandler(handler))
436
+ | None =>
437
+ let pending = eventCollectorPendingTopics.contents->Dict.get(ecName)->Option.getOr([])
438
+ pending->Array.push(topicName)
439
+ eventCollectorPendingTopics.contents->Dict.set(ecName, pending)
440
+ }
441
+ }
442
+
443
+ let reset = () => {
444
+ // Shut down all hubs — PubSub.shutdown interrupts Stream.fromQueue consumers,
445
+ // causing Stream.runForEach to complete and Effect.scoped to close the subscription.
446
+ let shutdownAll =
447
+ eventHubs.contents
448
+ ->Dict.valuesToArray
449
+ ->Array.map(hub => PubSub.shutdown(hub))
450
+ ->Effect.all({"concurrency": "unbounded"})
451
+ ->Effect.map(_ => ())
452
+ let _ = Effect.runSync(shutdownAll)
453
+ eventHubs := Dict.make()
454
+ subscriberCounts := Dict.make()
455
+ commandHandlers := Dict.make()
456
+ pendingCommandQueues := Dict.make()
457
+ queryDbRegistry := Dict.make()
458
+ queryDbScanRegistry := Dict.make()
459
+ queryDbStreamRegistry := Dict.make()
460
+ eventLogReplayRegistry := Dict.make()
461
+ dcbEventLogReadRegistry := Dict.make()
462
+ stateChangeListeners := Dict.make()
463
+ eventCollectorHandlers := Dict.make()
464
+ eventCollectorPendingTopics := Dict.make()
465
+ }
466
+ }
467
+
468
+ // Backward-compatible unbounded bus (2-tick delivery guarantee).
469
+ // All existing LocalBus.Make() call sites work without modification.
470
+ module Make = (): T => {
471
+ include Impl({
472
+ let capacity: option<int> = None
473
+ let silent = false
474
+ })
475
+ }
476
+
477
+ // Silent unbounded bus — suppresses diagnostic warnings. Use in tests.
478
+ module MakeSilent = (): T => {
479
+ include Impl({
480
+ let capacity: option<int> = None
481
+ let silent = true
482
+ })
483
+ }
484
+
485
+ // Bounded bus (Phase G): each subscriber queue has a fixed capacity.
486
+ // publishEvent suspends when any subscriber's queue is full — providing backpressure.
487
+ // Resolves in 3 microtask ticks (1 more than unbounded).
488
+ // Usage: module TestBus = LocalBus.MakeBounded({let capacity = 2})
489
+ module MakeBounded = (
490
+ C: {
491
+ let capacity: int
492
+ },
493
+ ): T => {
494
+ include Impl({
495
+ let capacity: option<int> = Some(C.capacity)
496
+ let silent = false
497
+ })
498
+ }