@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,339 @@
|
|
|
1
|
+
// SQLite-backed QueryDb storage.
|
|
2
|
+
//
|
|
3
|
+
// One table per registered QueryDb (`qdb_<name>`). Columns:
|
|
4
|
+
// partition_key TEXT
|
|
5
|
+
// sub_key TEXT
|
|
6
|
+
// item TEXT (JSON.stringify of the item)
|
|
7
|
+
// expires_at INTEGER NULL (unix epoch seconds; null = never expires)
|
|
8
|
+
// PRIMARY KEY (partition_key, sub_key)
|
|
9
|
+
//
|
|
10
|
+
// TTL: every read clause carries `(expires_at IS NULL OR expires_at > <now>)`.
|
|
11
|
+
// Lazy expiry — no background sweeper. Expired rows linger on disk until they
|
|
12
|
+
// are next read (where they are filtered out) or overwritten by a new save.
|
|
13
|
+
//
|
|
14
|
+
// GSI: each declared `indexConfig` creates a SQLite index on the JSON column
|
|
15
|
+
// using `json_extract(item, '$.<field>')`. Composite keys (`pkFields` /
|
|
16
|
+
// `skFields` joined by `pkSep`/`skSep`) become a single computed expression.
|
|
17
|
+
// The DynamoDB `projectionType` is recorded for documentation but the SQLite
|
|
18
|
+
// covering-index distinction is irrelevant — every column is on the same row.
|
|
19
|
+
|
|
20
|
+
open ReventlessCore
|
|
21
|
+
open Reventless.ReadModel
|
|
22
|
+
|
|
23
|
+
let tableName = (name: string): string => "qdb_" ++ name->String.replaceAll("-", "_")
|
|
24
|
+
|
|
25
|
+
// SQL identifier sanitisation — collapse anything outside [A-Za-z0-9_] to _.
|
|
26
|
+
// Keeps generated index names predictable and injection-safe.
|
|
27
|
+
let sanitizeIdent = (s: string): string =>
|
|
28
|
+
s
|
|
29
|
+
->String.split("")
|
|
30
|
+
->Array.map(ch => {
|
|
31
|
+
let code = ch->String.codePointAt(0)->Option.getOr(0)
|
|
32
|
+
let isAlphaNum =
|
|
33
|
+
(code >= 48 && code <= 57) ||
|
|
34
|
+
(code >= 65 && code <= 90) ||
|
|
35
|
+
(code >= 97 && code <= 122) ||
|
|
36
|
+
code == 95
|
|
37
|
+
isAlphaNum ? ch : "_"
|
|
38
|
+
})
|
|
39
|
+
->Array.join("")
|
|
40
|
+
|
|
41
|
+
let ensureSchema = (~db, ~table) =>
|
|
42
|
+
db->SqliteDriver.exec(
|
|
43
|
+
`CREATE TABLE IF NOT EXISTS ${table} (partition_key TEXT NOT NULL, sub_key TEXT NOT NULL DEFAULT '', item TEXT NOT NULL, expires_at INTEGER, PRIMARY KEY (partition_key, sub_key))`,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// Best-effort migration for tables created by an earlier (Phase 2) version
|
|
47
|
+
// that did not have the expires_at column. Safe no-op when column already
|
|
48
|
+
// exists. Wrapped in try/catch since some sqlite versions throw on duplicate.
|
|
49
|
+
let ensureExpiresColumn = (~db, ~table) =>
|
|
50
|
+
try db->SqliteDriver.exec(`ALTER TABLE ${table} ADD COLUMN expires_at INTEGER`) catch {
|
|
51
|
+
| _ => ()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Build the json_extract expression for a single field path.
|
|
55
|
+
let jsonField = (field: string): string => `json_extract(item, '$.${field}')`
|
|
56
|
+
|
|
57
|
+
// Composite-key indexes are concatenations: json_extract(...) || sep || json_extract(...) || ...
|
|
58
|
+
let compositeExpr = (fields: array<string>, sep: string): string => {
|
|
59
|
+
let escapedSep = sep->String.replaceAll("'", "''")
|
|
60
|
+
fields->Array.map(jsonField)->Array.join(` || '${escapedSep}' || `)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Determine the index's partition expression and (optional) sort expression.
|
|
64
|
+
// Uses pkFields if present, else falls back to idField; same for sub key.
|
|
65
|
+
let indexExpressions = (idx: indexConfig): (string, option<string>) => {
|
|
66
|
+
let pkExpr = switch idx.pkFields {
|
|
67
|
+
| Some(fields) if fields->Array.length > 0 =>
|
|
68
|
+
compositeExpr(fields, idx.pkSep->Option.getOr("/"))
|
|
69
|
+
| _ =>
|
|
70
|
+
switch idx.idField {
|
|
71
|
+
| Some(field) => jsonField(field)
|
|
72
|
+
| None => jsonField(idx.index)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
let skExpr = switch idx.skFields {
|
|
76
|
+
| Some(fields) if fields->Array.length > 0 =>
|
|
77
|
+
Some(compositeExpr(fields, idx.skSep->Option.getOr("/")))
|
|
78
|
+
| _ =>
|
|
79
|
+
switch idx.subIdField {
|
|
80
|
+
| Some(field) => Some(jsonField(field))
|
|
81
|
+
| None => None
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
(pkExpr, skExpr)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Create one SQLite index per declared GSI. Idempotent via IF NOT EXISTS.
|
|
88
|
+
let ensureIndexes = (~db, ~table, ~indexes: array<indexConfig>) =>
|
|
89
|
+
indexes->Array.forEach(idx => {
|
|
90
|
+
let (pkExpr, skExprOpt) = indexExpressions(idx)
|
|
91
|
+
let indexName = `idx_${table}_${sanitizeIdent(idx.index)}`
|
|
92
|
+
let cols = switch skExprOpt {
|
|
93
|
+
| Some(skExpr) => `${pkExpr}, ${skExpr}`
|
|
94
|
+
| None => pkExpr
|
|
95
|
+
}
|
|
96
|
+
db->SqliteDriver.exec(`CREATE INDEX IF NOT EXISTS ${indexName} ON ${table} (${cols})`)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
let decodeItem = (row: dict<JSON.t>): JSON.t =>
|
|
100
|
+
switch row->Dict.get("item") {
|
|
101
|
+
| Some(JSON.String(s)) =>
|
|
102
|
+
switch JSON.parseOrThrow(s) {
|
|
103
|
+
| json => json
|
|
104
|
+
| exception _ => JSON.Encode.null
|
|
105
|
+
}
|
|
106
|
+
| _ => JSON.Encode.null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let computeSubKey = (item: JSON.t, subIdField: option<string>): string =>
|
|
110
|
+
switch subIdField {
|
|
111
|
+
| None => ""
|
|
112
|
+
| Some(field) =>
|
|
113
|
+
switch item->JSON.Decode.object {
|
|
114
|
+
| Some(obj) =>
|
|
115
|
+
switch obj->Dict.get(field) {
|
|
116
|
+
| Some(JSON.String(s)) => s
|
|
117
|
+
| _ => ""
|
|
118
|
+
}
|
|
119
|
+
| None => ""
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let withId = (item: JSON.t, partitionKey: string): JSON.t =>
|
|
124
|
+
switch item->JSON.Decode.object {
|
|
125
|
+
| Some(obj) =>
|
|
126
|
+
if obj->Dict.get("id")->Option.isSome {
|
|
127
|
+
item
|
|
128
|
+
} else {
|
|
129
|
+
let copy = Dict.make()
|
|
130
|
+
obj->Dict.toArray->Array.forEach(((k, v)) => copy->Dict.set(k, v))
|
|
131
|
+
copy->Dict.set("id", JSON.Encode.string(partitionKey))
|
|
132
|
+
JSON.Encode.object(copy)
|
|
133
|
+
}
|
|
134
|
+
| None => item
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
type busCallbacks = {
|
|
138
|
+
publishStateChange: (~name: string, ~descriptor: JSON.t) => unit,
|
|
139
|
+
registerQueryDb: (string, QueryDb_Adapter.operations) => unit,
|
|
140
|
+
registerQueryDbScan: (string, unit => array<JSON.t>) => unit,
|
|
141
|
+
registerQueryDbStream: (string, unit => Stream.t<JSON.t, string, unit>) => unit,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// SQL fragment that excludes expired rows. Uses unix-epoch seconds via
|
|
145
|
+
// strftime; SQLite evaluates it on every row, which is fine at dev scale.
|
|
146
|
+
let notExpiredClause = "(expires_at IS NULL OR expires_at > strftime('%s','now'))"
|
|
147
|
+
|
|
148
|
+
let makeStorage = (
|
|
149
|
+
~db: SqliteDriver.t,
|
|
150
|
+
~bus: busCallbacks,
|
|
151
|
+
~name,
|
|
152
|
+
~indexes: array<indexConfig>,
|
|
153
|
+
~subIdField: option<string>,
|
|
154
|
+
): QueryDb_Adapter.storage => {
|
|
155
|
+
let table = tableName(name)
|
|
156
|
+
ensureSchema(~db, ~table)
|
|
157
|
+
ensureExpiresColumn(~db, ~table)
|
|
158
|
+
ensureIndexes(~db, ~table, ~indexes)
|
|
159
|
+
|
|
160
|
+
let upsertStmt = db->SqliteDriver.prepare(
|
|
161
|
+
`INSERT INTO ${table}(partition_key, sub_key, item, expires_at) VALUES(?,?,?,?) ON CONFLICT(partition_key, sub_key) DO UPDATE SET item = excluded.item, expires_at = excluded.expires_at`,
|
|
162
|
+
)
|
|
163
|
+
let selectByPartitionStmt = db->SqliteDriver.prepare(
|
|
164
|
+
`SELECT partition_key, sub_key, item FROM ${table} WHERE partition_key = ? AND ${notExpiredClause} ORDER BY sub_key ASC`,
|
|
165
|
+
)
|
|
166
|
+
let deleteByPartitionStmt = db->SqliteDriver.prepare(
|
|
167
|
+
`DELETE FROM ${table} WHERE partition_key = ?`,
|
|
168
|
+
)
|
|
169
|
+
let deleteBySubKeyStmt = db->SqliteDriver.prepare(
|
|
170
|
+
`DELETE FROM ${table} WHERE partition_key = ? AND sub_key = ?`,
|
|
171
|
+
)
|
|
172
|
+
let scanAllStmt = db->SqliteDriver.prepare(
|
|
173
|
+
`SELECT partition_key, sub_key, item FROM ${table} WHERE ${notExpiredClause} ORDER BY partition_key, sub_key`,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
let rowsFor = (id: string): array<JSON.t> =>
|
|
177
|
+
selectByPartitionStmt
|
|
178
|
+
->SqliteDriver.all([JSON.Encode.string(id)])
|
|
179
|
+
->Array.map(decodeItem)
|
|
180
|
+
|
|
181
|
+
let allRows = (): array<JSON.t> =>
|
|
182
|
+
scanAllStmt
|
|
183
|
+
->SqliteDriver.all([])
|
|
184
|
+
->Array.map(row => {
|
|
185
|
+
let item = decodeItem(row)
|
|
186
|
+
let partition = switch row->Dict.get("partition_key") {
|
|
187
|
+
| Some(JSON.String(s)) => s
|
|
188
|
+
| _ => ""
|
|
189
|
+
}
|
|
190
|
+
withId(item, partition)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
let saveOne = (id: string, state: JSON.t, ttl: option<int>) => {
|
|
194
|
+
let subKey = computeSubKey(state, subIdField)
|
|
195
|
+
let expiresAt = switch ttl {
|
|
196
|
+
| Some(t) => JSON.Encode.int(t)
|
|
197
|
+
| None => JSON.Encode.null
|
|
198
|
+
}
|
|
199
|
+
upsertStmt->SqliteDriver.run([
|
|
200
|
+
JSON.Encode.string(id),
|
|
201
|
+
JSON.Encode.string(subKey),
|
|
202
|
+
JSON.Encode.string(JSON.stringify(state)),
|
|
203
|
+
expiresAt,
|
|
204
|
+
])
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let load: QueryDb.load<string, JSON.t> = async id => Ok(rowsFor(id))
|
|
208
|
+
|
|
209
|
+
let loadStream: QueryDb.loadStream<string, JSON.t> = id => rowsFor(id)->Stream.fromIterable
|
|
210
|
+
|
|
211
|
+
// Entity key matches the AWS StateTopic Lambda output (Phase 1):
|
|
212
|
+
// single-key tables → partition value; composite tables → `pk-sk`.
|
|
213
|
+
let entityKeyFor = (id: string, subKey: string): string =>
|
|
214
|
+
switch subIdField {
|
|
215
|
+
| Some(_) => id ++ "-" ++ subKey
|
|
216
|
+
| None => id
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let publishUpdated = (id: string, state: JSON.t) => {
|
|
220
|
+
let subKey = computeSubKey(state, subIdField)
|
|
221
|
+
let descriptor = LocalBus.makeStateChangeDescriptor(
|
|
222
|
+
~changeKind="Updated",
|
|
223
|
+
~id=entityKeyFor(id, subKey),
|
|
224
|
+
~state=Some(state),
|
|
225
|
+
)
|
|
226
|
+
bus.publishStateChange(~name, ~descriptor)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let publishRemoved = (id: string, subKey: string) => {
|
|
230
|
+
let descriptor = LocalBus.makeStateChangeDescriptor(
|
|
231
|
+
~changeKind="Removed",
|
|
232
|
+
~id=entityKeyFor(id, subKey),
|
|
233
|
+
~state=None,
|
|
234
|
+
)
|
|
235
|
+
bus.publishStateChange(~name, ~descriptor)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Read all (id, sub_key) tuples that a partition-level delete would remove.
|
|
239
|
+
let rowKeysForPartition = (id: string): array<string> =>
|
|
240
|
+
selectByPartitionStmt
|
|
241
|
+
->SqliteDriver.all([JSON.Encode.string(id)])
|
|
242
|
+
->Array.map(row =>
|
|
243
|
+
switch row->Dict.get("sub_key") {
|
|
244
|
+
| Some(JSON.String(s)) => s
|
|
245
|
+
| _ => ""
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
let save: QueryDb.save<string, JSON.t> = async (id, state, _saveMode, ttl) => {
|
|
250
|
+
saveOne(id, state, ttl)
|
|
251
|
+
publishUpdated(id, state)
|
|
252
|
+
Ok()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let saveBatch: QueryDb.saveBatch<string, JSON.t> = async batch => {
|
|
256
|
+
db->SqliteDriver.transaction(() =>
|
|
257
|
+
batch->Array.forEach(((id, state, ttl)) => saveOne(id, state, ttl))
|
|
258
|
+
)
|
|
259
|
+
batch->Array.forEach(((id, state, _)) => publishUpdated(id, state))
|
|
260
|
+
Ok()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let count: QueryDb.count<string> = async (_id, _fieldName, inc) => Ok(inc)
|
|
264
|
+
|
|
265
|
+
let deleteOne = (id: string, subIdOpt) =>
|
|
266
|
+
switch subIdOpt {
|
|
267
|
+
| None => deleteByPartitionStmt->SqliteDriver.run([JSON.Encode.string(id)])
|
|
268
|
+
| Some((_, subValue)) =>
|
|
269
|
+
deleteBySubKeyStmt->SqliteDriver.run([
|
|
270
|
+
JSON.Encode.string(id),
|
|
271
|
+
JSON.Encode.string(subValue),
|
|
272
|
+
])
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let delete: QueryDb.delete<string> = async (id, subIdOpt) => {
|
|
276
|
+
let removedSubKeys = switch subIdOpt {
|
|
277
|
+
| None => rowKeysForPartition(id)
|
|
278
|
+
| Some((_, subValue)) => [subValue]
|
|
279
|
+
}
|
|
280
|
+
deleteOne(id, subIdOpt)
|
|
281
|
+
removedSubKeys->Array.forEach(sk => publishRemoved(id, sk))
|
|
282
|
+
Ok()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let deleteBatch: QueryDb.deleteBatch<string> = async ids => {
|
|
286
|
+
let removed: array<(string, string)> = []
|
|
287
|
+
ids->Array.forEach(((id, subIdOpt)) => {
|
|
288
|
+
switch subIdOpt {
|
|
289
|
+
| None => rowKeysForPartition(id)->Array.forEach(sk => removed->Array.push((id, sk)))
|
|
290
|
+
| Some((_, subValue)) => removed->Array.push((id, subValue))
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
db->SqliteDriver.transaction(() => ids->Array.forEach(((id, s)) => deleteOne(id, s)))
|
|
294
|
+
removed->Array.forEach(((id, sk)) => publishRemoved(id, sk))
|
|
295
|
+
Ok()
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let ops: QueryDb_Adapter.operations = {
|
|
299
|
+
load,
|
|
300
|
+
loadStream,
|
|
301
|
+
save,
|
|
302
|
+
saveBatch,
|
|
303
|
+
count,
|
|
304
|
+
delete,
|
|
305
|
+
deleteBatch,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
bus.registerQueryDb(name, ops)
|
|
309
|
+
bus.registerQueryDbScan(name, () => allRows())
|
|
310
|
+
bus.registerQueryDbStream(name, () => allRows()->Stream.fromIterable)
|
|
311
|
+
|
|
312
|
+
{
|
|
313
|
+
resources: [],
|
|
314
|
+
dataSourceName: ""->Pulumi.Output.make,
|
|
315
|
+
operations: Pulumi.Output.make(ops),
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
module Make = (Bus: LocalBus.T, DbProvider: {let db: SqliteDriver.t}) => {
|
|
320
|
+
type api = unit
|
|
321
|
+
type role = unit
|
|
322
|
+
|
|
323
|
+
let busCallbacks = {
|
|
324
|
+
publishStateChange: Bus.publishStateChange,
|
|
325
|
+
registerQueryDb: Bus.registerQueryDb,
|
|
326
|
+
registerQueryDbScan: Bus.registerQueryDbScan,
|
|
327
|
+
registerQueryDbStream: Bus.registerQueryDbStream,
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let make: QueryDb_Adapter.storageMaker<unit, unit> = (
|
|
331
|
+
~name,
|
|
332
|
+
~indexes,
|
|
333
|
+
~subIdField=?,
|
|
334
|
+
~ttl as _=?,
|
|
335
|
+
~api as _,
|
|
336
|
+
~apiRole as _,
|
|
337
|
+
~opts as _,
|
|
338
|
+
) => makeStorage(~db=DbProvider.db, ~bus=busCallbacks, ~name, ~indexes, ~subIdField)
|
|
339
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Stream from "effect/Stream";
|
|
6
|
+
import * as Pulumi from "@pulumi/pulumi";
|
|
7
|
+
import * as LocalBus$ReventlessLocal from "../LocalBus.res.mjs";
|
|
8
|
+
import * as SqliteDriver$ReventlessLocal from "../SqliteDriver.res.mjs";
|
|
9
|
+
|
|
10
|
+
function tableName(name) {
|
|
11
|
+
return "qdb_" + name.replaceAll("-", "_");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function sanitizeIdent(s) {
|
|
15
|
+
return s.split("").map(ch => {
|
|
16
|
+
let code = Stdlib_Option.getOr(ch.codePointAt(0), 0);
|
|
17
|
+
let isAlphaNum = code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95;
|
|
18
|
+
if (isAlphaNum) {
|
|
19
|
+
return ch;
|
|
20
|
+
} else {
|
|
21
|
+
return "_";
|
|
22
|
+
}
|
|
23
|
+
}).join("");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ensureSchema(db, table) {
|
|
27
|
+
SqliteDriver$ReventlessLocal.exec(db, `CREATE TABLE IF NOT EXISTS ` + table + ` (partition_key TEXT NOT NULL, sub_key TEXT NOT NULL DEFAULT '', item TEXT NOT NULL, expires_at INTEGER, PRIMARY KEY (partition_key, sub_key))`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ensureExpiresColumn(db, table) {
|
|
31
|
+
try {
|
|
32
|
+
return SqliteDriver$ReventlessLocal.exec(db, `ALTER TABLE ` + table + ` ADD COLUMN expires_at INTEGER`);
|
|
33
|
+
} catch (exn) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function jsonField(field) {
|
|
39
|
+
return `json_extract(item, '$.` + field + `')`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function compositeExpr(fields, sep) {
|
|
43
|
+
let escapedSep = sep.replaceAll("'", "''");
|
|
44
|
+
return fields.map(jsonField).join(` || '` + escapedSep + `' || `);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function indexExpressions(idx) {
|
|
48
|
+
let fields = idx.pkFields;
|
|
49
|
+
let pkExpr;
|
|
50
|
+
let exit = 0;
|
|
51
|
+
if (fields !== undefined && fields.length !== 0) {
|
|
52
|
+
pkExpr = compositeExpr(fields, Stdlib_Option.getOr(idx.pkSep, "/"));
|
|
53
|
+
} else {
|
|
54
|
+
exit = 1;
|
|
55
|
+
}
|
|
56
|
+
if (exit === 1) {
|
|
57
|
+
let field = idx.idField;
|
|
58
|
+
pkExpr = field !== undefined ? jsonField(field) : jsonField(idx.index);
|
|
59
|
+
}
|
|
60
|
+
let fields$1 = idx.skFields;
|
|
61
|
+
let skExpr;
|
|
62
|
+
let exit$1 = 0;
|
|
63
|
+
if (fields$1 !== undefined && fields$1.length !== 0) {
|
|
64
|
+
skExpr = compositeExpr(fields$1, Stdlib_Option.getOr(idx.skSep, "/"));
|
|
65
|
+
} else {
|
|
66
|
+
exit$1 = 1;
|
|
67
|
+
}
|
|
68
|
+
if (exit$1 === 1) {
|
|
69
|
+
let field$1 = idx.subIdField;
|
|
70
|
+
skExpr = field$1 !== undefined ? jsonField(field$1) : undefined;
|
|
71
|
+
}
|
|
72
|
+
return [
|
|
73
|
+
pkExpr,
|
|
74
|
+
skExpr
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function ensureIndexes(db, table, indexes) {
|
|
79
|
+
indexes.forEach(idx => {
|
|
80
|
+
let match = indexExpressions(idx);
|
|
81
|
+
let skExprOpt = match[1];
|
|
82
|
+
let pkExpr = match[0];
|
|
83
|
+
let indexName = `idx_` + table + `_` + sanitizeIdent(idx.index);
|
|
84
|
+
let cols = skExprOpt !== undefined ? pkExpr + `, ` + skExprOpt : pkExpr;
|
|
85
|
+
SqliteDriver$ReventlessLocal.exec(db, `CREATE INDEX IF NOT EXISTS ` + indexName + ` ON ` + table + ` (` + cols + `)`);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function decodeItem(row) {
|
|
90
|
+
let match = row["item"];
|
|
91
|
+
if (match === undefined) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (typeof match !== "string") {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(match);
|
|
99
|
+
} catch (exn) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function computeSubKey(item, subIdField) {
|
|
105
|
+
if (subIdField === undefined) {
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
let obj = Stdlib_JSON.Decode.object(item);
|
|
109
|
+
if (obj === undefined) {
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
let match = obj[subIdField];
|
|
113
|
+
if (typeof match === "string") {
|
|
114
|
+
return match;
|
|
115
|
+
} else {
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function withId(item, partitionKey) {
|
|
121
|
+
let obj = Stdlib_JSON.Decode.object(item);
|
|
122
|
+
if (obj === undefined) {
|
|
123
|
+
return item;
|
|
124
|
+
}
|
|
125
|
+
if (Stdlib_Option.isSome(obj["id"])) {
|
|
126
|
+
return item;
|
|
127
|
+
}
|
|
128
|
+
let copy = {};
|
|
129
|
+
Object.entries(obj).forEach(param => {
|
|
130
|
+
copy[param[0]] = param[1];
|
|
131
|
+
});
|
|
132
|
+
copy["id"] = partitionKey;
|
|
133
|
+
return copy;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let notExpiredClause = "(expires_at IS NULL OR expires_at > strftime('%s','now'))";
|
|
137
|
+
|
|
138
|
+
function makeStorage(db, bus, name, indexes, subIdField) {
|
|
139
|
+
let table = tableName(name);
|
|
140
|
+
ensureSchema(db, table);
|
|
141
|
+
ensureExpiresColumn(db, table);
|
|
142
|
+
ensureIndexes(db, table, indexes);
|
|
143
|
+
let upsertStmt = SqliteDriver$ReventlessLocal.prepare(db, `INSERT INTO ` + table + `(partition_key, sub_key, item, expires_at) VALUES(?,?,?,?) ON CONFLICT(partition_key, sub_key) DO UPDATE SET item = excluded.item, expires_at = excluded.expires_at`);
|
|
144
|
+
let selectByPartitionStmt = SqliteDriver$ReventlessLocal.prepare(db, `SELECT partition_key, sub_key, item FROM ` + table + ` WHERE partition_key = ? AND ` + notExpiredClause + ` ORDER BY sub_key ASC`);
|
|
145
|
+
let deleteByPartitionStmt = SqliteDriver$ReventlessLocal.prepare(db, `DELETE FROM ` + table + ` WHERE partition_key = ?`);
|
|
146
|
+
let deleteBySubKeyStmt = SqliteDriver$ReventlessLocal.prepare(db, `DELETE FROM ` + table + ` WHERE partition_key = ? AND sub_key = ?`);
|
|
147
|
+
let scanAllStmt = SqliteDriver$ReventlessLocal.prepare(db, `SELECT partition_key, sub_key, item FROM ` + table + ` WHERE ` + notExpiredClause + ` ORDER BY partition_key, sub_key`);
|
|
148
|
+
let rowsFor = id => SqliteDriver$ReventlessLocal.all(selectByPartitionStmt, [id]).map(decodeItem);
|
|
149
|
+
let allRows = () => SqliteDriver$ReventlessLocal.all(scanAllStmt, []).map(row => {
|
|
150
|
+
let item = decodeItem(row);
|
|
151
|
+
let match = row["partition_key"];
|
|
152
|
+
let partition = typeof match === "string" ? match : "";
|
|
153
|
+
return withId(item, partition);
|
|
154
|
+
});
|
|
155
|
+
let saveOne = (id, state, ttl) => {
|
|
156
|
+
let subKey = computeSubKey(state, subIdField);
|
|
157
|
+
let expiresAt = ttl !== undefined ? ttl : null;
|
|
158
|
+
SqliteDriver$ReventlessLocal.run(upsertStmt, [
|
|
159
|
+
id,
|
|
160
|
+
subKey,
|
|
161
|
+
JSON.stringify(state),
|
|
162
|
+
expiresAt
|
|
163
|
+
]);
|
|
164
|
+
};
|
|
165
|
+
let load = async id => ({
|
|
166
|
+
TAG: "Ok",
|
|
167
|
+
_0: rowsFor(id)
|
|
168
|
+
});
|
|
169
|
+
let loadStream = id => Stream.fromIterable(rowsFor(id));
|
|
170
|
+
let entityKeyFor = (id, subKey) => {
|
|
171
|
+
if (subIdField !== undefined) {
|
|
172
|
+
return id + "-" + subKey;
|
|
173
|
+
} else {
|
|
174
|
+
return id;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
let publishUpdated = (id, state) => {
|
|
178
|
+
let subKey = computeSubKey(state, subIdField);
|
|
179
|
+
let descriptor = LocalBus$ReventlessLocal.makeStateChangeDescriptor("Updated", entityKeyFor(id, subKey), state);
|
|
180
|
+
bus.publishStateChange(name, descriptor);
|
|
181
|
+
};
|
|
182
|
+
let publishRemoved = (id, subKey) => {
|
|
183
|
+
let descriptor = LocalBus$ReventlessLocal.makeStateChangeDescriptor("Removed", entityKeyFor(id, subKey), undefined);
|
|
184
|
+
bus.publishStateChange(name, descriptor);
|
|
185
|
+
};
|
|
186
|
+
let rowKeysForPartition = id => SqliteDriver$ReventlessLocal.all(selectByPartitionStmt, [id]).map(row => {
|
|
187
|
+
let match = row["sub_key"];
|
|
188
|
+
if (typeof match === "string") {
|
|
189
|
+
return match;
|
|
190
|
+
} else {
|
|
191
|
+
return "";
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
let save = async (id, state, _saveMode, ttl) => {
|
|
195
|
+
saveOne(id, state, ttl);
|
|
196
|
+
publishUpdated(id, state);
|
|
197
|
+
return {
|
|
198
|
+
TAG: "Ok",
|
|
199
|
+
_0: undefined
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
let saveBatch = async batch => {
|
|
203
|
+
SqliteDriver$ReventlessLocal.transaction(db, () => {
|
|
204
|
+
batch.forEach(param => saveOne(param[0], param[1], param[2]));
|
|
205
|
+
});
|
|
206
|
+
batch.forEach(param => publishUpdated(param[0], param[1]));
|
|
207
|
+
return {
|
|
208
|
+
TAG: "Ok",
|
|
209
|
+
_0: undefined
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
let count = async (_id, _fieldName, inc) => ({
|
|
213
|
+
TAG: "Ok",
|
|
214
|
+
_0: inc
|
|
215
|
+
});
|
|
216
|
+
let deleteOne = (id, subIdOpt) => {
|
|
217
|
+
if (subIdOpt !== undefined) {
|
|
218
|
+
return SqliteDriver$ReventlessLocal.run(deleteBySubKeyStmt, [
|
|
219
|
+
id,
|
|
220
|
+
subIdOpt[1]
|
|
221
|
+
]);
|
|
222
|
+
} else {
|
|
223
|
+
return SqliteDriver$ReventlessLocal.run(deleteByPartitionStmt, [id]);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
let $$delete = async (id, subIdOpt) => {
|
|
227
|
+
let removedSubKeys = subIdOpt !== undefined ? [subIdOpt[1]] : rowKeysForPartition(id);
|
|
228
|
+
deleteOne(id, subIdOpt);
|
|
229
|
+
removedSubKeys.forEach(sk => publishRemoved(id, sk));
|
|
230
|
+
return {
|
|
231
|
+
TAG: "Ok",
|
|
232
|
+
_0: undefined
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
let deleteBatch = async ids => {
|
|
236
|
+
let removed = [];
|
|
237
|
+
ids.forEach(param => {
|
|
238
|
+
let subIdOpt = param[1];
|
|
239
|
+
let id = param[0];
|
|
240
|
+
if (subIdOpt !== undefined) {
|
|
241
|
+
removed.push([
|
|
242
|
+
id,
|
|
243
|
+
subIdOpt[1]
|
|
244
|
+
]);
|
|
245
|
+
} else {
|
|
246
|
+
rowKeysForPartition(id).forEach(sk => {
|
|
247
|
+
removed.push([
|
|
248
|
+
id,
|
|
249
|
+
sk
|
|
250
|
+
]);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
SqliteDriver$ReventlessLocal.transaction(db, () => {
|
|
255
|
+
ids.forEach(param => deleteOne(param[0], param[1]));
|
|
256
|
+
});
|
|
257
|
+
removed.forEach(param => publishRemoved(param[0], param[1]));
|
|
258
|
+
return {
|
|
259
|
+
TAG: "Ok",
|
|
260
|
+
_0: undefined
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
let ops = {
|
|
264
|
+
load: load,
|
|
265
|
+
loadStream: loadStream,
|
|
266
|
+
save: save,
|
|
267
|
+
saveBatch: saveBatch,
|
|
268
|
+
count: count,
|
|
269
|
+
delete: $$delete,
|
|
270
|
+
deleteBatch: deleteBatch
|
|
271
|
+
};
|
|
272
|
+
bus.registerQueryDb(name, ops);
|
|
273
|
+
bus.registerQueryDbScan(name, () => allRows());
|
|
274
|
+
bus.registerQueryDbStream(name, () => Stream.fromIterable(allRows()));
|
|
275
|
+
return {
|
|
276
|
+
resources: [],
|
|
277
|
+
dataSourceName: Pulumi.output(""),
|
|
278
|
+
operations: Pulumi.output(ops)
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function Make(Bus) {
|
|
283
|
+
return DbProvider => {
|
|
284
|
+
let busCallbacks_publishStateChange = Bus.publishStateChange;
|
|
285
|
+
let busCallbacks_registerQueryDb = Bus.registerQueryDb;
|
|
286
|
+
let busCallbacks_registerQueryDbScan = Bus.registerQueryDbScan;
|
|
287
|
+
let busCallbacks_registerQueryDbStream = Bus.registerQueryDbStream;
|
|
288
|
+
let busCallbacks = {
|
|
289
|
+
publishStateChange: busCallbacks_publishStateChange,
|
|
290
|
+
registerQueryDb: busCallbacks_registerQueryDb,
|
|
291
|
+
registerQueryDbScan: busCallbacks_registerQueryDbScan,
|
|
292
|
+
registerQueryDbStream: busCallbacks_registerQueryDbStream
|
|
293
|
+
};
|
|
294
|
+
let make = (name, indexes, subIdField, param, param$1, param$2, param$3) => makeStorage(DbProvider.db, busCallbacks, name, indexes, subIdField);
|
|
295
|
+
return {
|
|
296
|
+
busCallbacks: busCallbacks,
|
|
297
|
+
make: make
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export {
|
|
303
|
+
tableName,
|
|
304
|
+
sanitizeIdent,
|
|
305
|
+
ensureSchema,
|
|
306
|
+
ensureExpiresColumn,
|
|
307
|
+
jsonField,
|
|
308
|
+
compositeExpr,
|
|
309
|
+
indexExpressions,
|
|
310
|
+
ensureIndexes,
|
|
311
|
+
decodeItem,
|
|
312
|
+
computeSubKey,
|
|
313
|
+
withId,
|
|
314
|
+
notExpiredClause,
|
|
315
|
+
makeStorage,
|
|
316
|
+
Make,
|
|
317
|
+
}
|
|
318
|
+
/* effect/Stream Not a pure module */
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Pure sort key filtering for sub-ID queries.
|
|
2
|
+
// Applies DynamoDB-style sort key conditions to an already-sorted item list.
|
|
3
|
+
// Used by QueryDbResolvers_GraphQL for {name}ById queries.
|
|
4
|
+
|
|
5
|
+
type result = {
|
|
6
|
+
items: array<JSON.t>,
|
|
7
|
+
nextToken: option<string>,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Extract the sort key string from a JSON item using the given field name. */
|
|
11
|
+
let getSk = (item, skField) =>
|
|
12
|
+
item->JSON.Decode.object->Option.flatMap(d => d->Dict.get(skField))->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
13
|
+
|
|
14
|
+
/** Apply sort key conditions, ordering, and pagination to a list of JSON items.
|
|
15
|
+
- `items` must already be sorted ascending by sort key.
|
|
16
|
+
- Returns filtered+paginated items and an optional `nextToken` for the next page. */
|
|
17
|
+
let apply = (
|
|
18
|
+
~items: array<JSON.t>,
|
|
19
|
+
~skField: string,
|
|
20
|
+
~prefix: option<string>=?,
|
|
21
|
+
~from: option<string>=?,
|
|
22
|
+
~to_: option<string>=?,
|
|
23
|
+
~eq: option<string>=?,
|
|
24
|
+
~reverse: bool=false,
|
|
25
|
+
~limit: option<int>=?,
|
|
26
|
+
~offset: int=0,
|
|
27
|
+
) => {
|
|
28
|
+
let filtered = items->Array.filter(item => {
|
|
29
|
+
let sk = getSk(item, skField)
|
|
30
|
+
let okPrefix = prefix->Option.map(p => sk->String.startsWith(p))->Option.getOr(true)
|
|
31
|
+
let okFrom = from->Option.map(f => sk >= f)->Option.getOr(true)
|
|
32
|
+
let okTo = to_->Option.map(t => sk <= t)->Option.getOr(true)
|
|
33
|
+
let okEq = eq->Option.map(e => sk == e)->Option.getOr(true)
|
|
34
|
+
okPrefix && okFrom && okTo && okEq
|
|
35
|
+
})
|
|
36
|
+
let ordered = if reverse { filtered->Array.toReversed } else { filtered }
|
|
37
|
+
let sliced = ordered->Array.slice(~start=offset, ~end=ordered->Array.length)
|
|
38
|
+
let (page, hasMore) = switch limit {
|
|
39
|
+
| Some(n) => (sliced->Array.slice(~start=0, ~end=n), sliced->Array.length > n)
|
|
40
|
+
| None => (sliced, false)
|
|
41
|
+
}
|
|
42
|
+
let nextToken = if hasMore {
|
|
43
|
+
Some(Int.toString(offset + page->Array.length))
|
|
44
|
+
} else {
|
|
45
|
+
None
|
|
46
|
+
}
|
|
47
|
+
{ items: page, nextToken }
|
|
48
|
+
}
|