@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,565 @@
|
|
|
1
|
+
// Domain (plugin-facing) in-memory GraphQL server — singleton.
|
|
2
|
+
// Collects SDL fragments and resolver functions from CommandGeneratorResolvers_GraphQL
|
|
3
|
+
// (mutations), QueryDbResolvers_GraphQL (queries), and InboundTranslationResolvers_GraphQL
|
|
4
|
+
// during Platform.Make().
|
|
5
|
+
// start() is called once after all components are built.
|
|
6
|
+
//
|
|
7
|
+
// Contains domain-specific extras not present in the platform server:
|
|
8
|
+
// - Relay Global ID encoding/decoding (encodeGlobalId / decodeGlobalId)
|
|
9
|
+
// - Node type registry (registerNodeType / registerNodeResolverCallback)
|
|
10
|
+
// - /sdl HTTP endpoint for Relay compiler
|
|
11
|
+
// - rebuildSchema for schema stitching
|
|
12
|
+
//
|
|
13
|
+
// Note: the old setRegistrationTarget redirect hack has been removed. Routing
|
|
14
|
+
// to the platform server is now done explicitly via resolveTargetGraphQL() in
|
|
15
|
+
// Platform.res (mirroring the AWS resolveTargetApi() pattern).
|
|
16
|
+
|
|
17
|
+
module YG = GraphqlYoga
|
|
18
|
+
|
|
19
|
+
let log = ReventlessCore.Logger.fromEnv()
|
|
20
|
+
|
|
21
|
+
// -- Node.js HTTP bindings ---------------------------------------------------
|
|
22
|
+
// /sdl serves the printed schema; /__inmemory/login & /logout serve the A4
|
|
23
|
+
// token-issuance endpoints. Everything else falls through to graphql-yoga.
|
|
24
|
+
|
|
25
|
+
type nodeRequest = {url: string, method: string, headers: dict<string>}
|
|
26
|
+
type nodeResponse
|
|
27
|
+
@send external writeHead: (nodeResponse, int, {..}) => unit = "writeHead"
|
|
28
|
+
@send external end_: (nodeResponse, string) => unit = "end"
|
|
29
|
+
@send external endEmpty: (nodeResponse, @as(json`null`) _) => unit = "end"
|
|
30
|
+
|
|
31
|
+
// Streaming body collector — request data fires once per chunk, end once at EOF.
|
|
32
|
+
@send external _onData: (nodeRequest, @as("data") _, string => unit) => unit = "on"
|
|
33
|
+
@send external _onEnd: (nodeRequest, @as("end") _, unit => unit) => unit = "on"
|
|
34
|
+
@send external _setEncoding: (nodeRequest, string) => unit = "setEncoding"
|
|
35
|
+
|
|
36
|
+
let readBody = (req: nodeRequest, onBody: string => unit): unit => {
|
|
37
|
+
let buf = ref("")
|
|
38
|
+
req->_setEncoding("utf8")
|
|
39
|
+
req->_onData(chunk => buf := buf.contents ++ chunk)
|
|
40
|
+
req->_onEnd(() => onBody(buf.contents))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type requestHandler = (nodeRequest, nodeResponse) => unit
|
|
44
|
+
@module("http") external createServerWithHandler: requestHandler => YG.httpServer = "createServer"
|
|
45
|
+
|
|
46
|
+
// -- /__inmemory/login + /logout handlers -----------------------------------
|
|
47
|
+
|
|
48
|
+
let _writeJson = (res: nodeResponse, ~status: int, body: JSON.t): unit => {
|
|
49
|
+
res->writeHead(
|
|
50
|
+
status,
|
|
51
|
+
{"Content-Type": "application/json", "Access-Control-Allow-Origin": "*"},
|
|
52
|
+
)
|
|
53
|
+
res->end_(body->JSON.stringify)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let _loginRejected = (res: nodeResponse, ~error: string): unit =>
|
|
57
|
+
_writeJson(
|
|
58
|
+
res,
|
|
59
|
+
~status=401,
|
|
60
|
+
JSON.Encode.object(Dict.fromArray([("error", JSON.Encode.string(error))])),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
type _loginBody = {username: string, password: string}
|
|
64
|
+
|
|
65
|
+
let handleLogin = (req: nodeRequest, res: nodeResponse): unit =>
|
|
66
|
+
readBody(req, body => {
|
|
67
|
+
let parsed = try {
|
|
68
|
+
Some((body->JSON.parseOrThrow->Obj.magic: _loginBody))
|
|
69
|
+
} catch {
|
|
70
|
+
| _ => None
|
|
71
|
+
}
|
|
72
|
+
switch parsed {
|
|
73
|
+
| None => _loginRejected(res, ~error="Invalid JSON body")
|
|
74
|
+
| Some({username, password}) =>
|
|
75
|
+
let _ = LocalAuth.Login.issue(~username, ~password)->Promise.then(result => {
|
|
76
|
+
switch result {
|
|
77
|
+
| Error(msg) => _loginRejected(res, ~error=msg)
|
|
78
|
+
| Ok(token) =>
|
|
79
|
+
// Echo back the identity the client will see in subsequent
|
|
80
|
+
// ctx.identity values, so the SPA doesn't need a second round-trip.
|
|
81
|
+
let identity = switch LocalAuth.lookupUser(username) {
|
|
82
|
+
| Some(i) => i
|
|
83
|
+
| None => Reventless.Identity.anonymous
|
|
84
|
+
}
|
|
85
|
+
let identityJson =
|
|
86
|
+
identity->S.reverseConvertToJsonOrThrow(Reventless.Identity.schema)
|
|
87
|
+
_writeJson(
|
|
88
|
+
res,
|
|
89
|
+
~status=200,
|
|
90
|
+
JSON.Encode.object(
|
|
91
|
+
Dict.fromArray([
|
|
92
|
+
("token", JSON.Encode.string(token)),
|
|
93
|
+
("identity", identityJson),
|
|
94
|
+
]),
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
Promise.resolve()
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
let handleLogout = (_req: nodeRequest, res: nodeResponse): unit => {
|
|
104
|
+
res->writeHead(204, {"Access-Control-Allow-Origin": "*"})
|
|
105
|
+
res->endEmpty
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Returns true when the request carries an `Authorization: Bearer <token>`
|
|
109
|
+
// header whose token fails HMAC verification. No header (anonymous), or a
|
|
110
|
+
// non-Bearer scheme (e.g. X-User flows), returns false — only an *invalid*
|
|
111
|
+
// Bearer is a rejection. Mirrors the LocalAuth.authenticate decoding
|
|
112
|
+
// rule so HTTP-level rejection matches resolver-level identity.
|
|
113
|
+
let _isInvalidBearer = (req: nodeRequest): bool =>
|
|
114
|
+
switch req.headers->Dict.get("authorization") {
|
|
115
|
+
| Some(h) if String.startsWith(h, "Bearer ") =>
|
|
116
|
+
let token =
|
|
117
|
+
String.slice(h, ~start=7, ~end=String.length(h))->String.trim
|
|
118
|
+
LocalAuth.Login.verifyAndDecode(token)->Option.isNone
|
|
119
|
+
| _ => false
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Shared dispatch for start() and rebuildSchema(). Order matters: built-in
|
|
123
|
+
// endpoints win over the yoga catch-all so they aren't shadowed by graphql.
|
|
124
|
+
let _dispatch = (req: nodeRequest, res: nodeResponse, yoga: YG.yoga, getSdl: unit => string): unit => {
|
|
125
|
+
// Strip query string before path matching.
|
|
126
|
+
let path = req.url->String.split("?")->Array.get(0)->Option.getOr(req.url)
|
|
127
|
+
// /__inmemory/login is the path that *issues* tokens — it never carries
|
|
128
|
+
// one. Every other path must reject an unverifiable Bearer with 401 so
|
|
129
|
+
// the host-shell's `on401` logout path can fire on stale tokens.
|
|
130
|
+
if path != "/__inmemory/login" && _isInvalidBearer(req) {
|
|
131
|
+
_writeJson(
|
|
132
|
+
res,
|
|
133
|
+
~status=401,
|
|
134
|
+
JSON.Encode.object(
|
|
135
|
+
Dict.fromArray([("error", JSON.Encode.string("Invalid bearer token"))]),
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
} else if path == "/sdl" {
|
|
139
|
+
res->writeHead(200, {"Content-Type": "text/plain", "Access-Control-Allow-Origin": "*"})
|
|
140
|
+
res->end_(getSdl())
|
|
141
|
+
} else if path == "/__inmemory/login" && req.method == "POST" {
|
|
142
|
+
handleLogin(req, res)
|
|
143
|
+
} else if path == "/__inmemory/logout" && req.method == "POST" {
|
|
144
|
+
handleLogout(req, res)
|
|
145
|
+
} else {
|
|
146
|
+
(yoga->Obj.magic)(req, res)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// -- Auth context factory ----------------------------------------------------
|
|
151
|
+
//
|
|
152
|
+
// Yoga's initial context wraps the Fetch API Request. The shared
|
|
153
|
+
// `Auth_GraphqlContext.buildAuthContext` flattens headers into a lowercase
|
|
154
|
+
// dict, runs them through `LocalAuth.authenticate`, and attaches the
|
|
155
|
+
// resolved identity to the resolver context. Lifted into its own module so
|
|
156
|
+
// the split-mode admin server (`GraphQL_ServerInstance`) enforces the same
|
|
157
|
+
// bearer-token rules instead of leaving admin queries wide open.
|
|
158
|
+
|
|
159
|
+
let buildAuthContext = Auth_GraphqlContext.buildAuthContext
|
|
160
|
+
|
|
161
|
+
// -- Resolver type alias ---------------------------------------------------
|
|
162
|
+
|
|
163
|
+
type resolverFn = YG.resolverFn
|
|
164
|
+
|
|
165
|
+
// -- Relay Global ID encoding/decoding -------------------------------------
|
|
166
|
+
|
|
167
|
+
@val external btoa: string => string = "btoa"
|
|
168
|
+
@val external atob: string => string = "atob"
|
|
169
|
+
|
|
170
|
+
let encodeGlobalId = (~typeName: string, ~localId: string): string =>
|
|
171
|
+
btoa(`${typeName}:${localId}`)
|
|
172
|
+
|
|
173
|
+
let decodeGlobalId = (globalId: string): option<(string, string)> =>
|
|
174
|
+
try {
|
|
175
|
+
let decoded = atob(globalId)
|
|
176
|
+
let idx = decoded->String.indexOf(":")
|
|
177
|
+
if idx > 0 {
|
|
178
|
+
let typeName = decoded->String.slice(~start=0, ~end=idx)
|
|
179
|
+
let localId = decoded->String.slice(~start=idx + 1, ~end=decoded->String.length)
|
|
180
|
+
Some((typeName, localId))
|
|
181
|
+
} else {
|
|
182
|
+
None
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
| _ => None
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// -- Registry --------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
let mutationResolvers: ref<dict<resolverFn>> = ref(Dict.make())
|
|
191
|
+
let queryResolvers: ref<dict<resolverFn>> = ref(Dict.make())
|
|
192
|
+
let subscriptionResolvers: ref<dict<resolverFn>> = ref(Dict.make())
|
|
193
|
+
|
|
194
|
+
let mutationFields: ref<array<string>> = ref([])
|
|
195
|
+
let queryFields: ref<array<string>> = ref([])
|
|
196
|
+
let subscriptionFields: ref<array<string>> = ref([])
|
|
197
|
+
let typeDefinitions: ref<array<string>> = ref([])
|
|
198
|
+
|
|
199
|
+
let registerMutations = (~sdlFields: array<string>, ~resolvers: dict<resolverFn>) => {
|
|
200
|
+
mutationFields.contents = mutationFields.contents->Array.concat(sdlFields)
|
|
201
|
+
resolvers->Dict.toArray->Array.forEach(((k, v)) => mutationResolvers.contents->Dict.set(k, v))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let registerQueries = (~sdlFields: array<string>, ~resolvers: dict<resolverFn>) => {
|
|
205
|
+
queryFields.contents = queryFields.contents->Array.concat(sdlFields)
|
|
206
|
+
resolvers->Dict.toArray->Array.forEach(((k, v)) => queryResolvers.contents->Dict.set(k, v))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let registerSubscriptions = (~sdlFields: array<string>, ~resolvers: dict<resolverFn>) => {
|
|
210
|
+
subscriptionFields.contents = subscriptionFields.contents->Array.concat(sdlFields)
|
|
211
|
+
resolvers->Dict.toArray->Array.forEach(((k, v)) =>
|
|
212
|
+
subscriptionResolvers.contents->Dict.set(k, v)
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Look up a registered mutation resolver by field name (used by MCP_Server). */
|
|
217
|
+
let getMutationResolver = (fieldName: string): option<resolverFn> =>
|
|
218
|
+
mutationResolvers.contents->Dict.get(fieldName)
|
|
219
|
+
|
|
220
|
+
/** Look up a registered query resolver by field name (used by MCP_Server). */
|
|
221
|
+
let getQueryResolver = (fieldName: string): option<resolverFn> =>
|
|
222
|
+
queryResolvers.contents->Dict.get(fieldName)
|
|
223
|
+
|
|
224
|
+
let registerTypes = (~sdlTypes: array<string>) =>
|
|
225
|
+
typeDefinitions.contents = typeDefinitions.contents->Array.concat(sdlTypes)
|
|
226
|
+
|
|
227
|
+
// -- Relay Node type registry -----------------------------------------------
|
|
228
|
+
// Maps GraphQL type names to QueryDb component names for node(id: ID!) resolution.
|
|
229
|
+
let nodeTypeRegistry: ref<dict<string>> = ref(Dict.make())
|
|
230
|
+
|
|
231
|
+
let registerNodeType = (~typeName: string, ~queryDbName: string) =>
|
|
232
|
+
nodeTypeRegistry.contents->Dict.set(typeName, queryDbName)
|
|
233
|
+
|
|
234
|
+
// -- Relay Node resolver callback ------------------------------------------
|
|
235
|
+
// Set by QueryDbResolvers_GraphQL to resolve node(id: ID!) queries.
|
|
236
|
+
// Takes (typeName, localId) and returns the entity JSON with __typename.
|
|
237
|
+
type nodeResolverCallback = (~typeName: string, ~localId: string) => promise<option<JSON.t>>
|
|
238
|
+
let nodeResolverCallback: ref<option<nodeResolverCallback>> = ref(None)
|
|
239
|
+
let registerNodeResolverCallback = (cb: nodeResolverCallback) =>
|
|
240
|
+
nodeResolverCallback.contents = Some(cb)
|
|
241
|
+
|
|
242
|
+
// Builds the internal node resolver using the registered callback and type registry.
|
|
243
|
+
let buildNodeResolver = (): option<(string, resolverFn)> =>
|
|
244
|
+
switch nodeResolverCallback.contents {
|
|
245
|
+
| None => None
|
|
246
|
+
| Some(resolve) =>
|
|
247
|
+
let resolver: resolverFn = async (_root, args, _ctx) => {
|
|
248
|
+
let id =
|
|
249
|
+
args
|
|
250
|
+
->JSON.Decode.object
|
|
251
|
+
->Option.flatMap(d => d->Dict.get("id"))
|
|
252
|
+
->Option.flatMap(JSON.Decode.string)
|
|
253
|
+
->Option.getOr("")
|
|
254
|
+
switch decodeGlobalId(id) {
|
|
255
|
+
| Some((typeName, localId)) =>
|
|
256
|
+
switch await resolve(~typeName, ~localId) {
|
|
257
|
+
| Some(entity) => entity
|
|
258
|
+
| None => JSON.Encode.null
|
|
259
|
+
}
|
|
260
|
+
| None => JSON.Encode.null
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
Some(("node", resolver))
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// -- Server lifecycle ------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
@val external processEnv: dict<string> = "process.env"
|
|
269
|
+
let debug = processEnv->Dict.get("GRAPHQL_DEBUG")->Option.isSome
|
|
270
|
+
|
|
271
|
+
let activeServer: ref<option<YG.httpServer>> = ref(None)
|
|
272
|
+
let activeSchema: ref<option<YG.schema>> = ref(None)
|
|
273
|
+
let lastFullSdl: ref<option<string>> = ref(None)
|
|
274
|
+
|
|
275
|
+
let buildSdl = () => {
|
|
276
|
+
let typesSdl = typeDefinitions.contents->Array.length > 0
|
|
277
|
+
? typeDefinitions.contents->Array.join("\n\n")
|
|
278
|
+
: ""
|
|
279
|
+
let mutations =
|
|
280
|
+
mutationFields.contents->Array.length > 0
|
|
281
|
+
? mutationFields.contents->Array.join("\n")
|
|
282
|
+
: " _noop: String"
|
|
283
|
+
let queries =
|
|
284
|
+
queryFields.contents->Array.length > 0
|
|
285
|
+
? queryFields.contents->Array.join("\n")
|
|
286
|
+
: " _noop: String"
|
|
287
|
+
let queriesMutationsSdl = `type Query {
|
|
288
|
+
${queries}
|
|
289
|
+
}
|
|
290
|
+
type Mutation {
|
|
291
|
+
${mutations}
|
|
292
|
+
}`
|
|
293
|
+
let subscriptionsSdl =
|
|
294
|
+
subscriptionFields.contents->Array.length > 0
|
|
295
|
+
? `\n\ntype Subscription {\n${subscriptionFields.contents->Array.join("\n")}\n}`
|
|
296
|
+
: ""
|
|
297
|
+
let base =
|
|
298
|
+
typesSdl->String.length > 0
|
|
299
|
+
? typesSdl ++ "\n\n" ++ queriesMutationsSdl
|
|
300
|
+
: queriesMutationsSdl
|
|
301
|
+
base ++ subscriptionsSdl
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// `~contextFactory` is accepted to satisfy the `GraphQL_ServerInstance.t`
|
|
305
|
+
// interface but ignored — the data server always wires `buildAuthContext`
|
|
306
|
+
// internally so the bearer-token rules + /__inmemory/login + /sdl dispatch
|
|
307
|
+
// stay consistent across callers.
|
|
308
|
+
let start = (~port: int=4000, ~contextFactory as _: option<YG.contextFactory>=?, ()) => {
|
|
309
|
+
// Register node resolver if callback is set
|
|
310
|
+
switch buildNodeResolver() {
|
|
311
|
+
| Some((name, resolver)) => queryResolvers.contents->Dict.set(name, resolver)
|
|
312
|
+
| None => ()
|
|
313
|
+
}
|
|
314
|
+
let resolvers = Dict.make()
|
|
315
|
+
resolvers->Dict.set("Query", queryResolvers.contents)
|
|
316
|
+
resolvers->Dict.set("Mutation", mutationResolvers.contents)
|
|
317
|
+
if subscriptionResolvers.contents->Dict.keysToArray->Array.length > 0 {
|
|
318
|
+
resolvers->Dict.set("Subscription", subscriptionResolvers.contents)
|
|
319
|
+
}
|
|
320
|
+
let sdl = buildSdl()
|
|
321
|
+
lastFullSdl.contents = Some(sdl)
|
|
322
|
+
let schema = YG.createSchema({"typeDefs": sdl, "resolvers": resolvers})
|
|
323
|
+
activeSchema.contents = Some(schema)
|
|
324
|
+
let yoga = YG.createYogaWithContext({
|
|
325
|
+
"schema": schema,
|
|
326
|
+
"graphiql": true,
|
|
327
|
+
"logging": debug,
|
|
328
|
+
"maskedErrors": !debug,
|
|
329
|
+
"context": buildAuthContext,
|
|
330
|
+
})
|
|
331
|
+
// Custom HTTP handler: serves /sdl + /__inmemory/{login,logout}, else yoga.
|
|
332
|
+
let getSdl = () =>
|
|
333
|
+
switch activeSchema.contents {
|
|
334
|
+
| Some(s) => YG.printSchema(s)
|
|
335
|
+
| None => lastFullSdl.contents->Option.getOr("")
|
|
336
|
+
}
|
|
337
|
+
let server = createServerWithHandler((req, res) => _dispatch(req, res, yoga, getSdl))
|
|
338
|
+
server->YG.listen(port, () =>
|
|
339
|
+
log.info(~comp="GraphQL:Domain", `listening on http://localhost:${port->Int.toString}/graphql (SDL: /sdl)`)
|
|
340
|
+
)
|
|
341
|
+
activeServer.contents = Some(server)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let stop = () =>
|
|
345
|
+
switch activeServer.contents {
|
|
346
|
+
| Some(server) =>
|
|
347
|
+
server->YG.close(() => ())
|
|
348
|
+
activeServer.contents = None
|
|
349
|
+
activeSchema.contents = None
|
|
350
|
+
| None => ()
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Rebuild the server schema by stitching plugin fragments with registered resolvers.
|
|
354
|
+
// Merges any additional type definitions from fragments into the type registry,
|
|
355
|
+
// then uses buildSdl() which includes types + Query + Mutation.
|
|
356
|
+
// Stops the existing server and restarts with the new SDL.
|
|
357
|
+
let rebuildSchema = (
|
|
358
|
+
~baseFragment: Reventless.Plugin.apiSchemaFragment,
|
|
359
|
+
~pluginFragments: array<Reventless.Plugin.apiSchemaFragment>,
|
|
360
|
+
) => {
|
|
361
|
+
stop()
|
|
362
|
+
// Merge any fragment types not already registered via the hook
|
|
363
|
+
let allFragments = Array.concat([baseFragment], pluginFragments)
|
|
364
|
+
let fragmentTypes = allFragments->Array.flatMap(frag => ReventlessCore.GraphQL_Stitcher.decode(frag).types)
|
|
365
|
+
let existingNames = Set.fromArray(typeDefinitions.contents->Array.map(ReventlessCore.GraphQL_Stitcher.extractLeadingName))
|
|
366
|
+
fragmentTypes->Array.forEach(typeDef => {
|
|
367
|
+
let name = ReventlessCore.GraphQL_Stitcher.extractLeadingName(typeDef)
|
|
368
|
+
if !(existingNames->Set.has(name)) {
|
|
369
|
+
typeDefinitions.contents->Array.push(typeDef)
|
|
370
|
+
existingNames->Set.add(name)
|
|
371
|
+
}
|
|
372
|
+
})
|
|
373
|
+
// Register node resolver if callback is set
|
|
374
|
+
switch buildNodeResolver() {
|
|
375
|
+
| Some((name, resolver)) => queryResolvers.contents->Dict.set(name, resolver)
|
|
376
|
+
| None => ()
|
|
377
|
+
}
|
|
378
|
+
let fullSdl = buildSdl()
|
|
379
|
+
lastFullSdl.contents = Some(fullSdl)
|
|
380
|
+
let resolvers = Dict.make()
|
|
381
|
+
resolvers->Dict.set("Query", queryResolvers.contents)
|
|
382
|
+
resolvers->Dict.set("Mutation", mutationResolvers.contents)
|
|
383
|
+
if subscriptionResolvers.contents->Dict.keysToArray->Array.length > 0 {
|
|
384
|
+
resolvers->Dict.set("Subscription", subscriptionResolvers.contents)
|
|
385
|
+
}
|
|
386
|
+
let schema = YG.createSchema({"typeDefs": fullSdl, "resolvers": resolvers})
|
|
387
|
+
activeSchema.contents = Some(schema)
|
|
388
|
+
let yoga = YG.createYogaWithContext({
|
|
389
|
+
"schema": schema,
|
|
390
|
+
"graphiql": true,
|
|
391
|
+
"logging": debug,
|
|
392
|
+
"maskedErrors": !debug,
|
|
393
|
+
"context": buildAuthContext,
|
|
394
|
+
})
|
|
395
|
+
let getSdl = () =>
|
|
396
|
+
switch activeSchema.contents {
|
|
397
|
+
| Some(s) => YG.printSchema(s)
|
|
398
|
+
| None => lastFullSdl.contents->Option.getOr("")
|
|
399
|
+
}
|
|
400
|
+
let server = createServerWithHandler((req, res) => _dispatch(req, res, yoga, getSdl))
|
|
401
|
+
server->YG.listen(4000, () =>
|
|
402
|
+
log.info(~comp="GraphQL:Domain", "rebuilt schema - http://localhost:4000/graphql (SDL: /sdl)")
|
|
403
|
+
)
|
|
404
|
+
activeServer.contents = Some(server)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Reset registry state (call between isolated test suites).
|
|
408
|
+
let reset = () => {
|
|
409
|
+
mutationResolvers.contents = Dict.make()
|
|
410
|
+
queryResolvers.contents = Dict.make()
|
|
411
|
+
subscriptionResolvers.contents = Dict.make()
|
|
412
|
+
mutationFields.contents = []
|
|
413
|
+
queryFields.contents = []
|
|
414
|
+
subscriptionFields.contents = []
|
|
415
|
+
typeDefinitions.contents = []
|
|
416
|
+
nodeTypeRegistry.contents = Dict.make()
|
|
417
|
+
nodeResolverCallback.contents = None
|
|
418
|
+
activeSchema.contents = None
|
|
419
|
+
lastFullSdl.contents = None
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// -- Inspection & diagnostics ---------------------------------------------
|
|
423
|
+
|
|
424
|
+
let getRegisteredSdl = () => buildSdl()
|
|
425
|
+
|
|
426
|
+
type resolverNames = {mutations: array<string>, queries: array<string>}
|
|
427
|
+
|
|
428
|
+
let getRegisteredResolverNames = (): resolverNames => {
|
|
429
|
+
mutations: mutationResolvers.contents->Dict.keysToArray,
|
|
430
|
+
queries: queryResolvers.contents->Dict.keysToArray,
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
let getFullSdl = () => lastFullSdl.contents
|
|
434
|
+
|
|
435
|
+
let getSchema = () => activeSchema.contents
|
|
436
|
+
|
|
437
|
+
let getLiveSdl = () => activeSchema.contents->Option.map(YG.printSchema)
|
|
438
|
+
|
|
439
|
+
let printLiveSdl = () =>
|
|
440
|
+
switch getLiveSdl() {
|
|
441
|
+
| Some(sdl) =>
|
|
442
|
+
log.info(~comp="GraphQL:Domain", "Live SDL:")
|
|
443
|
+
Console.log(sdl)
|
|
444
|
+
| None => log.info(~comp="GraphQL:Domain", "no active schema")
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
type diagnostics = GraphQL_ServerInstance.diagnostics
|
|
448
|
+
|
|
449
|
+
let extractFieldName = (sdlField: string): string => {
|
|
450
|
+
let trimmed = sdlField->String.trim
|
|
451
|
+
trimmed
|
|
452
|
+
->String.split("(")
|
|
453
|
+
->Array.get(0)
|
|
454
|
+
->Option.getOr("")
|
|
455
|
+
->String.trim
|
|
456
|
+
->String.split(":")
|
|
457
|
+
->Array.get(0)
|
|
458
|
+
->Option.getOr("")
|
|
459
|
+
->String.trim
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let diagnostics = (): diagnostics => {
|
|
463
|
+
let mutFieldNames =
|
|
464
|
+
mutationFields.contents->Array.map(extractFieldName)->Array.filter(n => n != "_noop")
|
|
465
|
+
let queryFieldNames =
|
|
466
|
+
queryFields.contents->Array.map(extractFieldName)->Array.filter(n => n != "_noop")
|
|
467
|
+
let mutResolverNames = mutationResolvers.contents->Dict.keysToArray
|
|
468
|
+
let queryResolverNames = queryResolvers.contents->Dict.keysToArray
|
|
469
|
+
|
|
470
|
+
let mismatches: array<string> = []
|
|
471
|
+
|
|
472
|
+
// SDL fields without resolvers
|
|
473
|
+
let mutResolverSet = Set.fromArray(mutResolverNames)
|
|
474
|
+
mutFieldNames->Array.forEach(name =>
|
|
475
|
+
if !(mutResolverSet->Set.has(name)) {
|
|
476
|
+
mismatches->Array.push(`Mutation "${name}": SDL field but no resolver`)
|
|
477
|
+
}
|
|
478
|
+
)
|
|
479
|
+
let queryResolverSet = Set.fromArray(queryResolverNames)
|
|
480
|
+
queryFieldNames->Array.forEach(name =>
|
|
481
|
+
if !(queryResolverSet->Set.has(name)) {
|
|
482
|
+
mismatches->Array.push(`Query "${name}": SDL field but no resolver`)
|
|
483
|
+
}
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
// Resolvers without SDL fields
|
|
487
|
+
let mutFieldSet = Set.fromArray(mutFieldNames)
|
|
488
|
+
mutResolverNames->Array.forEach(name =>
|
|
489
|
+
if !(mutFieldSet->Set.has(name)) {
|
|
490
|
+
mismatches->Array.push(`Mutation "${name}": resolver but no SDL field`)
|
|
491
|
+
}
|
|
492
|
+
)
|
|
493
|
+
let queryFieldSet = Set.fromArray(queryFieldNames)
|
|
494
|
+
queryResolverNames->Array.forEach(name =>
|
|
495
|
+
if !(queryFieldSet->Set.has(name)) {
|
|
496
|
+
mismatches->Array.push(`Query "${name}": resolver but no SDL field`)
|
|
497
|
+
}
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
let typeNames = typeDefinitions.contents->Array.map(ReventlessCore.GraphQL_Stitcher.extractLeadingName)
|
|
501
|
+
|
|
502
|
+
{
|
|
503
|
+
registeredTypeDefinitions: typeNames,
|
|
504
|
+
registeredMutationFields: mutFieldNames,
|
|
505
|
+
registeredQueryFields: queryFieldNames,
|
|
506
|
+
registeredMutationResolvers: mutResolverNames,
|
|
507
|
+
registeredQueryResolvers: queryResolverNames,
|
|
508
|
+
typeCount: typeNames->Array.length,
|
|
509
|
+
sdlMutationCount: mutFieldNames->Array.length,
|
|
510
|
+
sdlQueryCount: queryFieldNames->Array.length,
|
|
511
|
+
resolverMutationCount: mutResolverNames->Array.length,
|
|
512
|
+
resolverQueryCount: queryResolverNames->Array.length,
|
|
513
|
+
mismatches,
|
|
514
|
+
fullSdl: lastFullSdl.contents,
|
|
515
|
+
serverRunning: activeServer.contents->Option.isSome,
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
let printDiagnostics = () => {
|
|
520
|
+
let p = s => log.info(~comp="GraphQL:Domain", s)
|
|
521
|
+
let d = diagnostics()
|
|
522
|
+
p("Diagnostics")
|
|
523
|
+
p(` Types: ${d.typeCount->Int.toString}`)
|
|
524
|
+
p(
|
|
525
|
+
` Mutations: ${d.sdlMutationCount->Int.toString} SDL fields, ${d.resolverMutationCount->Int.toString} resolvers`,
|
|
526
|
+
)
|
|
527
|
+
p(
|
|
528
|
+
` Queries: ${d.sdlQueryCount->Int.toString} SDL fields, ${d.resolverQueryCount->Int.toString} resolvers`,
|
|
529
|
+
)
|
|
530
|
+
if d.mismatches->Array.length > 0 {
|
|
531
|
+
p(` Mismatches (${d.mismatches->Array.length->Int.toString}):`)
|
|
532
|
+
d.mismatches->Array.forEach(m => p(` ⚠ ${m}`))
|
|
533
|
+
} else {
|
|
534
|
+
p(" No mismatches")
|
|
535
|
+
}
|
|
536
|
+
p(` Server running: ${d.serverRunning ? "yes" : "no"}`)
|
|
537
|
+
switch d.fullSdl {
|
|
538
|
+
| Some(sdl) =>
|
|
539
|
+
p("")
|
|
540
|
+
p("--- Full SDL ---")
|
|
541
|
+
sdl->String.split("\n")->Array.forEach(line => p(line))
|
|
542
|
+
p("--- End ---")
|
|
543
|
+
| None => p(" No SDL recorded")
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// -- GraphQL_ServerInstance.t interface ----------------------------------
|
|
548
|
+
// Exposes this singleton as a GraphQL_ServerInstance.t for use by
|
|
549
|
+
// resolveTargetGraphQL() in Platform.res (mirrors AWS resolveTargetApi() pattern).
|
|
550
|
+
let asInterface: GraphQL_ServerInstance.t = {
|
|
551
|
+
registerMutations,
|
|
552
|
+
registerQueries,
|
|
553
|
+
registerSubscriptions,
|
|
554
|
+
registerTypes,
|
|
555
|
+
getMutationResolver,
|
|
556
|
+
getQueryResolver,
|
|
557
|
+
start,
|
|
558
|
+
stop,
|
|
559
|
+
reset,
|
|
560
|
+
buildSdl,
|
|
561
|
+
getFullSdl,
|
|
562
|
+
getSchema,
|
|
563
|
+
diagnostics,
|
|
564
|
+
printDiagnostics,
|
|
565
|
+
}
|