@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.
- package/CHANGELOG.md +1197 -0
- package/LICENSE +202 -0
- package/__mocks__/emptyModule.js +2 -0
- package/__mocks__/nodeSqlite.mjs +6 -0
- package/package.json +67 -0
- package/rescript.json +39 -0
- package/src/Platform.res +2162 -0
- package/src/Platform.res.mjs +3389 -0
- package/src/adapter/Api/LocalGraphQL_Adapter.res +29 -0
- package/src/adapter/Api/LocalGraphQL_Adapter.res.mjs +26 -0
- package/src/adapter/Api/LocalGraphQL_SubscriptionResolvers.res +146 -0
- package/src/adapter/Api/LocalGraphQL_SubscriptionResolvers.res.mjs +88 -0
- package/src/adapter/Auth/Auth_GraphqlContext.res +83 -0
- package/src/adapter/Auth/Auth_GraphqlContext.res.mjs +76 -0
- package/src/adapter/Auth/LocalAuth.res +239 -0
- package/src/adapter/Auth/LocalAuth.res.mjs +247 -0
- package/src/adapter/Auth/UserStore.res +134 -0
- package/src/adapter/Auth/UserStore.res.mjs +146 -0
- package/src/adapter/Backend.res +46 -0
- package/src/adapter/Backend.res.mjs +66 -0
- package/src/adapter/BackendState.res +34 -0
- package/src/adapter/BackendState.res.mjs +42 -0
- package/src/adapter/Cloner/LocalClonerRunner.res +13 -0
- package/src/adapter/Cloner/LocalClonerRunner.res.mjs +12 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +329 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res.mjs +301 -0
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +76 -0
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res.mjs +57 -0
- package/src/adapter/CommandGenerator/LocalCommandGeneratorResolvers.res +19 -0
- package/src/adapter/CommandGenerator/LocalCommandGeneratorResolvers.res.mjs +19 -0
- package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +104 -0
- package/src/adapter/CommandTopic/LocalCommandTopicChannel.res.mjs +82 -0
- package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +32 -0
- package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res.mjs +38 -0
- package/src/adapter/Counter/LocalCounterHandler.res +42 -0
- package/src/adapter/Counter/LocalCounterHandler.res.mjs +45 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +131 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res.mjs +140 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +334 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res.mjs +299 -0
- package/src/adapter/DomainGraphQL_Server.res +565 -0
- package/src/adapter/DomainGraphQL_Server.res.mjs +576 -0
- package/src/adapter/DomainMCP_Server.res +369 -0
- package/src/adapter/DomainMCP_Server.res.mjs +358 -0
- package/src/adapter/EventCollector/LocalEventCollectorChannel.res +87 -0
- package/src/adapter/EventCollector/LocalEventCollectorChannel.res.mjs +45 -0
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +105 -0
- package/src/adapter/EventLog/EventLogStorage_InMemory.res.mjs +117 -0
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +145 -0
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res.mjs +155 -0
- package/src/adapter/EventTopic/LocalEventTopicPublisher.res +36 -0
- package/src/adapter/EventTopic/LocalEventTopicPublisher.res.mjs +31 -0
- package/src/adapter/GraphQL_Server.res +6 -0
- package/src/adapter/GraphQL_Server.res.mjs +154 -0
- package/src/adapter/GraphQL_ServerInstance.res +310 -0
- package/src/adapter/GraphQL_ServerInstance.res.mjs +257 -0
- package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +57 -0
- package/src/adapter/Heartbeat/LocalHeartbeatRunner.res.mjs +34 -0
- package/src/adapter/LocalBus.res +498 -0
- package/src/adapter/LocalBus.res.mjs +1011 -0
- package/src/adapter/LocalPluginSpec.res +16 -0
- package/src/adapter/LocalPluginSpec.res.mjs +33 -0
- package/src/adapter/LocalSideEffectHandler.res +44 -0
- package/src/adapter/LocalSideEffectHandler.res.mjs +34 -0
- package/src/adapter/MCP_Server.res +4 -0
- package/src/adapter/MCP_Server.res.mjs +67 -0
- package/src/adapter/MCP_ServerInstance.res +362 -0
- package/src/adapter/MCP_ServerInstance.res.mjs +313 -0
- package/src/adapter/PlatformGraphQL_Server.res +75 -0
- package/src/adapter/PlatformGraphQL_Server.res.mjs +84 -0
- package/src/adapter/PlatformMCP_Server.res +23 -0
- package/src/adapter/PlatformMCP_Server.res.mjs +46 -0
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +793 -0
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +569 -0
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +242 -0
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res.mjs +241 -0
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +339 -0
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res.mjs +318 -0
- package/src/adapter/QueryDb/SortKey_Filter.res +48 -0
- package/src/adapter/QueryDb/SortKey_Filter.res.mjs +46 -0
- package/src/adapter/QueryEngine/LocalQueryEngine.res +57 -0
- package/src/adapter/QueryEngine/LocalQueryEngine.res.mjs +56 -0
- package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +91 -0
- package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res.mjs +50 -0
- package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +47 -0
- package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res.mjs +37 -0
- package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +11 -0
- package/src/adapter/Runtime/LocalPluginRuntime_Builder.res.mjs +30 -0
- package/src/adapter/Runtime/LocalRuntimeEnvironment.res +54 -0
- package/src/adapter/Runtime/LocalRuntimeEnvironment.res.mjs +40 -0
- package/src/adapter/Scheduler/LocalScheduledPublisher.res +102 -0
- package/src/adapter/Scheduler/LocalScheduledPublisher.res.mjs +84 -0
- package/src/adapter/SqliteDriver.res +52 -0
- package/src/adapter/SqliteDriver.res.mjs +76 -0
- package/src/adapter/SqliteDriver.resi +31 -0
- package/src/adapter/Task/LocalTaskBucket.res +75 -0
- package/src/adapter/Task/LocalTaskBucket.res.mjs +48 -0
- package/src/adapter/Task/TaskBucket_Sqlite.res +47 -0
- package/src/adapter/Task/TaskBucket_Sqlite.res.mjs +40 -0
- package/src/components/Aggregate_Builder.res +55 -0
- package/src/components/Aggregate_Builder.res.mjs +140 -0
- package/src/components/AutomationSlice_Builder.res +41 -0
- package/src/components/AutomationSlice_Builder.res.mjs +57 -0
- package/src/components/Counter_Builder.res +14 -0
- package/src/components/Counter_Builder.res.mjs +32 -0
- package/src/components/DcbEventLog_Builder.res +18 -0
- package/src/components/DcbEventLog_Builder.res.mjs +24 -0
- package/src/components/ExtensionPoint_Builder.res +25 -0
- package/src/components/ExtensionPoint_Builder.res.mjs +44 -0
- package/src/components/InboundTranslationSlice_Builder.res +34 -0
- package/src/components/InboundTranslationSlice_Builder.res.mjs +45 -0
- package/src/components/OutboundTranslationSlice_Builder.res +43 -0
- package/src/components/OutboundTranslationSlice_Builder.res.mjs +59 -0
- package/src/components/Plugin_Builder.res +37 -0
- package/src/components/Plugin_Builder.res.mjs +67 -0
- package/src/components/ReadModel_Builder.res +47 -0
- package/src/components/ReadModel_Builder.res.mjs +54 -0
- package/src/components/StateChangeSlice_Builder.res +19 -0
- package/src/components/StateChangeSlice_Builder.res.mjs +21 -0
- package/src/components/StateViewSlice_Builder.res +42 -0
- package/src/components/StateViewSlice_Builder.res.mjs +58 -0
- package/src/components/Task_Builder.res +28 -0
- package/src/components/Task_Builder.res.mjs +41 -0
- package/src/reexport/CommandTopic.res +3 -0
- package/src/reexport/CommandTopic.res.mjs +55 -0
- package/src/test/Mocks/MockDcbEventLogStorage.res +136 -0
- package/src/test/Mocks/MockDcbEventLogStorage.res.mjs +146 -0
- package/src/test/Mocks/MockEventLogStorage.res +57 -0
- package/src/test/Mocks/MockEventLogStorage.res.mjs +65 -0
- package/src/test/Mocks/MockPublisher.res +29 -0
- package/src/test/Mocks/MockPublisher.res.mjs +28 -0
- package/src/test/Mocks/MockQueryDbStorage.res +104 -0
- package/src/test/Mocks/MockQueryDbStorage.res.mjs +136 -0
- package/src/test/TestRunner.res +96 -0
- package/src/test/TestRunner.res.mjs +71 -0
- package/tests/SplitApiFixtures.res +84 -0
- package/tests/SplitApiFixtures.res.mjs +88 -0
- package/tests/SplitApiTest.res +102 -0
- package/tests/SplitApiTest.res.mjs +82 -0
- package/tests/TestFixtures.res +54 -0
- package/tests/TestFixtures.res.mjs +65 -0
- package/tests/adapter/BackendParityTest.res +94 -0
- package/tests/adapter/BackendParityTest.res.mjs +96 -0
- package/tests/adapter/CollectNEventsTest.res +82 -0
- package/tests/adapter/CollectNEventsTest.res.mjs +83 -0
- package/tests/adapter/CommandAuthorizationTest.res +200 -0
- package/tests/adapter/CommandAuthorizationTest.res.mjs +190 -0
- package/tests/adapter/CommandTopicChannelTest.res +174 -0
- package/tests/adapter/CommandTopicChannelTest.res.mjs +170 -0
- package/tests/adapter/CounterHandlerTest.res +113 -0
- package/tests/adapter/CounterHandlerTest.res.mjs +96 -0
- package/tests/adapter/DcbEventLogStorageSqliteTest.res +157 -0
- package/tests/adapter/DcbEventLogStorageSqliteTest.res.mjs +165 -0
- package/tests/adapter/DcbEventLogStorageTest.res +187 -0
- package/tests/adapter/DcbEventLogStorageTest.res.mjs +195 -0
- package/tests/adapter/EventCollectorChannelTest.res +166 -0
- package/tests/adapter/EventCollectorChannelTest.res.mjs +178 -0
- package/tests/adapter/EventLogStorageSqliteTest.res +105 -0
- package/tests/adapter/EventLogStorageSqliteTest.res.mjs +116 -0
- package/tests/adapter/EventTapTest.res +102 -0
- package/tests/adapter/EventTapTest.res.mjs +94 -0
- package/tests/adapter/EventTopicPublisherTest.res +76 -0
- package/tests/adapter/EventTopicPublisherTest.res.mjs +86 -0
- package/tests/adapter/GraphQL_SchemaInspectorTest.res +763 -0
- package/tests/adapter/GraphQL_SchemaInspectorTest.res.mjs +578 -0
- package/tests/adapter/GraphQL_SubscriptionResolversTest.res +161 -0
- package/tests/adapter/GraphQL_SubscriptionResolversTest.res.mjs +134 -0
- package/tests/adapter/HeartbeatRunnerTest.res +136 -0
- package/tests/adapter/HeartbeatRunnerTest.res.mjs +96 -0
- package/tests/adapter/LocalAuthHttpTest.res +224 -0
- package/tests/adapter/LocalAuthHttpTest.res.mjs +237 -0
- package/tests/adapter/LocalAuthLoginTest.res +180 -0
- package/tests/adapter/LocalAuthLoginTest.res.mjs +207 -0
- package/tests/adapter/LocalAuthTest.res +106 -0
- package/tests/adapter/LocalAuthTest.res.mjs +145 -0
- package/tests/adapter/LocalAuthUserStoreTest.res +172 -0
- package/tests/adapter/LocalAuthUserStoreTest.res.mjs +208 -0
- package/tests/adapter/LocalBusBoundedTest.res +157 -0
- package/tests/adapter/LocalBusBoundedTest.res.mjs +168 -0
- package/tests/adapter/LocalBusPubSubTest.res +81 -0
- package/tests/adapter/LocalBusPubSubTest.res.mjs +81 -0
- package/tests/adapter/QueryDbGsiTtlTest.res +268 -0
- package/tests/adapter/QueryDbGsiTtlTest.res.mjs +209 -0
- package/tests/adapter/QueryDbListResolverTest.res +361 -0
- package/tests/adapter/QueryDbListResolverTest.res.mjs +426 -0
- package/tests/adapter/QueryDbStorageSqliteTest.res +159 -0
- package/tests/adapter/QueryDbStorageSqliteTest.res.mjs +126 -0
- package/tests/adapter/QueryDbStorageTest.res +171 -0
- package/tests/adapter/QueryDbStorageTest.res.mjs +170 -0
- package/tests/adapter/QueryEngineTest.res +152 -0
- package/tests/adapter/QueryEngineTest.res.mjs +148 -0
- package/tests/adapter/ScheduledPublisherTest.res +199 -0
- package/tests/adapter/ScheduledPublisherTest.res.mjs +203 -0
- package/tests/adapter/SortKey_FilterTest.res +103 -0
- package/tests/adapter/SortKey_FilterTest.res.mjs +135 -0
- package/tests/adapter/SqliteDriverTest.res +86 -0
- package/tests/adapter/SqliteDriverTest.res.mjs +101 -0
- package/tests/adapter/TaskBucketSqliteTest.res +44 -0
- package/tests/adapter/TaskBucketSqliteTest.res.mjs +40 -0
- package/tests/adapter/TaskBucketTest.res +106 -0
- package/tests/adapter/TaskBucketTest.res.mjs +118 -0
- package/tests/components/aggregate/AggregateFixtures.res +78 -0
- package/tests/components/aggregate/AggregateFixtures.res.mjs +109 -0
- package/tests/components/aggregate/AggregateTest.res +129 -0
- package/tests/components/aggregate/AggregateTest.res.mjs +129 -0
- package/tests/components/automationslice/AutomationSliceCallbackTest.res +212 -0
- package/tests/components/automationslice/AutomationSliceCallbackTest.res.mjs +233 -0
- package/tests/components/automationslice/AutomationSliceFixtures.res +189 -0
- package/tests/components/automationslice/AutomationSliceFixtures.res.mjs +276 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +289 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res.mjs +453 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res +47 -0
- package/tests/components/automationslice/AutomationSliceSelfDeadlockTest.res.mjs +31 -0
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +213 -0
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res.mjs +268 -0
- package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +118 -0
- package/tests/components/automationslice/MixedSourceAutomationSliceTest.res.mjs +99 -0
- package/tests/components/commandgenerator/CommandGeneratorFixtures.res +73 -0
- package/tests/components/commandgenerator/CommandGeneratorFixtures.res.mjs +109 -0
- package/tests/components/commandgenerator/CommandGeneratorTest.res +43 -0
- package/tests/components/commandgenerator/CommandGeneratorTest.res.mjs +46 -0
- package/tests/components/commandtopic/CommandTopicFixtures.res +37 -0
- package/tests/components/commandtopic/CommandTopicFixtures.res.mjs +68 -0
- package/tests/components/commandtopic/CommandTopicStreamFixtures.res +43 -0
- package/tests/components/commandtopic/CommandTopicStreamFixtures.res.mjs +91 -0
- package/tests/components/commandtopic/CommandTopicStreamTest.res +30 -0
- package/tests/components/commandtopic/CommandTopicStreamTest.res.mjs +35 -0
- package/tests/components/commandtopic/CommandTopicTest.res +55 -0
- package/tests/components/commandtopic/CommandTopicTest.res.mjs +62 -0
- package/tests/components/counter/CounterFixtures.res +42 -0
- package/tests/components/counter/CounterFixtures.res.mjs +34 -0
- package/tests/components/counter/CounterTest.res +46 -0
- package/tests/components/counter/CounterTest.res.mjs +61 -0
- package/tests/components/dcb/DcbCrossPartitionFixtures.res +170 -0
- package/tests/components/dcb/DcbCrossPartitionFixtures.res.mjs +276 -0
- package/tests/components/dcb/DcbCrossPartitionReadTest.res +68 -0
- package/tests/components/dcb/DcbCrossPartitionReadTest.res.mjs +78 -0
- package/tests/components/dcb/DcbEventLogAppendStreamTest.res +72 -0
- package/tests/components/dcb/DcbEventLogAppendStreamTest.res.mjs +82 -0
- package/tests/components/dcb/DcbEventLogStreamTest.res +103 -0
- package/tests/components/dcb/DcbEventLogStreamTest.res.mjs +91 -0
- package/tests/components/dcb/DcbFixtures.res +145 -0
- package/tests/components/dcb/DcbFixtures.res.mjs +221 -0
- package/tests/components/dcb/DcbTest.res +55 -0
- package/tests/components/dcb/DcbTest.res.mjs +55 -0
- package/tests/components/eventcollector/EventCollectorFixtures.res +42 -0
- package/tests/components/eventcollector/EventCollectorFixtures.res.mjs +49 -0
- package/tests/components/eventcollector/EventCollectorTest.res +37 -0
- package/tests/components/eventcollector/EventCollectorTest.res.mjs +36 -0
- package/tests/components/eventlog/EventLogAppendStreamTest.res +93 -0
- package/tests/components/eventlog/EventLogAppendStreamTest.res.mjs +113 -0
- package/tests/components/eventlog/EventLogConflictTest.res +86 -0
- package/tests/components/eventlog/EventLogConflictTest.res.mjs +81 -0
- package/tests/components/eventlog/EventLogFixtures.res +52 -0
- package/tests/components/eventlog/EventLogFixtures.res.mjs +80 -0
- package/tests/components/eventlog/EventLogStreamTest.res +98 -0
- package/tests/components/eventlog/EventLogStreamTest.res.mjs +142 -0
- package/tests/components/eventlog/EventLogTest.res +53 -0
- package/tests/components/eventlog/EventLogTest.res.mjs +74 -0
- package/tests/components/eventtopic/EventTopicFixtures.res +47 -0
- package/tests/components/eventtopic/EventTopicFixtures.res.mjs +77 -0
- package/tests/components/eventtopic/EventTopicStreamFixtures.res +27 -0
- package/tests/components/eventtopic/EventTopicStreamFixtures.res.mjs +62 -0
- package/tests/components/eventtopic/EventTopicStreamTest.res +35 -0
- package/tests/components/eventtopic/EventTopicStreamTest.res.mjs +44 -0
- package/tests/components/eventtopic/EventTopicTest.res +35 -0
- package/tests/components/eventtopic/EventTopicTest.res.mjs +43 -0
- package/tests/components/extensionpoint/ExtensionPointFixtures.res +145 -0
- package/tests/components/extensionpoint/ExtensionPointFixtures.res.mjs +188 -0
- package/tests/components/extensionpoint/ExtensionPointTest.res +62 -0
- package/tests/components/extensionpoint/ExtensionPointTest.res.mjs +55 -0
- package/tests/components/heartbeat/HeartbeatFixtures.res +48 -0
- package/tests/components/heartbeat/HeartbeatFixtures.res.mjs +46 -0
- package/tests/components/heartbeat/HeartbeatTest.res +67 -0
- package/tests/components/heartbeat/HeartbeatTest.res.mjs +65 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +241 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res.mjs +239 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +36 -0
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res.mjs +73 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +191 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res.mjs +297 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +78 -0
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res.mjs +137 -0
- package/tests/components/querydb/QueryDbFixtures.res +39 -0
- package/tests/components/querydb/QueryDbFixtures.res.mjs +73 -0
- package/tests/components/querydb/QueryDbSubIdFixtures.res +44 -0
- package/tests/components/querydb/QueryDbSubIdFixtures.res.mjs +80 -0
- package/tests/components/querydb/QueryDbSubIdTest.res +104 -0
- package/tests/components/querydb/QueryDbSubIdTest.res.mjs +104 -0
- package/tests/components/querydb/QueryDbTest.res +81 -0
- package/tests/components/querydb/QueryDbTest.res.mjs +126 -0
- package/tests/components/readmodel/DcbReadModelE2EFixtures.res +215 -0
- package/tests/components/readmodel/DcbReadModelE2EFixtures.res.mjs +308 -0
- package/tests/components/readmodel/DcbReadModelE2ETest.res +45 -0
- package/tests/components/readmodel/DcbReadModelE2ETest.res.mjs +37 -0
- package/tests/components/readmodel/DcbReadModelTypoFailFastTest.res +88 -0
- package/tests/components/readmodel/DcbReadModelTypoFailFastTest.res.mjs +152 -0
- package/tests/components/readmodel/MixedSourceReadModelFixtures.res +181 -0
- package/tests/components/readmodel/MixedSourceReadModelFixtures.res.mjs +277 -0
- package/tests/components/readmodel/MixedSourceReadModelTest.res +61 -0
- package/tests/components/readmodel/MixedSourceReadModelTest.res.mjs +68 -0
- package/tests/components/readmodel/ReadModelFixtures.res +138 -0
- package/tests/components/readmodel/ReadModelFixtures.res.mjs +169 -0
- package/tests/components/readmodel/ReadModelTest.res +46 -0
- package/tests/components/readmodel/ReadModelTest.res.mjs +36 -0
- package/tests/components/scheduler/SchedulerFixtures.res +59 -0
- package/tests/components/scheduler/SchedulerFixtures.res.mjs +63 -0
- package/tests/components/scheduler/SchedulerTest.res +76 -0
- package/tests/components/scheduler/SchedulerTest.res.mjs +86 -0
- package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +133 -0
- package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +149 -0
- package/tests/components/sideeffecthandler/SideEffectHandlerTest.res +39 -0
- package/tests/components/sideeffecthandler/SideEffectHandlerTest.res.mjs +28 -0
- package/tests/components/stateviewslice/StateViewSliceFixtures.res +117 -0
- package/tests/components/stateviewslice/StateViewSliceFixtures.res.mjs +188 -0
- package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +117 -0
- package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res.mjs +183 -0
- package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +78 -0
- package/tests/components/stateviewslice/StateViewSliceSubIdTest.res.mjs +107 -0
- package/tests/components/stateviewslice/StateViewSliceTest.res +59 -0
- package/tests/components/stateviewslice/StateViewSliceTest.res.mjs +72 -0
- package/tests/components/task/TaskFixtures.res +87 -0
- package/tests/components/task/TaskFixtures.res.mjs +82 -0
- package/tests/components/task/TaskTest.res +98 -0
- package/tests/components/task/TaskTest.res.mjs +46 -0
- package/tests/plugin/PluginBehavior_GWT.res +146 -0
- package/tests/plugin/PluginBehavior_GWT.res.mjs +281 -0
- package/tests/plugin/Plugin_Fixtures.res +61 -0
- package/tests/plugin/Plugin_Fixtures.res.mjs +105 -0
- package/tests/plugin/PluginsProjection_GWT.res +95 -0
- package/tests/plugin/PluginsProjection_GWT.res.mjs +120 -0
- package/tests/plugin/UIFragmentRegistryProjection_GWT.res +72 -0
- package/tests/plugin/UIFragmentRegistryProjection_GWT.res.mjs +117 -0
- 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
|
+
}
|