@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,793 @@
|
|
|
1
|
+
// GraphQL query resolvers for in-memory QueryDb.
|
|
2
|
+
// Make(Bus) functor: registers query fields into the provided server during component construction.
|
|
3
|
+
// Supports: getById, list (when listFieldName provided), {name}ById (when subId), and {name}By{Index} per index.
|
|
4
|
+
//
|
|
5
|
+
// Query names are resolved from Plugin_Helpers.queryFieldNamesRegistry (populated by
|
|
6
|
+
// Plugin_Builder) to align with fragment SDL. Falls back to camelCase(name) for
|
|
7
|
+
// backward compatibility when no registry entry exists.
|
|
8
|
+
//
|
|
9
|
+
// Relay support is domain-only (platform QueryDbs pass relay=None):
|
|
10
|
+
// - encodeGlobalId / decodeGlobalId for Relay global IDs
|
|
11
|
+
// - node(id: ID!) resolution via registerNodeResolverCallback
|
|
12
|
+
// - registerNodeType for the Relay Node type registry
|
|
13
|
+
// Platform admin views are not Relay-paginated so relay=None is correct for them.
|
|
14
|
+
|
|
15
|
+
// Relay support type — lifted to module level so Platform.res can construct a value
|
|
16
|
+
// without a functor application in type position (not supported in ReScript).
|
|
17
|
+
// Domain plugins pass Some(domainRelaySupport); platform plugins pass None.
|
|
18
|
+
type relaySupport = {
|
|
19
|
+
encodeGlobalId: (~typeName: string, ~localId: string) => string,
|
|
20
|
+
registerNodeType: (~typeName: string, ~queryDbName: string) => unit,
|
|
21
|
+
registerNodeResolverCallback: DomainGraphQL_Server.nodeResolverCallback => unit,
|
|
22
|
+
nodeTypeRegistry: ref<dict<string>>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@val external btoa: string => string = "btoa"
|
|
26
|
+
@val external atob: string => string = "atob"
|
|
27
|
+
|
|
28
|
+
// Shared keyset-cursor helpers. The cursor is base64 of the row's value for the
|
|
29
|
+
// active sort field (orderBy.field, or "id" when no orderBy is supplied). Used
|
|
30
|
+
// by both the connection list resolver and the items resolver so cursor encoding
|
|
31
|
+
// stays in lockstep.
|
|
32
|
+
let encodeCursor = (value: string): string => btoa(value)
|
|
33
|
+
let decodeCursor = (cursor: string): string => atob(cursor)
|
|
34
|
+
|
|
35
|
+
// Default page size for the connection list resolver when neither `first` nor
|
|
36
|
+
// `last` is supplied. Matches the UI's `defaultPageSize` constant; a bound is
|
|
37
|
+
// necessary for the keyset model to report `pageInfo` correctly.
|
|
38
|
+
let defaultListPageSize = 50
|
|
39
|
+
|
|
40
|
+
module Make = (Bus: LocalBus.T) => {
|
|
41
|
+
open ReventlessCore
|
|
42
|
+
|
|
43
|
+
type api = unit
|
|
44
|
+
type role = unit
|
|
45
|
+
|
|
46
|
+
// -- Identity extraction from GraphQL context ---------------------------------
|
|
47
|
+
// graphql-yoga's `context` factory in DomainGraphQL_Server.buildAuthContext
|
|
48
|
+
// runs LocalAuth.authenticate per request and attaches the resolved
|
|
49
|
+
// `Identity.t` to `ctx.identity`. Fallback to anonymous only when ctx is
|
|
50
|
+
// malformed.
|
|
51
|
+
|
|
52
|
+
let extractIdentity = (ctx: JSON.t): Reventless.Identity.t => {
|
|
53
|
+
try {
|
|
54
|
+
switch (ctx->Obj.magic)["identity"]->Nullable.toOption {
|
|
55
|
+
| Some(id) => (id: Reventless.Identity.t)
|
|
56
|
+
| None => Reventless.Identity.anonymous
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
| _ => Reventless.Identity.anonymous
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// -- Module-level server and relay refs ------------------------------------
|
|
64
|
+
// Set by Platform.res before component construction runs so the make function
|
|
65
|
+
// can pick up the correct target server and relay support (mirrors AWS ~api pattern).
|
|
66
|
+
let serverRef: ref<GraphQL_ServerInstance.t> = ref(DomainGraphQL_Server.asInterface)
|
|
67
|
+
let relayRef: ref<option<relaySupport>> = ref(Some({
|
|
68
|
+
encodeGlobalId: DomainGraphQL_Server.encodeGlobalId,
|
|
69
|
+
registerNodeType: DomainGraphQL_Server.registerNodeType,
|
|
70
|
+
registerNodeResolverCallback: DomainGraphQL_Server.registerNodeResolverCallback,
|
|
71
|
+
nodeTypeRegistry: DomainGraphQL_Server.nodeTypeRegistry,
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
let make: QueryDb_Adapter.resolversMaker<unit, unit> = (
|
|
75
|
+
~name,
|
|
76
|
+
~api as _,
|
|
77
|
+
~apiRole as _,
|
|
78
|
+
~dataSourceName as _,
|
|
79
|
+
~indexes,
|
|
80
|
+
~subIdField,
|
|
81
|
+
~idResolverConfigs as _,
|
|
82
|
+
~idsResolverConfigs as _,
|
|
83
|
+
~authorization,
|
|
84
|
+
~opts as _,
|
|
85
|
+
) => {
|
|
86
|
+
// Read the active server and relay support from module-level refs set by Platform.res.
|
|
87
|
+
// This mirrors how AWS adapters receive ~api from the hook; here the hook sets the refs
|
|
88
|
+
// before calling make() so the values are available at registration time.
|
|
89
|
+
let server = serverRef.contents
|
|
90
|
+
let relay = relayRef.contents
|
|
91
|
+
|
|
92
|
+
// Register the Relay node resolver callback once per QueryDb (domain only).
|
|
93
|
+
// Scans all QueryDb instances to resolve node(id: ID!) queries.
|
|
94
|
+
switch relay {
|
|
95
|
+
| Some(r) =>
|
|
96
|
+
r.registerNodeResolverCallback(async (~typeName, ~localId) => {
|
|
97
|
+
let queryDbName = switch r.nodeTypeRegistry.contents->Dict.get(typeName) {
|
|
98
|
+
| Some(n) => n
|
|
99
|
+
| None => typeName
|
|
100
|
+
}
|
|
101
|
+
switch Bus.getQueryDb(queryDbName) {
|
|
102
|
+
| Some(ops) =>
|
|
103
|
+
let items =
|
|
104
|
+
await ops.loadStream(localId)
|
|
105
|
+
->Stream.runCollect
|
|
106
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
107
|
+
->Effect.runPromise
|
|
108
|
+
switch items->Array.get(0) {
|
|
109
|
+
| Some(item) =>
|
|
110
|
+
let obj = item->JSON.Decode.object->Option.getOr(Dict.make())
|
|
111
|
+
obj->Dict.set("__typename", JSON.Encode.string(typeName))
|
|
112
|
+
obj->Dict.set("id", r.encodeGlobalId(~typeName, ~localId)->JSON.Encode.string)
|
|
113
|
+
Some(JSON.Encode.object(obj))
|
|
114
|
+
| None => None
|
|
115
|
+
}
|
|
116
|
+
| None => None
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
| None => ()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let runInterceptor = async (~ctx, ~args): QueryDb_Callback.interceptResult => {
|
|
123
|
+
let identity = extractIdentity(ctx)
|
|
124
|
+
// Spec-level authorization runs first; failures short-circuit before
|
|
125
|
+
// the user-supplied interceptor (mirrors mutation enforcement).
|
|
126
|
+
if !Reventless.Authorization.isAllowed(authorization, identity) {
|
|
127
|
+
Deny("Forbidden")
|
|
128
|
+
} else {
|
|
129
|
+
switch QueryDb_Callback.queryInterceptorHook.contents {
|
|
130
|
+
| None => Allow
|
|
131
|
+
| Some(interceptor) => await interceptor(~identity, ~readModelName=name, ~args)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let cap = s => s->String.charAt(0)->String.toUpperCase ++ s->String.slice(~start=1)
|
|
137
|
+
|
|
138
|
+
// Resolve query field names: check registry first, fall back to safe defaults.
|
|
139
|
+
// Fallbacks use simple GraphQL built-in types to avoid referencing non-existent custom types.
|
|
140
|
+
let registryEntry = Plugin_Helpers.queryFieldNamesRegistry->Dict.get(name)
|
|
141
|
+
let singleQueryName = switch registryEntry {
|
|
142
|
+
| Some({singleFieldName}) => singleFieldName
|
|
143
|
+
| None => name->String.charAt(0)->String.toLowerCase ++ name->String.slice(~start=1)
|
|
144
|
+
}
|
|
145
|
+
let listQueryName = switch registryEntry {
|
|
146
|
+
| Some({listFieldName}) => listFieldName
|
|
147
|
+
| None => name ++ "s"
|
|
148
|
+
}
|
|
149
|
+
let returnTypeName = switch registryEntry {
|
|
150
|
+
| Some({returnTypeName: rt}) => rt
|
|
151
|
+
| None => "String"
|
|
152
|
+
}
|
|
153
|
+
let pluralTypeName = switch registryEntry {
|
|
154
|
+
| Some({pluralTypeName}) => pluralTypeName
|
|
155
|
+
| None => "[String]"
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Resolve includeIdParam flag from registry (defaults to true for ReadModels)
|
|
159
|
+
let includeIdParam = switch registryEntry {
|
|
160
|
+
| Some({includeIdParam}) => includeIdParam
|
|
161
|
+
| None => true
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Resolve connectionSpec flag from registry (defaults to true)
|
|
165
|
+
let connectionSpec = switch registryEntry {
|
|
166
|
+
| Some({connectionSpec}) => connectionSpec
|
|
167
|
+
| None => true
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Register this entity type in the Relay Node type registry (domain only).
|
|
171
|
+
if includeIdParam {
|
|
172
|
+
switch relay {
|
|
173
|
+
| Some(r) => r.registerNodeType(~typeName=returnTypeName, ~queryDbName=name)
|
|
174
|
+
| None => ()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let encodeId = switch relay {
|
|
179
|
+
| Some(r) => (~typeName, ~localId) => r.encodeGlobalId(~typeName, ~localId)
|
|
180
|
+
| None => (~typeName as _, ~localId) => localId
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// -- Main query: getById ---------------------------------------------------
|
|
184
|
+
let byIdSdl = if includeIdParam {
|
|
185
|
+
switch subIdField {
|
|
186
|
+
| Some(sf) => ` ${singleQueryName}(id: ID!, ${sf}: String): ${returnTypeName}`
|
|
187
|
+
| None => ` ${singleQueryName}(id: ID!): ${returnTypeName}`
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
` ${singleQueryName}: ${returnTypeName}`
|
|
191
|
+
}
|
|
192
|
+
let byIdResolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
193
|
+
switch await runInterceptor(~ctx, ~args) {
|
|
194
|
+
| Deny(_) => JSON.Encode.null
|
|
195
|
+
| Allow =>
|
|
196
|
+
let id =
|
|
197
|
+
args->JSON.Decode.object->Option.flatMap(d => d->Dict.get("id"))->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
198
|
+
switch Bus.getQueryDb(name) {
|
|
199
|
+
| Some(ops) =>
|
|
200
|
+
let items =
|
|
201
|
+
await ops.loadStream(id)
|
|
202
|
+
->Stream.runCollect
|
|
203
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
204
|
+
->Effect.runPromise
|
|
205
|
+
switch items->Array.get(0) {
|
|
206
|
+
| Some(item) =>
|
|
207
|
+
if includeIdParam {
|
|
208
|
+
let obj = item->JSON.Decode.object->Option.getOr(Dict.make())
|
|
209
|
+
obj->Dict.set("id", encodeId(~typeName=returnTypeName, ~localId=id)->JSON.Encode.string)
|
|
210
|
+
JSON.Encode.object(obj)
|
|
211
|
+
} else {
|
|
212
|
+
item
|
|
213
|
+
}
|
|
214
|
+
| None => JSON.Encode.null
|
|
215
|
+
}
|
|
216
|
+
| None => JSON.Encode.null
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// -- Batched-by-ids query: {listFieldName}ByIds ---------------------------
|
|
222
|
+
// Mirrors the AppSync BatchGetItem resolver. Single-key projections only
|
|
223
|
+
// (subIdField=None) — matches the SDL emitted by FragmentGenerator. Missing
|
|
224
|
+
// ids drop out of the response (no cardinality preservation), matching
|
|
225
|
+
// BatchGetItem semantics.
|
|
226
|
+
let byIdsSdl = if includeIdParam && subIdField === None {
|
|
227
|
+
[GraphQL_FragmentGenerator.deriveByIdsQueryField(~listFieldName=listQueryName, ~returnTypeName)]
|
|
228
|
+
} else {
|
|
229
|
+
[]
|
|
230
|
+
}
|
|
231
|
+
let byIdsResolverEntry: option<(string, GraphQL_ServerInstance.resolverFn)> =
|
|
232
|
+
if includeIdParam && subIdField === None {
|
|
233
|
+
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
234
|
+
switch await runInterceptor(~ctx, ~args) {
|
|
235
|
+
| Deny(_) => []->JSON.Encode.array
|
|
236
|
+
| Allow =>
|
|
237
|
+
let ids =
|
|
238
|
+
args
|
|
239
|
+
->JSON.Decode.object
|
|
240
|
+
->Option.flatMap(d => d->Dict.get("ids"))
|
|
241
|
+
->Option.flatMap(JSON.Decode.array)
|
|
242
|
+
->Option.getOr([])
|
|
243
|
+
->Array.filterMap(JSON.Decode.string)
|
|
244
|
+
switch Bus.getQueryDb(name) {
|
|
245
|
+
| Some(ops) =>
|
|
246
|
+
let loaded = await ids->Array.map(id =>
|
|
247
|
+
ops.loadStream(id)
|
|
248
|
+
->Stream.runCollect
|
|
249
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
250
|
+
->Effect.runPromise
|
|
251
|
+
->Promise.thenResolve(items => (id, items->Array.get(0)))
|
|
252
|
+
)->Promise.all
|
|
253
|
+
loaded
|
|
254
|
+
->Array.filterMap(((id, opt)) =>
|
|
255
|
+
opt->Option.map(item => {
|
|
256
|
+
let obj = item->JSON.Decode.object->Option.getOr(Dict.make())
|
|
257
|
+
obj->Dict.set(
|
|
258
|
+
"id",
|
|
259
|
+
encodeId(~typeName=returnTypeName, ~localId=id)->JSON.Encode.string,
|
|
260
|
+
)
|
|
261
|
+
JSON.Encode.object(obj)
|
|
262
|
+
})
|
|
263
|
+
)
|
|
264
|
+
->JSON.Encode.array
|
|
265
|
+
| None => []->JSON.Encode.array
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
Some((listQueryName ++ "ByIds", resolver))
|
|
270
|
+
} else {
|
|
271
|
+
None
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Look up labelField from registry (Phase 3). Used by list-query filter.search /
|
|
275
|
+
// searchPrefix to target the entity's human-readable column without per-entity
|
|
276
|
+
// resolver wiring. Falls back to "id" so filtering still works on entities
|
|
277
|
+
// without a @displayName annotation (match on the partition key).
|
|
278
|
+
let labelField = switch registryEntry {
|
|
279
|
+
| Some({labelField: ?lf}) => lf->Option.getOr("id")
|
|
280
|
+
| None => "id"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// -- List query -------------------------------------------------------------
|
|
284
|
+
// Look up the registered state schema (populated alongside queryFieldNamesRegistry)
|
|
285
|
+
// so the resolver derives the same serverCapability the FragmentGenerator emitted.
|
|
286
|
+
let stateSchemaOpt = Plugin_Helpers.stateSchemaRegistry->Dict.get(name)
|
|
287
|
+
let capability = switch stateSchemaOpt {
|
|
288
|
+
| Some(s) => GraphQL_FragmentGenerator.deriveServerCapability(s)
|
|
289
|
+
| None => GraphQL_FragmentGenerator.emptyCapability
|
|
290
|
+
}
|
|
291
|
+
let (listSdl, listResolver): (array<string>, GraphQL_ServerInstance.resolverFn) = if connectionSpec {
|
|
292
|
+
// Relay Connection spec format
|
|
293
|
+
let filterTypeName = returnTypeName ++ "Filter"
|
|
294
|
+
let orderByTypes = GraphQL_FragmentGenerator.deriveConnectionOrderByType(
|
|
295
|
+
~singularTypeName=returnTypeName,
|
|
296
|
+
~capability,
|
|
297
|
+
)
|
|
298
|
+
let hasOrderBy = orderByTypes->Array.length > 0
|
|
299
|
+
let typesToRegister = [
|
|
300
|
+
GraphQL_FragmentGenerator.deriveConnectionFilterType(~filterTypeName, ~capability),
|
|
301
|
+
]->Array.concat(orderByTypes)
|
|
302
|
+
server.registerTypes(~sdlTypes=typesToRegister)
|
|
303
|
+
let sdl = [
|
|
304
|
+
GraphQL_FragmentGenerator.deriveConnectionQueryField(
|
|
305
|
+
~listFieldName=listQueryName,
|
|
306
|
+
~singularTypeName=returnTypeName,
|
|
307
|
+
~filterTypeName,
|
|
308
|
+
~hasOrderBy,
|
|
309
|
+
),
|
|
310
|
+
]
|
|
311
|
+
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
312
|
+
switch await runInterceptor(~ctx, ~args) {
|
|
313
|
+
| Deny(_) =>
|
|
314
|
+
Obj.magic({
|
|
315
|
+
"edges": [],
|
|
316
|
+
"pageInfo": {
|
|
317
|
+
"hasNextPage": false,
|
|
318
|
+
"hasPreviousPage": false,
|
|
319
|
+
"startCursor": Nullable.null,
|
|
320
|
+
"endCursor": Nullable.null,
|
|
321
|
+
},
|
|
322
|
+
})
|
|
323
|
+
| Allow =>
|
|
324
|
+
let items = switch Bus.getQueryDbStream(name) {
|
|
325
|
+
| Some(makeStream) =>
|
|
326
|
+
await makeStream()->Stream.runCollect->Effect.runPromise
|
|
327
|
+
| None =>
|
|
328
|
+
switch Bus.getQueryDbScan(name) {
|
|
329
|
+
| Some(scanAll) => scanAll()
|
|
330
|
+
| None => []
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Apply filter (search, searchPrefix, ids) before any pagination.
|
|
334
|
+
let argsDict = args->JSON.Decode.object->Option.getOr(Dict.make())
|
|
335
|
+
let filterDict =
|
|
336
|
+
argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
|
|
337
|
+
let search = filterDict->Dict.get("search")->Option.flatMap(JSON.Decode.string)
|
|
338
|
+
let searchPrefix = filterDict->Dict.get("searchPrefix")->Option.flatMap(JSON.Decode.string)
|
|
339
|
+
let ids =
|
|
340
|
+
filterDict
|
|
341
|
+
->Dict.get("ids")
|
|
342
|
+
->Option.flatMap(JSON.Decode.array)
|
|
343
|
+
->Option.map(arr => arr->Array.filterMap(JSON.Decode.string))
|
|
344
|
+
let getLabel = item =>
|
|
345
|
+
item
|
|
346
|
+
->JSON.Decode.object
|
|
347
|
+
->Option.flatMap(d => d->Dict.get(labelField))
|
|
348
|
+
->Option.flatMap(JSON.Decode.string)
|
|
349
|
+
->Option.getOr("")
|
|
350
|
+
let getId = item =>
|
|
351
|
+
item
|
|
352
|
+
->JSON.Decode.object
|
|
353
|
+
->Option.flatMap(d => d->Dict.get("id"))
|
|
354
|
+
->Option.flatMap(JSON.Decode.string)
|
|
355
|
+
->Option.getOr("")
|
|
356
|
+
// Per-field eq / from / to filters derived from capability — applied
|
|
357
|
+
// alongside the legacy search/searchPrefix/ids block.
|
|
358
|
+
let getFieldString = (item, field) =>
|
|
359
|
+
item
|
|
360
|
+
->JSON.Decode.object
|
|
361
|
+
->Option.flatMap(d => d->Dict.get(field))
|
|
362
|
+
->Option.flatMap(v =>
|
|
363
|
+
switch v->JSON.Decode.string {
|
|
364
|
+
| Some(s) => Some(s)
|
|
365
|
+
| None =>
|
|
366
|
+
v
|
|
367
|
+
->JSON.Decode.float
|
|
368
|
+
->Option.map(f => Float.toString(f))
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
let perFieldChecks: array<JSON.t => bool> = capability.filterFields->Array.flatMap(f => {
|
|
372
|
+
let checks: array<JSON.t => bool> = []
|
|
373
|
+
switch filterDict->Dict.get(f.name ++ "Eq") {
|
|
374
|
+
| Some(v) when v != JSON.Encode.null =>
|
|
375
|
+
let expected = switch v->JSON.Decode.string {
|
|
376
|
+
| Some(s) => s
|
|
377
|
+
| None =>
|
|
378
|
+
v->JSON.Decode.float->Option.map(f => Float.toString(f))->Option.getOr("")
|
|
379
|
+
}
|
|
380
|
+
checks->Array.push(item =>
|
|
381
|
+
getFieldString(item, f.name)->Option.mapOr(false, v => v == expected)
|
|
382
|
+
)
|
|
383
|
+
| _ => ()
|
|
384
|
+
}
|
|
385
|
+
if f.range {
|
|
386
|
+
switch filterDict->Dict.get(f.name ++ "From") {
|
|
387
|
+
| Some(v) when v != JSON.Encode.null =>
|
|
388
|
+
let from =
|
|
389
|
+
v
|
|
390
|
+
->JSON.Decode.string
|
|
391
|
+
->Option.getOr(
|
|
392
|
+
v->JSON.Decode.float->Option.map(f => Float.toString(f))->Option.getOr(""),
|
|
393
|
+
)
|
|
394
|
+
checks->Array.push(item =>
|
|
395
|
+
getFieldString(item, f.name)->Option.mapOr(false, v => v >= from)
|
|
396
|
+
)
|
|
397
|
+
| _ => ()
|
|
398
|
+
}
|
|
399
|
+
switch filterDict->Dict.get(f.name ++ "To") {
|
|
400
|
+
| Some(v) when v != JSON.Encode.null =>
|
|
401
|
+
let to_ =
|
|
402
|
+
v
|
|
403
|
+
->JSON.Decode.string
|
|
404
|
+
->Option.getOr(
|
|
405
|
+
v->JSON.Decode.float->Option.map(f => Float.toString(f))->Option.getOr(""),
|
|
406
|
+
)
|
|
407
|
+
checks->Array.push(item =>
|
|
408
|
+
getFieldString(item, f.name)->Option.mapOr(false, v => v <= to_)
|
|
409
|
+
)
|
|
410
|
+
| _ => ()
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
checks
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
let filtered = items->Array.filter(item => {
|
|
417
|
+
let passSearch = switch search {
|
|
418
|
+
| Some(s) if s->String.length > 0 =>
|
|
419
|
+
getLabel(item)->String.toLowerCase->String.includes(s->String.toLowerCase)
|
|
420
|
+
| _ => true
|
|
421
|
+
}
|
|
422
|
+
let passPrefix = switch searchPrefix {
|
|
423
|
+
| Some(p) if p->String.length > 0 =>
|
|
424
|
+
getLabel(item)->String.toLowerCase->String.startsWith(p->String.toLowerCase)
|
|
425
|
+
| _ => true
|
|
426
|
+
}
|
|
427
|
+
// Filter `ids` accepts either the Relay-encoded global ID (the
|
|
428
|
+
// form returned by node.id) or the entity's raw local ID — so
|
|
429
|
+
// callers that hold a foreign-key value like `customerId =
|
|
430
|
+
// "cust-1"` can hydrate labels without first encoding to
|
|
431
|
+
// `Ordering_Customer:cust-1` base64.
|
|
432
|
+
let passIds = switch ids {
|
|
433
|
+
| Some(idList) if idList->Array.length > 0 =>
|
|
434
|
+
let itemId = getId(item)
|
|
435
|
+
let itemLocalId =
|
|
436
|
+
DomainGraphQL_Server.decodeGlobalId(itemId)->Option.map(((_, lid)) => lid)
|
|
437
|
+
idList->Array.some(i => i == itemId || itemLocalId == Some(i))
|
|
438
|
+
| _ => true
|
|
439
|
+
}
|
|
440
|
+
let passPerField = perFieldChecks->Array.every(check => check(item))
|
|
441
|
+
passSearch && passPrefix && passIds && passPerField
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
// Apply orderBy when provided. Sorts on the requested field; ties
|
|
445
|
+
// broken by id so keyset-style cursors stay stable across requests
|
|
446
|
+
// that share a sort field. When orderBy is omitted, items are sorted
|
|
447
|
+
// by id ascending so the natural order is deterministic and pagination
|
|
448
|
+
// cursors remain stable.
|
|
449
|
+
let orderByDict =
|
|
450
|
+
argsDict
|
|
451
|
+
->Dict.get("orderBy")
|
|
452
|
+
->Option.flatMap(JSON.Decode.object)
|
|
453
|
+
let orderByField =
|
|
454
|
+
orderByDict
|
|
455
|
+
->Option.flatMap(ob => ob->Dict.get("field"))
|
|
456
|
+
->Option.flatMap(JSON.Decode.string)
|
|
457
|
+
let direction =
|
|
458
|
+
orderByDict
|
|
459
|
+
->Option.flatMap(ob => ob->Dict.get("direction"))
|
|
460
|
+
->Option.flatMap(JSON.Decode.string)
|
|
461
|
+
->Option.getOr("ASC")
|
|
462
|
+
let isDesc = direction == "DESC"
|
|
463
|
+
let sorted = switch orderByField {
|
|
464
|
+
| Some(f) =>
|
|
465
|
+
let cmp = (a, b) => {
|
|
466
|
+
let av = getFieldString(a, f)->Option.getOr("")
|
|
467
|
+
let bv = getFieldString(b, f)->Option.getOr("")
|
|
468
|
+
let primary = if av < bv {
|
|
469
|
+
-1
|
|
470
|
+
} else if av > bv {
|
|
471
|
+
1
|
|
472
|
+
} else {
|
|
473
|
+
0
|
|
474
|
+
}
|
|
475
|
+
let primary = isDesc ? -primary : primary
|
|
476
|
+
if primary != 0 {
|
|
477
|
+
primary
|
|
478
|
+
} else {
|
|
479
|
+
let aid = getId(a)
|
|
480
|
+
let bid = getId(b)
|
|
481
|
+
if aid < bid {
|
|
482
|
+
-1
|
|
483
|
+
} else if aid > bid {
|
|
484
|
+
1
|
|
485
|
+
} else {
|
|
486
|
+
0
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
filtered->Array.toSorted((a, b) => cmp(a, b)->Int.toFloat)
|
|
491
|
+
| None =>
|
|
492
|
+
// Default to id-ascending so cursor pagination has a stable order.
|
|
493
|
+
filtered->Array.toSorted((a, b) => {
|
|
494
|
+
let aid = getId(a)
|
|
495
|
+
let bid = getId(b)
|
|
496
|
+
if aid < bid {
|
|
497
|
+
-1.
|
|
498
|
+
} else if aid > bid {
|
|
499
|
+
1.
|
|
500
|
+
} else {
|
|
501
|
+
0.
|
|
502
|
+
}
|
|
503
|
+
})
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Keyset pagination. Cursor encodes the row's value for the active
|
|
507
|
+
// sort field — orderBy.field when supplied, "id" otherwise. Boundary
|
|
508
|
+
// is applied as a value comparison against the sorted array; in DESC
|
|
509
|
+
// sort the comparison flips. Note: when the cursor field has duplicate
|
|
510
|
+
// values, the boundary excludes all rows with the cursor's value, not
|
|
511
|
+
// just the row at the cursor position. Acceptable for in-memory dev;
|
|
512
|
+
// in practice common sort fields (id, createdAt) are unique.
|
|
513
|
+
let first =
|
|
514
|
+
argsDict->Dict.get("first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
|
|
515
|
+
let after = argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string)
|
|
516
|
+
let last =
|
|
517
|
+
argsDict->Dict.get("last")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
|
|
518
|
+
let before = argsDict->Dict.get("before")->Option.flatMap(JSON.Decode.string)
|
|
519
|
+
let isBackward = last->Option.isSome
|
|
520
|
+
let cursorField = orderByField->Option.getOr("id")
|
|
521
|
+
let getCursorValue = item =>
|
|
522
|
+
getFieldString(item, cursorField)->Option.getOr(getId(item))
|
|
523
|
+
|
|
524
|
+
let cursorBounded = switch (isBackward, after, before) {
|
|
525
|
+
| (false, Some(c), _) =>
|
|
526
|
+
let cv = decodeCursor(c)
|
|
527
|
+
sorted->Array.filter(item => {
|
|
528
|
+
let v = getCursorValue(item)
|
|
529
|
+
isDesc ? v < cv : v > cv
|
|
530
|
+
})
|
|
531
|
+
| (true, _, Some(c)) =>
|
|
532
|
+
let cv = decodeCursor(c)
|
|
533
|
+
sorted->Array.filter(item => {
|
|
534
|
+
let v = getCursorValue(item)
|
|
535
|
+
isDesc ? v > cv : v < cv
|
|
536
|
+
})
|
|
537
|
+
| _ => sorted
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
let pageSize = if isBackward {
|
|
541
|
+
last->Option.getOr(defaultListPageSize)
|
|
542
|
+
} else {
|
|
543
|
+
first->Option.getOr(defaultListPageSize)
|
|
544
|
+
}
|
|
545
|
+
let take = pageSize + 1
|
|
546
|
+
let (pageItems, hasMore) = if isBackward {
|
|
547
|
+
// Take the last `take` items from the cursor-bounded slice. If we
|
|
548
|
+
// grabbed an extra, drop the leading entry (the boundary marker).
|
|
549
|
+
let len = cursorBounded->Array.length
|
|
550
|
+
let startIdx = len > take ? len - take : 0
|
|
551
|
+
let arr = cursorBounded->Array.slice(~start=startIdx, ~end=len)
|
|
552
|
+
let hasMore = arr->Array.length > pageSize
|
|
553
|
+
let result = if hasMore {
|
|
554
|
+
arr->Array.slice(~start=1, ~end=arr->Array.length)
|
|
555
|
+
} else {
|
|
556
|
+
arr
|
|
557
|
+
}
|
|
558
|
+
(result, hasMore)
|
|
559
|
+
} else {
|
|
560
|
+
let arr = cursorBounded->Array.slice(~start=0, ~end=take)
|
|
561
|
+
let hasMore = arr->Array.length > pageSize
|
|
562
|
+
(arr->Array.slice(~start=0, ~end=pageSize), hasMore)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let edges = pageItems->Array.map(item =>
|
|
566
|
+
Obj.magic({"node": item, "cursor": encodeCursor(getCursorValue(item))})
|
|
567
|
+
)
|
|
568
|
+
let startCursor =
|
|
569
|
+
pageItems->Array.get(0)->Option.map(item => encodeCursor(getCursorValue(item)))
|
|
570
|
+
let endCursor =
|
|
571
|
+
pageItems
|
|
572
|
+
->Array.get(pageItems->Array.length - 1)
|
|
573
|
+
->Option.map(item => encodeCursor(getCursorValue(item)))
|
|
574
|
+
Obj.magic({
|
|
575
|
+
"edges": edges,
|
|
576
|
+
"pageInfo": {
|
|
577
|
+
"hasNextPage": !isBackward && hasMore,
|
|
578
|
+
"hasPreviousPage": isBackward && hasMore,
|
|
579
|
+
"startCursor": startCursor->Nullable.fromOption,
|
|
580
|
+
"endCursor": endCursor->Nullable.fromOption,
|
|
581
|
+
},
|
|
582
|
+
})
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
(sdl, resolver)
|
|
586
|
+
} else {
|
|
587
|
+
// Legacy AppSync-style format
|
|
588
|
+
let sdl = [` ${listQueryName}(nextToken: String, limit: Int): ${pluralTypeName}!`]
|
|
589
|
+
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
590
|
+
switch await runInterceptor(~ctx, ~args) {
|
|
591
|
+
| Deny(_) => Obj.magic({"nextToken": Nullable.null, "scannedCount": 0, "items": []})
|
|
592
|
+
| Allow =>
|
|
593
|
+
let items = switch Bus.getQueryDbStream(name) {
|
|
594
|
+
| Some(makeStream) =>
|
|
595
|
+
await makeStream()->Stream.runCollect->Effect.runPromise
|
|
596
|
+
| None =>
|
|
597
|
+
switch Bus.getQueryDbScan(name) {
|
|
598
|
+
| Some(scanAll) => scanAll()
|
|
599
|
+
| None => []
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
Obj.magic({"nextToken": Nullable.null, "scannedCount": items->Array.length, "items": items})
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
(sdl, resolver)
|
|
606
|
+
}
|
|
607
|
+
let listResolvers = [(listQueryName, listResolver)]
|
|
608
|
+
|
|
609
|
+
// -- Items query: {name}Items (only when subId configured) -----------------
|
|
610
|
+
// Relay Connection response. Accepts `filter` input object + first/after/last/before.
|
|
611
|
+
// Cursor is base64 of the sort key value (keyset pagination — see module-level
|
|
612
|
+
// `encodeCursor` / `decodeCursor` helpers).
|
|
613
|
+
let itemsSdl = switch subIdField {
|
|
614
|
+
| Some(_sf) =>
|
|
615
|
+
let filterTypeName = returnTypeName ++ "ItemsFilter"
|
|
616
|
+
let connectionTypeName = returnTypeName ++ "Connection"
|
|
617
|
+
server.registerTypes(
|
|
618
|
+
~sdlTypes=[
|
|
619
|
+
`input ${filterTypeName} {\n prefix: String\n from: String\n to: String\n eq: String\n order: SortOrder\n}`,
|
|
620
|
+
],
|
|
621
|
+
)
|
|
622
|
+
[` ${singleQueryName}Items(id: ID!, filter: ${filterTypeName}, first: Int, after: String, last: Int, before: String): ${connectionTypeName}!`]
|
|
623
|
+
| None => []
|
|
624
|
+
}
|
|
625
|
+
let itemsResolvers: array<(string, GraphQL_ServerInstance.resolverFn)> = switch subIdField {
|
|
626
|
+
| Some(sf) =>
|
|
627
|
+
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
628
|
+
let emptyConn = Obj.magic({
|
|
629
|
+
"edges": [],
|
|
630
|
+
"pageInfo": {
|
|
631
|
+
"hasNextPage": false,
|
|
632
|
+
"hasPreviousPage": false,
|
|
633
|
+
"startCursor": Nullable.null,
|
|
634
|
+
"endCursor": Nullable.null,
|
|
635
|
+
},
|
|
636
|
+
})
|
|
637
|
+
switch await runInterceptor(~ctx, ~args) {
|
|
638
|
+
| Deny(_) => emptyConn
|
|
639
|
+
| Allow =>
|
|
640
|
+
let argsDict = args->JSON.Decode.object->Option.getOr(Dict.make())
|
|
641
|
+
let id = argsDict->Dict.get("id")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
642
|
+
let filterDict =
|
|
643
|
+
argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
|
|
644
|
+
let filterPrefix = filterDict->Dict.get("prefix")->Option.flatMap(JSON.Decode.string)
|
|
645
|
+
let filterFrom = filterDict->Dict.get("from")->Option.flatMap(JSON.Decode.string)
|
|
646
|
+
let filterTo = filterDict->Dict.get("to")->Option.flatMap(JSON.Decode.string)
|
|
647
|
+
let filterEq = filterDict->Dict.get("eq")->Option.flatMap(JSON.Decode.string)
|
|
648
|
+
let orderDesc = filterDict->Dict.get("order")->Option.flatMap(JSON.Decode.string)->Option.map(o => o == "DESC")->Option.getOr(false)
|
|
649
|
+
let first = argsDict->Dict.get("first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
|
|
650
|
+
let after = argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string)
|
|
651
|
+
let last = argsDict->Dict.get("last")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
|
|
652
|
+
let before = argsDict->Dict.get("before")->Option.flatMap(JSON.Decode.string)
|
|
653
|
+
let isBackward = last->Option.isSome
|
|
654
|
+
|
|
655
|
+
switch Bus.getQueryDb(name) {
|
|
656
|
+
| None => emptyConn
|
|
657
|
+
| Some(ops) =>
|
|
658
|
+
let allItems =
|
|
659
|
+
await ops.loadStream(id)
|
|
660
|
+
->Stream.runCollect
|
|
661
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
662
|
+
->Effect.runPromise
|
|
663
|
+
|
|
664
|
+
// Cursor-keyed filtering: exclude items on the cursor side of the boundary
|
|
665
|
+
let cursorFiltered = if isBackward {
|
|
666
|
+
switch before->Option.map(decodeCursor) {
|
|
667
|
+
| Some(beforeKey) =>
|
|
668
|
+
allItems->Array.filter(item =>
|
|
669
|
+
item->JSON.Decode.object->Option.flatMap(d => d->Dict.get(sf))->Option.flatMap(JSON.Decode.string)->Option.map(v => v < beforeKey)->Option.getOr(false)
|
|
670
|
+
)
|
|
671
|
+
| None => allItems
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
switch after->Option.map(decodeCursor) {
|
|
675
|
+
| Some(afterKey) =>
|
|
676
|
+
allItems->Array.filter(item =>
|
|
677
|
+
item->JSON.Decode.object->Option.flatMap(d => d->Dict.get(sf))->Option.flatMap(JSON.Decode.string)->Option.map(v => v > afterKey)->Option.getOr(false)
|
|
678
|
+
)
|
|
679
|
+
| None => allItems
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Apply SortKey_Filter for prefix/from/to/eq
|
|
684
|
+
let filtered = SortKey_Filter.apply(
|
|
685
|
+
~items=cursorFiltered,
|
|
686
|
+
~skField=sf,
|
|
687
|
+
~prefix=?filterPrefix,
|
|
688
|
+
~from=?filterFrom,
|
|
689
|
+
~to_=?filterTo,
|
|
690
|
+
~eq=?filterEq,
|
|
691
|
+
~reverse=if isBackward { !orderDesc } else { orderDesc },
|
|
692
|
+
~offset=0,
|
|
693
|
+
).items
|
|
694
|
+
|
|
695
|
+
// For backward pagination flip to logical order after taking
|
|
696
|
+
let (pageItems, hasMore) = switch (isBackward, last, first) {
|
|
697
|
+
| (true, Some(n), _) =>
|
|
698
|
+
let take = n + 1
|
|
699
|
+
let taken = filtered->Array.slice(~start=0, ~end=take)
|
|
700
|
+
let hasMore = taken->Array.length > n
|
|
701
|
+
let result = taken->Array.slice(~start=0, ~end=n)->Array.toReversed
|
|
702
|
+
(result, hasMore)
|
|
703
|
+
| (_, _, Some(n)) =>
|
|
704
|
+
let take = n + 1
|
|
705
|
+
let taken = filtered->Array.slice(~start=0, ~end=take)
|
|
706
|
+
let hasMore = taken->Array.length > n
|
|
707
|
+
(taken->Array.slice(~start=0, ~end=n), hasMore)
|
|
708
|
+
| _ => (filtered, false)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let getSkValue = item =>
|
|
712
|
+
item->JSON.Decode.object->Option.flatMap(d => d->Dict.get(sf))->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
713
|
+
|
|
714
|
+
let edges = pageItems->Array.map(item =>
|
|
715
|
+
Obj.magic({"node": item, "cursor": encodeCursor(getSkValue(item))})
|
|
716
|
+
)
|
|
717
|
+
let startCursor = pageItems->Array.get(0)->Option.map(item => encodeCursor(getSkValue(item)))
|
|
718
|
+
let endCursor = pageItems->Array.get(pageItems->Array.length - 1)->Option.map(item => encodeCursor(getSkValue(item)))
|
|
719
|
+
|
|
720
|
+
Obj.magic({
|
|
721
|
+
"edges": edges,
|
|
722
|
+
"pageInfo": {
|
|
723
|
+
"hasNextPage": !isBackward && hasMore,
|
|
724
|
+
"hasPreviousPage": isBackward && hasMore,
|
|
725
|
+
"startCursor": startCursor->Nullable.fromOption,
|
|
726
|
+
"endCursor": endCursor->Nullable.fromOption,
|
|
727
|
+
},
|
|
728
|
+
})
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
[(singleQueryName ++ "Items", resolver)]
|
|
733
|
+
| None => []
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// -- Index queries: {name}By{Index} ---------------------------------------
|
|
737
|
+
let indexSdlFields = indexes->Array.map((ic: Reventless.ReadModel.indexConfig) =>
|
|
738
|
+
` ${singleQueryName}By${cap(ic.index)}(${ic.index}: String!): [String]`
|
|
739
|
+
)
|
|
740
|
+
let indexResolvers: array<(string, GraphQL_ServerInstance.resolverFn)> = indexes->Array.map(
|
|
741
|
+
(ic: Reventless.ReadModel.indexConfig) => {
|
|
742
|
+
let index = ic.index
|
|
743
|
+
let resolverName = singleQueryName ++ "By" ++ cap(index)
|
|
744
|
+
let filterField = ic.idField->Option.getOr(index)
|
|
745
|
+
let resolver: GraphQL_ServerInstance.resolverFn = async (_root, args, ctx) => {
|
|
746
|
+
switch await runInterceptor(~ctx, ~args) {
|
|
747
|
+
| Deny(_) => []->JSON.Encode.array
|
|
748
|
+
| Allow =>
|
|
749
|
+
let value =
|
|
750
|
+
args->JSON.Decode.object->Option.flatMap(d => d->Dict.get(index))->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
751
|
+
switch Bus.getQueryDbScan(name) {
|
|
752
|
+
| Some(scanAll) =>
|
|
753
|
+
scanAll()
|
|
754
|
+
->Array.filter(item =>
|
|
755
|
+
item
|
|
756
|
+
->JSON.Decode.object
|
|
757
|
+
->Option.flatMap(d => d->Dict.get(filterField))
|
|
758
|
+
->Option.flatMap(JSON.Decode.string)
|
|
759
|
+
->Option.map(v => v == value)
|
|
760
|
+
->Option.getOr(false)
|
|
761
|
+
)
|
|
762
|
+
->JSON.Encode.array
|
|
763
|
+
| None => []->JSON.Encode.array
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
(resolverName, resolver)
|
|
768
|
+
},
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
// -- Register all fields --------------------------------------------------
|
|
772
|
+
let allSdl =
|
|
773
|
+
[byIdSdl]
|
|
774
|
+
->Array.concat(byIdsSdl)
|
|
775
|
+
->Array.concat(listSdl)
|
|
776
|
+
->Array.concat(itemsSdl)
|
|
777
|
+
->Array.concat(indexSdlFields)
|
|
778
|
+
|
|
779
|
+
let resolvers = Dict.make()
|
|
780
|
+
resolvers->Dict.set(singleQueryName, byIdResolver)
|
|
781
|
+
byIdsResolverEntry->Option.forEach(((k, v)) => resolvers->Dict.set(k, v))
|
|
782
|
+
listResolvers->Array.forEach(((k, v)) => resolvers->Dict.set(k, v))
|
|
783
|
+
itemsResolvers->Array.forEach(((k, v)) => resolvers->Dict.set(k, v))
|
|
784
|
+
indexResolvers->Array.forEach(((k, v)) => resolvers->Dict.set(k, v))
|
|
785
|
+
server.registerQueries(~sdlFields=allSdl, ~resolvers)
|
|
786
|
+
|
|
787
|
+
{
|
|
788
|
+
resources: [],
|
|
789
|
+
resourcesMaker: _ => [],
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
}
|