@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,68 @@
|
|
|
1
|
+
// End-to-end proof of the inferred cross-partition read fix: a sibling product
|
|
2
|
+
// sharing the same categoryId must NOT be returned by AddProduct's decision read,
|
|
3
|
+
// because the categoryId clause indexes only the category's own events. Exercises
|
|
4
|
+
// the full command → narrowed query → in-memory DcbEventLog read → decide path
|
|
5
|
+
// that the unit tests and GWTs do not cover (the GWT harness folds events raw).
|
|
6
|
+
|
|
7
|
+
open JestGlobals
|
|
8
|
+
open DcbCrossPartitionFixtures
|
|
9
|
+
|
|
10
|
+
describe("DCB inferred cross-partition read (sibling exclusion)", () => {
|
|
11
|
+
let _ = beforeAllAsync(async () => {
|
|
12
|
+
let _ = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
let _ = beforeEach(() => {
|
|
16
|
+
capturedEventCount := 0
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
testPromise(
|
|
20
|
+
"a sibling product sharing the categoryId is not read; the new product is added",
|
|
21
|
+
async () => {
|
|
22
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
23
|
+
// Category cat1 exists, and a DIFFERENT product p1 already lives in it
|
|
24
|
+
// (tagged productId=p1 AND categoryId=cat1).
|
|
25
|
+
let _ = await ops.append([
|
|
26
|
+
encodeEvent(CategoryAdded({categoryId: "cat1", name: "Electronics"})),
|
|
27
|
+
encodeEvent(ProductAdded({productId: "p1", categoryId: "cat1", name: "Laptop"})),
|
|
28
|
+
])
|
|
29
|
+
capturedEventCount := 0 // count only events produced by the command below
|
|
30
|
+
|
|
31
|
+
// Add a new product p2 in cat1. The categoryId clause narrows ProductAdded
|
|
32
|
+
// out, so it reads only CategoryAdded — p1 is invisible — and p2 is added.
|
|
33
|
+
await dispatch(addProductJson("p2", "cat1", "Mouse"), "p2")
|
|
34
|
+
expect(capturedEventCount.contents)->toBe(1)
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
testPromise(
|
|
39
|
+
"a product added through the slice is NOT written to the categoryId GSI (payload, not indexed)",
|
|
40
|
+
async () => {
|
|
41
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
42
|
+
let _ = await ops.append([encodeEvent(CategoryAdded({categoryId: "cat3", name: "Toys"}))])
|
|
43
|
+
// p3 is produced by the slice → its categoryId is filtered out on write.
|
|
44
|
+
await dispatch(addProductJson("p3", "cat3", "Blocks"), "p3")
|
|
45
|
+
|
|
46
|
+
// Queryable by its own partition key…
|
|
47
|
+
let byProduct = await ops.read(~query=[{tags: [{Reventless.DcbTag.key: "productId", value: "p3"}]}])
|
|
48
|
+
expect(byProduct.events->Array.some(e => e.eventType == "ProductAdded"))->toBe(true)
|
|
49
|
+
|
|
50
|
+
// …but the categoryId index returns only the category's own event, never p3.
|
|
51
|
+
let byCategory = await ops.read(~query=[{tags: [{Reventless.DcbTag.key: "categoryId", value: "cat3"}]}])
|
|
52
|
+
expect(byCategory.events->Array.some(e => e.eventType == "ProductAdded"))->toBe(false)
|
|
53
|
+
expect(byCategory.events->Array.some(e => e.eventType == "CategoryAdded"))->toBe(true)
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
testPromise("re-adding the same product is still rejected (its own read is partition-scoped)", async () => {
|
|
58
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
59
|
+
let _ = await ops.append([
|
|
60
|
+
encodeEvent(CategoryAdded({categoryId: "cat2", name: "Books"})),
|
|
61
|
+
encodeEvent(ProductAdded({productId: "p9", categoryId: "cat2", name: "Novel"})),
|
|
62
|
+
])
|
|
63
|
+
capturedEventCount := 0 // count only events produced by the command below
|
|
64
|
+
// p9 already exists — its own productId clause returns its ProductAdded.
|
|
65
|
+
await dispatch(addProductJson("p9", "cat2", "Novel"), "p9")
|
|
66
|
+
expect(capturedEventCount.contents)->toBe(0)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
|
|
4
|
+
import * as TestRunner$ReventlessLocal from "../../../src/test/TestRunner.res.mjs";
|
|
5
|
+
import * as DcbCrossPartitionFixtures$ReventlessLocal from "./DcbCrossPartitionFixtures.res.mjs";
|
|
6
|
+
|
|
7
|
+
globalThis.describe("DCB inferred cross-partition read (sibling exclusion)", () => {
|
|
8
|
+
globalThis.beforeAll(async () => {
|
|
9
|
+
await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbCrossPartitionFixtures$ReventlessLocal.eventLog));
|
|
10
|
+
});
|
|
11
|
+
globalThis.beforeEach(() => {
|
|
12
|
+
DcbCrossPartitionFixtures$ReventlessLocal.capturedEventCount.contents = 0;
|
|
13
|
+
});
|
|
14
|
+
globalThis.test("a sibling product sharing the categoryId is not read; the new product is added", async () => {
|
|
15
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbCrossPartitionFixtures$ReventlessLocal.eventLog));
|
|
16
|
+
await ops.append([
|
|
17
|
+
DcbCrossPartitionFixtures$ReventlessLocal.encodeEvent({
|
|
18
|
+
TAG: "CategoryAdded",
|
|
19
|
+
categoryId: "cat1",
|
|
20
|
+
name: "Electronics"
|
|
21
|
+
}),
|
|
22
|
+
DcbCrossPartitionFixtures$ReventlessLocal.encodeEvent({
|
|
23
|
+
TAG: "ProductAdded",
|
|
24
|
+
productId: "p1",
|
|
25
|
+
categoryId: "cat1",
|
|
26
|
+
name: "Laptop"
|
|
27
|
+
})
|
|
28
|
+
], undefined);
|
|
29
|
+
DcbCrossPartitionFixtures$ReventlessLocal.capturedEventCount.contents = 0;
|
|
30
|
+
await DcbCrossPartitionFixtures$ReventlessLocal.dispatch(DcbCrossPartitionFixtures$ReventlessLocal.addProductJson("p2", "cat1", "Mouse"), "p2");
|
|
31
|
+
globalThis.expect(DcbCrossPartitionFixtures$ReventlessLocal.capturedEventCount.contents).toBe(1);
|
|
32
|
+
});
|
|
33
|
+
globalThis.test("a product added through the slice is NOT written to the categoryId GSI (payload, not indexed)", async () => {
|
|
34
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbCrossPartitionFixtures$ReventlessLocal.eventLog));
|
|
35
|
+
await ops.append([DcbCrossPartitionFixtures$ReventlessLocal.encodeEvent({
|
|
36
|
+
TAG: "CategoryAdded",
|
|
37
|
+
categoryId: "cat3",
|
|
38
|
+
name: "Toys"
|
|
39
|
+
})], undefined);
|
|
40
|
+
await DcbCrossPartitionFixtures$ReventlessLocal.dispatch(DcbCrossPartitionFixtures$ReventlessLocal.addProductJson("p3", "cat3", "Blocks"), "p3");
|
|
41
|
+
let byProduct = await ops.read([{
|
|
42
|
+
tags: [{
|
|
43
|
+
key: "productId",
|
|
44
|
+
value: "p3"
|
|
45
|
+
}]
|
|
46
|
+
}], undefined);
|
|
47
|
+
globalThis.expect(byProduct.events.some(e => e.eventType === "ProductAdded")).toBe(true);
|
|
48
|
+
let byCategory = await ops.read([{
|
|
49
|
+
tags: [{
|
|
50
|
+
key: "categoryId",
|
|
51
|
+
value: "cat3"
|
|
52
|
+
}]
|
|
53
|
+
}], undefined);
|
|
54
|
+
globalThis.expect(byCategory.events.some(e => e.eventType === "ProductAdded")).toBe(false);
|
|
55
|
+
globalThis.expect(byCategory.events.some(e => e.eventType === "CategoryAdded")).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
globalThis.test("re-adding the same product is still rejected (its own read is partition-scoped)", async () => {
|
|
58
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbCrossPartitionFixtures$ReventlessLocal.eventLog));
|
|
59
|
+
await ops.append([
|
|
60
|
+
DcbCrossPartitionFixtures$ReventlessLocal.encodeEvent({
|
|
61
|
+
TAG: "CategoryAdded",
|
|
62
|
+
categoryId: "cat2",
|
|
63
|
+
name: "Books"
|
|
64
|
+
}),
|
|
65
|
+
DcbCrossPartitionFixtures$ReventlessLocal.encodeEvent({
|
|
66
|
+
TAG: "ProductAdded",
|
|
67
|
+
productId: "p9",
|
|
68
|
+
categoryId: "cat2",
|
|
69
|
+
name: "Novel"
|
|
70
|
+
})
|
|
71
|
+
], undefined);
|
|
72
|
+
DcbCrossPartitionFixtures$ReventlessLocal.capturedEventCount.contents = 0;
|
|
73
|
+
await DcbCrossPartitionFixtures$ReventlessLocal.dispatch(DcbCrossPartitionFixtures$ReventlessLocal.addProductJson("p9", "cat2", "Novel"), "p9");
|
|
74
|
+
globalThis.expect(DcbCrossPartitionFixtures$ReventlessLocal.capturedEventCount.contents).toBe(0);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Tests for DcbEventLog.appendStream (Phase H of effect-stream-integration plan).
|
|
2
|
+
// Verifies that a Stream<rawEvent> can drive a single atomic append, and that
|
|
3
|
+
// the readStream → appendStream pipeline works end-to-end.
|
|
4
|
+
|
|
5
|
+
open JestGlobals
|
|
6
|
+
open DcbFixtures
|
|
7
|
+
|
|
8
|
+
// Helper to encode a typed event into a raw event for appending to the DcbEventLog
|
|
9
|
+
let encodeEvent = (event: DcbFixtures.ItemEventLog.event): ReventlessInfra.DcbEventLog.rawEvent => {
|
|
10
|
+
let json = event->S.reverseConvertToJsonOrThrow(DcbFixtures.ItemEventLog.eventSchema)
|
|
11
|
+
let (eventType, data) = json->ReventlessCore.Message.splitMessage
|
|
12
|
+
let tags = Reventless.DcbTag.extractTags(DcbFixtures.ItemEventLog.eventSchema, event)
|
|
13
|
+
let meta = ReventlessCore.Message.generateMeta(~service="test")
|
|
14
|
+
{eventType, data: JSON.Object(data), tags, meta}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("DcbEventLog.appendStream (in-memory adapter)", () => {
|
|
18
|
+
let _ = beforeAllAsync(async () => {
|
|
19
|
+
let _ = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
let _ = beforeEach(() => {
|
|
23
|
+
capturedEventCount := 0
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
testPromise("appendStream writes all events from stream", async () => {
|
|
27
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
28
|
+
let stream = [
|
|
29
|
+
encodeEvent(DcbFixtures.ItemEventLog.ItemAdded({id: "as-w1", name: "Widget1"})),
|
|
30
|
+
encodeEvent(DcbFixtures.ItemEventLog.ItemAdded({id: "as-w1", name: "Widget2"})),
|
|
31
|
+
]->Stream.fromIterable
|
|
32
|
+
let result = await ops.appendStream(stream)->Effect.runPromise
|
|
33
|
+
let isOk = switch result {
|
|
34
|
+
| Ok(_) => true
|
|
35
|
+
| Error(_) => false
|
|
36
|
+
}
|
|
37
|
+
expect(isOk)->toBe(true)
|
|
38
|
+
let read = await ops.read(~query=tagQuery("as-w1"))
|
|
39
|
+
expect(read.events->Array.length)->toBe(2)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
testPromise("appendStream on empty stream succeeds", async () => {
|
|
43
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
44
|
+
// Empty stream → empty array append → storage returns Ok(position)
|
|
45
|
+
let result = await ops.appendStream(Stream.empty)->Effect.runPromise
|
|
46
|
+
let isOk = switch result {
|
|
47
|
+
| Ok(_) => true
|
|
48
|
+
| Error(_) => false
|
|
49
|
+
}
|
|
50
|
+
expect(isOk)->toBe(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
testPromise("readStream → appendStream pipeline copies events", async () => {
|
|
54
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
55
|
+
let _ = await ops.append([encodeEvent(DcbFixtures.ItemEventLog.ItemAdded({id: "as-src", name: "Source"}))])
|
|
56
|
+
// readStream returns rawSequencedEvent; extract bare rawEvent for appendStream
|
|
57
|
+
let srcStream =
|
|
58
|
+
ops.readStream(~query=tagQuery("as-src"))->Stream.map(se => {
|
|
59
|
+
let rawEvent: ReventlessInfra.DcbEventLog.rawEvent = {
|
|
60
|
+
eventType: se.eventType,
|
|
61
|
+
data: se.data,
|
|
62
|
+
tags: se.tags,
|
|
63
|
+
meta: se.meta,
|
|
64
|
+
}
|
|
65
|
+
rawEvent
|
|
66
|
+
})
|
|
67
|
+
let _ = await ops.appendStream(srcStream)->Effect.runPromise
|
|
68
|
+
let dst = await ops.read(~query=tagQuery("as-src"))
|
|
69
|
+
// 1 original + 1 copy = 2 events for the same tag
|
|
70
|
+
expect(dst.events->Array.length)->toBe(2)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Stream from "effect/Stream";
|
|
6
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
7
|
+
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
8
|
+
import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
|
|
9
|
+
import * as TestRunner$ReventlessLocal from "../../../src/test/TestRunner.res.mjs";
|
|
10
|
+
import * as DcbFixtures$ReventlessLocal from "./DcbFixtures.res.mjs";
|
|
11
|
+
|
|
12
|
+
function encodeEvent(event) {
|
|
13
|
+
let json = S.reverseConvertToJsonOrThrow(event, DcbFixtures$ReventlessLocal.ItemEventLog.eventSchema);
|
|
14
|
+
let match = Message$ReventlessCore.splitMessage(json);
|
|
15
|
+
let tags = DcbTag$Reventless.extractTags(DcbFixtures$ReventlessLocal.ItemEventLog.eventSchema, event);
|
|
16
|
+
let meta = Message$ReventlessCore.generateMeta("test", undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
17
|
+
return {
|
|
18
|
+
eventType: match[0],
|
|
19
|
+
data: match[1],
|
|
20
|
+
tags: tags,
|
|
21
|
+
meta: meta
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
globalThis.describe("DcbEventLog.appendStream (in-memory adapter)", () => {
|
|
26
|
+
globalThis.beforeAll(async () => {
|
|
27
|
+
await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
28
|
+
});
|
|
29
|
+
globalThis.beforeEach(() => {
|
|
30
|
+
DcbFixtures$ReventlessLocal.capturedEventCount.contents = 0;
|
|
31
|
+
});
|
|
32
|
+
globalThis.test("appendStream writes all events from stream", async () => {
|
|
33
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
34
|
+
let stream = Stream.fromIterable([
|
|
35
|
+
encodeEvent({
|
|
36
|
+
TAG: "ItemAdded",
|
|
37
|
+
id: "as-w1",
|
|
38
|
+
name: "Widget1"
|
|
39
|
+
}),
|
|
40
|
+
encodeEvent({
|
|
41
|
+
TAG: "ItemAdded",
|
|
42
|
+
id: "as-w1",
|
|
43
|
+
name: "Widget2"
|
|
44
|
+
})
|
|
45
|
+
]);
|
|
46
|
+
let result = await Effect.runPromise(ops.appendStream(stream, undefined));
|
|
47
|
+
let isOk;
|
|
48
|
+
isOk = result.TAG === "Ok";
|
|
49
|
+
globalThis.expect(isOk).toBe(true);
|
|
50
|
+
let read = await ops.read(DcbFixtures$ReventlessLocal.tagQuery("as-w1"), undefined);
|
|
51
|
+
globalThis.expect(read.events.length).toBe(2);
|
|
52
|
+
});
|
|
53
|
+
globalThis.test("appendStream on empty stream succeeds", async () => {
|
|
54
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
55
|
+
let result = await Effect.runPromise(ops.appendStream(Stream.empty, undefined));
|
|
56
|
+
let isOk;
|
|
57
|
+
isOk = result.TAG === "Ok";
|
|
58
|
+
globalThis.expect(isOk).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
globalThis.test("readStream → appendStream pipeline copies events", async () => {
|
|
61
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
62
|
+
await ops.append([encodeEvent({
|
|
63
|
+
TAG: "ItemAdded",
|
|
64
|
+
id: "as-src",
|
|
65
|
+
name: "Source"
|
|
66
|
+
})], undefined);
|
|
67
|
+
let srcStream = Stream.map(ops.readStream(DcbFixtures$ReventlessLocal.tagQuery("as-src"), undefined, undefined), se => ({
|
|
68
|
+
eventType: se.eventType,
|
|
69
|
+
data: se.data,
|
|
70
|
+
tags: se.tags,
|
|
71
|
+
meta: se.meta
|
|
72
|
+
}));
|
|
73
|
+
await Effect.runPromise(ops.appendStream(srcStream, undefined));
|
|
74
|
+
let dst = await ops.read(DcbFixtures$ReventlessLocal.tagQuery("as-src"), undefined);
|
|
75
|
+
globalThis.expect(dst.events.length).toBe(2);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export {
|
|
80
|
+
encodeEvent,
|
|
81
|
+
}
|
|
82
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Tests for DcbEventLog.readStream (Phase D of effect-stream-integration plan).
|
|
2
|
+
// Mirrors the pattern of EventLogStreamTest.res for the DCB event log.
|
|
3
|
+
|
|
4
|
+
open JestGlobals
|
|
5
|
+
open DcbFixtures
|
|
6
|
+
|
|
7
|
+
describe("DcbEventLog.readStream (in-memory adapter)", () => {
|
|
8
|
+
let _ = beforeAllAsync(async () => {
|
|
9
|
+
let _ = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
let _ = beforeEach(() => {
|
|
13
|
+
capturedEventCount := 0
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe("basic streaming", () => {
|
|
17
|
+
testPromise("readStream for tag with no matching events returns empty stream", async () => {
|
|
18
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
19
|
+
let count = await ops.readStream(~query=tagQuery("no-such-id"))
|
|
20
|
+
->Stream.runFold(0, (n, _) => n + 1)
|
|
21
|
+
->Effect.runPromise
|
|
22
|
+
expect(count)->toBe(0)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
testPromise("readStream emits all events matching a tag query", async () => {
|
|
26
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
27
|
+
let _ = await dispatch(addItemJson("stream-1", "Widget"), "stream-1")
|
|
28
|
+
let count = await ops.readStream(~query=tagQuery("stream-1"))
|
|
29
|
+
->Stream.runFold(0, (n, _) => n + 1)
|
|
30
|
+
->Effect.runPromise
|
|
31
|
+
expect(count)->toBe(1)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
testPromise("readStream for distinct IDs returns independent streams", async () => {
|
|
35
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
36
|
+
let _ = await dispatch(addItemJson("stream-A", "Alpha"), "stream-A")
|
|
37
|
+
let _ = await dispatch(addItemJson("stream-B", "Beta"), "stream-B")
|
|
38
|
+
let countA = await ops.readStream(~query=tagQuery("stream-A"))
|
|
39
|
+
->Stream.runFold(0, (n, _) => n + 1)
|
|
40
|
+
->Effect.runPromise
|
|
41
|
+
let countB = await ops.readStream(~query=tagQuery("stream-B"))
|
|
42
|
+
->Stream.runFold(0, (n, _) => n + 1)
|
|
43
|
+
->Effect.runPromise
|
|
44
|
+
expect(countA)->toBe(1)
|
|
45
|
+
expect(countB)->toBe(1)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
testPromise("take(1) limits stream without loading all", async () => {
|
|
49
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
50
|
+
let _ = await dispatch(addItemJson("stream-C1", "C1"), "stream-C1")
|
|
51
|
+
let _ = await dispatch(addItemJson("stream-C2", "C2"), "stream-C2")
|
|
52
|
+
// Broad query: all ItemAdded events regardless of tag value
|
|
53
|
+
let result = await ops.readStream(~query=typeQuery("ItemAdded"))
|
|
54
|
+
->Stream.take(1)
|
|
55
|
+
->Stream.runCollect
|
|
56
|
+
->Effect.runPromise
|
|
57
|
+
expect(result->Array.length)->toBe(1)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe("fold patterns — (state, headPosition) used in StateChangeSlice_Callback", () => {
|
|
62
|
+
testPromise("runFold extracts last headPosition for append condition", async () => {
|
|
63
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
64
|
+
let _ = await dispatch(addItemJson("fold-1", "FoldItem"), "fold-1")
|
|
65
|
+
let (_, headPosition) = await ops.readStream(~query=tagQuery("fold-1"))
|
|
66
|
+
->Stream.runFold(((), None), ((_dm, _pos), se) => ((), Some(se.position)))
|
|
67
|
+
->Effect.runPromise
|
|
68
|
+
expect(Option.isSome(headPosition))->toBe(true)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
testPromise("runFold on empty stream returns initial accumulator", async () => {
|
|
72
|
+
let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
|
|
73
|
+
let (_, headPosition) = await ops.readStream(~query=tagQuery("no-such-id-fold"))
|
|
74
|
+
->Stream.runFold((false, None), ((_dm, _pos), se) => (true, Some(se.position)))
|
|
75
|
+
->Effect.runPromise
|
|
76
|
+
expect(headPosition)->toEqual(None)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe("StateChangeSlice regression — second command sees first event via stream fold", () => {
|
|
81
|
+
testPromise("second AddItem with same ID is rejected (state sees existing event)", async () => {
|
|
82
|
+
// First command succeeds → ItemAdded event stored
|
|
83
|
+
// Second command: readStream folds all events → state = true (exists)
|
|
84
|
+
// decide(true, AddItem) → Error(ItemAlreadyExists) → no new events
|
|
85
|
+
let id = "dup-test-stream"
|
|
86
|
+
let _ = await dispatch(addItemJson(id, "First"), id)
|
|
87
|
+
let countBefore = capturedEventCount.contents
|
|
88
|
+
let _ = await dispatch(addItemJson(id, "Duplicate"), id)
|
|
89
|
+
// Duplicate command must not publish a second event
|
|
90
|
+
expect(capturedEventCount.contents)->toBe(countBefore)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
testPromise("second AddItem with different ID succeeds", async () => {
|
|
94
|
+
let id1 = "distinct-1"
|
|
95
|
+
let id2 = "distinct-2"
|
|
96
|
+
let _ = await dispatch(addItemJson(id1, "First"), id1)
|
|
97
|
+
let countAfterFirst = capturedEventCount.contents
|
|
98
|
+
let _ = await dispatch(addItemJson(id2, "Second"), id2)
|
|
99
|
+
// New ID should publish an event
|
|
100
|
+
expect(capturedEventCount.contents)->toBe(countAfterFirst + 1)
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Stream$1 from "effect/Stream";
|
|
7
|
+
import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
|
|
8
|
+
import * as TestRunner$ReventlessLocal from "../../../src/test/TestRunner.res.mjs";
|
|
9
|
+
import * as DcbFixtures$ReventlessLocal from "./DcbFixtures.res.mjs";
|
|
10
|
+
|
|
11
|
+
globalThis.describe("DcbEventLog.readStream (in-memory adapter)", () => {
|
|
12
|
+
globalThis.beforeAll(async () => {
|
|
13
|
+
await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
14
|
+
});
|
|
15
|
+
globalThis.beforeEach(() => {
|
|
16
|
+
DcbFixtures$ReventlessLocal.capturedEventCount.contents = 0;
|
|
17
|
+
});
|
|
18
|
+
globalThis.describe("basic streaming", () => {
|
|
19
|
+
globalThis.test("readStream for tag with no matching events returns empty stream", async () => {
|
|
20
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
21
|
+
let count = await Effect.runPromise(Stream$1.runFold(ops.readStream(DcbFixtures$ReventlessLocal.tagQuery("no-such-id"), undefined, undefined), 0, (n, param) => n + 1 | 0));
|
|
22
|
+
globalThis.expect(count).toBe(0);
|
|
23
|
+
});
|
|
24
|
+
globalThis.test("readStream emits all events matching a tag query", async () => {
|
|
25
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
26
|
+
await DcbFixtures$ReventlessLocal.dispatch(DcbFixtures$ReventlessLocal.addItemJson("stream-1", "Widget"), "stream-1");
|
|
27
|
+
let count = await Effect.runPromise(Stream$1.runFold(ops.readStream(DcbFixtures$ReventlessLocal.tagQuery("stream-1"), undefined, undefined), 0, (n, param) => n + 1 | 0));
|
|
28
|
+
globalThis.expect(count).toBe(1);
|
|
29
|
+
});
|
|
30
|
+
globalThis.test("readStream for distinct IDs returns independent streams", async () => {
|
|
31
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
32
|
+
await DcbFixtures$ReventlessLocal.dispatch(DcbFixtures$ReventlessLocal.addItemJson("stream-A", "Alpha"), "stream-A");
|
|
33
|
+
await DcbFixtures$ReventlessLocal.dispatch(DcbFixtures$ReventlessLocal.addItemJson("stream-B", "Beta"), "stream-B");
|
|
34
|
+
let countA = await Effect.runPromise(Stream$1.runFold(ops.readStream(DcbFixtures$ReventlessLocal.tagQuery("stream-A"), undefined, undefined), 0, (n, param) => n + 1 | 0));
|
|
35
|
+
let countB = await Effect.runPromise(Stream$1.runFold(ops.readStream(DcbFixtures$ReventlessLocal.tagQuery("stream-B"), undefined, undefined), 0, (n, param) => n + 1 | 0));
|
|
36
|
+
globalThis.expect(countA).toBe(1);
|
|
37
|
+
globalThis.expect(countB).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
globalThis.test("take(1) limits stream without loading all", async () => {
|
|
40
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
41
|
+
await DcbFixtures$ReventlessLocal.dispatch(DcbFixtures$ReventlessLocal.addItemJson("stream-C1", "C1"), "stream-C1");
|
|
42
|
+
await DcbFixtures$ReventlessLocal.dispatch(DcbFixtures$ReventlessLocal.addItemJson("stream-C2", "C2"), "stream-C2");
|
|
43
|
+
let result = await Effect.runPromise(Stream.runCollect(Stream$1.take(ops.readStream(DcbFixtures$ReventlessLocal.typeQuery("ItemAdded"), undefined, undefined), 1)));
|
|
44
|
+
globalThis.expect(result.length).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
globalThis.describe("fold patterns — (state, headPosition) used in StateChangeSlice_Callback", () => {
|
|
48
|
+
globalThis.test("runFold extracts last headPosition for append condition", async () => {
|
|
49
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
50
|
+
await DcbFixtures$ReventlessLocal.dispatch(DcbFixtures$ReventlessLocal.addItemJson("fold-1", "FoldItem"), "fold-1");
|
|
51
|
+
let match = await Effect.runPromise(Stream$1.runFold(ops.readStream(DcbFixtures$ReventlessLocal.tagQuery("fold-1"), undefined, undefined), [
|
|
52
|
+
undefined,
|
|
53
|
+
undefined
|
|
54
|
+
], (param, se) => [
|
|
55
|
+
undefined,
|
|
56
|
+
se.position
|
|
57
|
+
]));
|
|
58
|
+
globalThis.expect(Stdlib_Option.isSome(match[1])).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
globalThis.test("runFold on empty stream returns initial accumulator", async () => {
|
|
61
|
+
let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(DcbFixtures$ReventlessLocal.eventLog));
|
|
62
|
+
let match = await Effect.runPromise(Stream$1.runFold(ops.readStream(DcbFixtures$ReventlessLocal.tagQuery("no-such-id-fold"), undefined, undefined), [
|
|
63
|
+
false,
|
|
64
|
+
undefined
|
|
65
|
+
], (param, se) => [
|
|
66
|
+
true,
|
|
67
|
+
se.position
|
|
68
|
+
]));
|
|
69
|
+
globalThis.expect(match[1]).toEqual(undefined);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
globalThis.describe("StateChangeSlice regression — second command sees first event via stream fold", () => {
|
|
73
|
+
globalThis.test("second AddItem with same ID is rejected (state sees existing event)", async () => {
|
|
74
|
+
let id = "dup-test-stream";
|
|
75
|
+
await DcbFixtures$ReventlessLocal.dispatch(DcbFixtures$ReventlessLocal.addItemJson(id, "First"), id);
|
|
76
|
+
let countBefore = DcbFixtures$ReventlessLocal.capturedEventCount.contents;
|
|
77
|
+
await DcbFixtures$ReventlessLocal.dispatch(DcbFixtures$ReventlessLocal.addItemJson(id, "Duplicate"), id);
|
|
78
|
+
globalThis.expect(DcbFixtures$ReventlessLocal.capturedEventCount.contents).toBe(countBefore);
|
|
79
|
+
});
|
|
80
|
+
globalThis.test("second AddItem with different ID succeeds", async () => {
|
|
81
|
+
let id1 = "distinct-1";
|
|
82
|
+
let id2 = "distinct-2";
|
|
83
|
+
await DcbFixtures$ReventlessLocal.dispatch(DcbFixtures$ReventlessLocal.addItemJson(id1, "First"), id1);
|
|
84
|
+
let countAfterFirst = DcbFixtures$ReventlessLocal.capturedEventCount.contents;
|
|
85
|
+
await DcbFixtures$ReventlessLocal.dispatch(DcbFixtures$ReventlessLocal.addItemJson(id2, "Second"), id2);
|
|
86
|
+
globalThis.expect(DcbFixtures$ReventlessLocal.capturedEventCount.contents).toBe(countAfterFirst + 1 | 0);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// E2E test fixtures for DcbEventLog + StateChangeSlice.
|
|
2
|
+
// Contains specs, bus setup, component wiring, and shared test helpers.
|
|
3
|
+
|
|
4
|
+
open TestFixtures
|
|
5
|
+
|
|
6
|
+
// ─────────────────────────────────────────────────────────────
|
|
7
|
+
// Event type definition for the DcbEventLog
|
|
8
|
+
// ─────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
module ItemEventLog = {
|
|
11
|
+
@schema
|
|
12
|
+
type event = ItemAdded({id: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ─────────────────────────────────────────────────────────────
|
|
16
|
+
// Minimal StateChangeSlice spec
|
|
17
|
+
// ─────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
module AddItemSpec = {
|
|
20
|
+
let name = "AddItem"
|
|
21
|
+
module Id = Reventless.Id.String
|
|
22
|
+
let moduleUrl: string = %raw(`import.meta.url`)
|
|
23
|
+
|
|
24
|
+
@schema
|
|
25
|
+
type event = ItemAdded({id: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
26
|
+
|
|
27
|
+
@schema
|
|
28
|
+
type consumedEvent = ItemAdded
|
|
29
|
+
|
|
30
|
+
@schema
|
|
31
|
+
type command = AddItem({id: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
32
|
+
|
|
33
|
+
@schema
|
|
34
|
+
type error = ItemAlreadyExists
|
|
35
|
+
|
|
36
|
+
let commandSchema = commandSchema // auto-generated by @schema
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module AddItemBehavior = {
|
|
40
|
+
module Spec = AddItemSpec
|
|
41
|
+
let moduleUrl: string = %raw(`import.meta.url`)
|
|
42
|
+
|
|
43
|
+
type state = bool // true = item exists
|
|
44
|
+
let initialState = false
|
|
45
|
+
|
|
46
|
+
let evolve = (_state: state, _event: Spec.consumedEvent) => true // any event means item exists
|
|
47
|
+
|
|
48
|
+
let decide = (state: state, command: Spec.command): result<array<Spec.event>, Spec.error> =>
|
|
49
|
+
if state {
|
|
50
|
+
Error(ItemAlreadyExists)
|
|
51
|
+
} else {
|
|
52
|
+
switch command {
|
|
53
|
+
| AddItem({id, name}) => Ok([Spec.ItemAdded({id, name})])
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─────────────────────────────────────────────────────────────
|
|
59
|
+
// Isolated bus for this test suite
|
|
60
|
+
// ─────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
module Bus = LocalBus.Make()
|
|
63
|
+
|
|
64
|
+
// Topic name = DcbEventLog name ++ "EventTopic" = "ItemEventLogEventTopic"
|
|
65
|
+
let capturedEventCount: ref<int> = ref(0)
|
|
66
|
+
let _ = Bus.subscribeToEvents("ItemEventLogEventTopic", async (_, _, _) => {
|
|
67
|
+
capturedEventCount := capturedEventCount.contents + 1
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// ─────────────────────────────────────────────────────────────
|
|
71
|
+
// Activate Pulumi mock mode (must be before any Component.make)
|
|
72
|
+
// ─────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
let _ = TestRunner.setup()
|
|
75
|
+
|
|
76
|
+
// ─────────────────────────────────────────────────────────────
|
|
77
|
+
// Build DcbEventLog
|
|
78
|
+
// ─────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
module ItemEventLogMaker = DcbEventLog_Builder.Make(Bus)
|
|
81
|
+
let eventLog = ItemEventLogMaker.make(~name="ItemEventLog", ~partitionTag=Reventless.DcbTag.Simple({key: "id"}))
|
|
82
|
+
|
|
83
|
+
// ─────────────────────────────────────────────────────────────
|
|
84
|
+
// Build StateChangeSlice
|
|
85
|
+
// ─────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
module AddItemMaker = StateChangeSlice_Builder.Make(AddItemSpec, AddItemBehavior)
|
|
88
|
+
|
|
89
|
+
// publishJsons routing — dispatches each command to its registered handler.
|
|
90
|
+
let publishJsons: ReventlessInfra.CommandTopic.publishJsons = async cmdJsons => {
|
|
91
|
+
let _ = await cmdJsons
|
|
92
|
+
->Array.map(async cmdJson => {
|
|
93
|
+
let typeName = switch cmdJson.commandJson {
|
|
94
|
+
| JSON.Object(dict) =>
|
|
95
|
+
dict
|
|
96
|
+
->Dict.get("TAG")
|
|
97
|
+
->Option.flatMap(j =>
|
|
98
|
+
switch j {
|
|
99
|
+
| JSON.String(s) => Some(s)
|
|
100
|
+
| _ => None
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
->Option.getOr("")
|
|
104
|
+
| _ => ""
|
|
105
|
+
}
|
|
106
|
+
let fullBody = JSON.Encode.object(
|
|
107
|
+
Dict.fromArray([
|
|
108
|
+
("id", JSON.Encode.string(cmdJson.id)),
|
|
109
|
+
("meta", cmdJson.meta->S.reverseConvertToJsonOrThrow(Reventless.Message.metaSchema)),
|
|
110
|
+
("command", cmdJson.commandJson),
|
|
111
|
+
]),
|
|
112
|
+
)
|
|
113
|
+
let handlers = ReventlessCore.CommandTopic.getHandlers(typeName)
|
|
114
|
+
let _ = await handlers
|
|
115
|
+
->Array.map(async entry => {
|
|
116
|
+
let item: ReventlessInfra.CommandTopic.topicItem<JSON.t> = {
|
|
117
|
+
reference: cmdJson.id,
|
|
118
|
+
command: fullBody,
|
|
119
|
+
}
|
|
120
|
+
let _ = await entry.handler(Stream.fromIterable([item]))->Effect.runPromise
|
|
121
|
+
})
|
|
122
|
+
->Promise.all
|
|
123
|
+
})
|
|
124
|
+
->Promise.all
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let publishJsonsOutput = publishJsons->Pulumi.Output.make
|
|
128
|
+
|
|
129
|
+
let _addItemSlice = AddItemMaker.make(~dcbEventLog=eventLog, ~publishJsons=publishJsonsOutput)
|
|
130
|
+
|
|
131
|
+
// ─────────────────────────────────────────────────────────────
|
|
132
|
+
// Test helpers
|
|
133
|
+
// ─────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
let dispatch = async (commandJson, id) =>
|
|
136
|
+
await publishJsons([{Reventless.Message.id, meta: testMeta, commandJson}])
|
|
137
|
+
|
|
138
|
+
let tagQuery = (id: string): Reventless.DcbTag.query => [
|
|
139
|
+
{tags: [{Reventless.DcbTag.key: "id", value: id}]},
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
let typeQuery = (eventType: string): Reventless.DcbTag.query => [{eventTypes: [eventType]}]
|
|
143
|
+
|
|
144
|
+
let addItemJson = (id, name) =>
|
|
145
|
+
AddItemSpec.AddItem({id, name})->S.reverseConvertToJsonOrThrow(AddItemSpec.commandSchema)
|