@reventlessdev/reventless-local 3.0.0-alpha.248 → 3.0.0-alpha.250
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 +16 -0
- package/package.json +14 -14
- package/src/BakedManifest.res +5 -6
- package/src/EmitCapabilities.res +3 -1
- package/src/LocalPlatformRegistry.res +3 -1
- package/src/LocalPlatformStart.res +7 -6
- package/src/LocalSeedTarget.res +3 -1
- package/src/Platform.res +291 -215
- package/src/ShellConfig.res +10 -17
- package/src/UiHints.res +8 -14
- package/src/UiSlots.res +10 -17
- package/src/adapter/Api/LocalEvents_Server.res +5 -3
- package/src/adapter/Api/LocalGraphQL_Adapter.res +4 -4
- package/src/adapter/Auth/LocalAuth.res +11 -15
- package/src/adapter/Auth/UserStore.res +5 -7
- package/src/adapter/Backend.res +1 -2
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +42 -26
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +13 -3
- package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +15 -17
- package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +18 -22
- package/src/adapter/Counter/LocalCounterHandler.res +1 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +33 -22
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +57 -57
- package/src/adapter/DomainGraphQL_Server.res +35 -34
- package/src/adapter/DomainMCP_Server.res +45 -41
- package/src/adapter/EventCollector/LocalEventCollectorChannel.res +47 -36
- package/src/adapter/EventHistory/EventHistoryResolvers_GraphQL.res +15 -12
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +9 -4
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +36 -24
- package/src/adapter/EventTopic/LocalEventTopicPublisher.res +9 -3
- package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +9 -14
- package/src/adapter/LocalBus.res +12 -12
- package/src/adapter/LocalCapabilities.res.mjs +1 -1
- package/src/adapter/LocalGeocodeResolvers.res +1 -4
- package/src/adapter/LocalSideEffectHandler.res +1 -1
- package/src/adapter/LocalStateChangeDescriptor.res +2 -1
- package/src/adapter/MCP_ServerInstance.res +51 -39
- package/src/adapter/ObjectStore/LocalObjectStore.res +5 -2
- package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -2
- package/src/adapter/PlatformGraphQL_Server.res +9 -5
- package/src/adapter/ProjectionCheckpoint.res +8 -1
- package/src/adapter/ProjectionPending.res +1 -3
- package/src/adapter/QueryDb/LocalQueryDbStorage.res +2 -1
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +367 -269
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +5 -3
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +63 -40
- package/src/adapter/QueryDb/SortKey_Filter.res +14 -6
- package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +12 -10
- package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +8 -2
- package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +1 -4
- package/src/adapter/Scheduler/LocalScheduledPublisher.res +12 -12
- package/src/adapter/SqliteDriver.res +16 -4
- package/src/adapter/Task/TaskBucket_Sqlite.res +5 -6
- package/src/components/Aggregate_Builder.res +6 -1
- package/src/components/Counter_Builder.res +1 -5
- package/src/components/DcbEventLog_Builder.res +5 -3
- package/src/components/ExtensionPoint_Builder.res +13 -15
- package/src/components/InboundTranslationSlice_Builder.res +3 -2
- package/src/components/OutboundTranslationSlice_Builder.res +3 -2
- package/src/components/ReadModel_Builder.res +18 -20
- package/src/components/StateViewSlice_Builder.res +3 -2
- package/src/components/Task_Builder.res +12 -13
- package/src/reset/LocalSeedReset.res +2 -5
- package/src/test/Mocks/MockDcbEventLogStorage.res +5 -1
- package/src/test/Mocks/MockEventLogStorage.res +8 -3
- package/src/test/Mocks/MockQueryDbStorage.res +3 -2
- package/src/test/TestRunner.res +1 -1
- package/tests/BakedManifestFilesTest.res +7 -10
- package/tests/LocalEventTapTest.res +26 -21
- package/tests/LocalPlatformStartTest.res +33 -28
- package/tests/PluginEventDecodeTest.res +3 -3
- package/tests/ShellConfigTest.res +30 -21
- package/tests/SplitApiFixtures.res +34 -10
- package/tests/SplitApiTest.res +92 -62
- package/tests/adapter/BackendParityTest.res +180 -130
- package/tests/adapter/CollectNEventsTest.res +6 -4
- package/tests/adapter/CommandAuthorizationTest.res +47 -41
- package/tests/adapter/CommandTopicChannelTest.res +163 -139
- package/tests/adapter/CounterHandlerTest.res +95 -77
- package/tests/adapter/DcbEventLogStorageSqliteTest.res +171 -168
- package/tests/adapter/DcbEventLogStorageTest.res +264 -207
- package/tests/adapter/DomainGraphQL_ServerTest.res +53 -43
- package/tests/adapter/EventCollectorChannelTest.res +153 -124
- package/tests/adapter/EventHistoryResolverTest.res +46 -18
- package/tests/adapter/EventLogSnapshotParityTest.res +57 -45
- package/tests/adapter/EventLogStorageSqliteTest.res +9 -10
- package/tests/adapter/EventTapTest.res +37 -20
- package/tests/adapter/EventTopicPublisherTest.res +74 -50
- package/tests/adapter/GraphQLSubscriptionBridgeTest.res +4 -3
- package/tests/adapter/GraphQL_SchemaInspectorTest.res +666 -568
- package/tests/adapter/GraphQL_SubscriptionResolversTest.res +105 -96
- package/tests/adapter/HeartbeatRunnerTest.res +99 -81
- package/tests/adapter/InboundTranslationMutationTest.res +4 -1
- package/tests/adapter/LocalAuthHttpTest.res +79 -85
- package/tests/adapter/LocalAuthLoginTest.res +42 -50
- package/tests/adapter/LocalAuthTest.res +8 -16
- package/tests/adapter/LocalAuthUserStoreTest.res +2 -7
- package/tests/adapter/LocalBusBoundedTest.res +187 -117
- package/tests/adapter/LocalBusPubSubTest.res +118 -76
- package/tests/adapter/LocalDispatchContextTest.res +1 -2
- package/tests/adapter/LocalEvents_ServerTest.res +242 -187
- package/tests/adapter/PgProjectionCatchupTest.res +44 -10
- package/tests/adapter/ProjectionCheckpointTest.res +261 -206
- package/tests/adapter/QueryDbGsiTtlTest.res +123 -83
- package/tests/adapter/QueryDbListPushdownParityTest.res +107 -67
- package/tests/adapter/QueryDbListResolverTest.res +225 -208
- package/tests/adapter/QueryDbResolvedFieldsTest.res +17 -14
- package/tests/adapter/QueryDbStorageSqliteTest.res +33 -49
- package/tests/adapter/QueryDbStorageTest.res +163 -117
- package/tests/adapter/QueryDbUnionRoundTripTest.res +8 -10
- package/tests/adapter/QueryEngineTest.res +179 -126
- package/tests/adapter/ScheduledPublisherTest.res +171 -135
- package/tests/adapter/ServedBucketHttpTest.res +65 -59
- package/tests/adapter/SortKey_FilterTest.res +19 -3
- package/tests/adapter/SqliteDriverTest.res +13 -10
- package/tests/adapter/TaskBucketTest.res +94 -80
- package/tests/components/aggregate/AggregateTest.res +31 -18
- package/tests/components/automationslice/AutomationSliceCallbackTest.res +259 -222
- package/tests/components/automationslice/AutomationSliceFixtures.res +4 -2
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +38 -38
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +12 -14
- package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +8 -32
- package/tests/components/commandgenerator/CommandGeneratorFixtures.res +7 -5
- package/tests/components/commandgenerator/CommandGeneratorTest.res +40 -31
- package/tests/components/commandtopic/CommandTopicFixtures.res +3 -4
- package/tests/components/commandtopic/CommandTopicStreamFixtures.res +7 -5
- package/tests/components/commandtopic/CommandTopicTest.res +24 -15
- package/tests/components/counter/CounterFixtures.res +6 -8
- package/tests/components/dcb/DcbCrossPartitionFixtures.res +8 -2
- package/tests/components/dcb/DcbCrossPartitionReadTest.res +20 -13
- package/tests/components/dcb/DcbEventLogAppendStreamTest.res +12 -8
- package/tests/components/dcb/DcbEventLogStreamTest.res +88 -61
- package/tests/components/dcb/DcbFixtures.res +4 -1
- package/tests/components/dcb/DcbTest.res +9 -6
- package/tests/components/eventcollector/EventCollectorFixtures.res +0 -1
- package/tests/components/eventcollector/EventCollectorTest.res +6 -3
- package/tests/components/eventlog/EventLogAppendStreamTest.res +88 -68
- package/tests/components/eventlog/EventLogConflictTest.res +8 -4
- package/tests/components/eventlog/EventLogFixtures.res +3 -3
- package/tests/components/eventlog/EventLogProvisioningSeamTest.res +26 -13
- package/tests/components/eventlog/EventLogStreamTest.res +119 -56
- package/tests/components/eventlog/EventLogTest.res +20 -4
- package/tests/components/eventtopic/EventTopicFixtures.res +3 -3
- package/tests/components/eventtopic/EventTopicStreamFixtures.res +1 -2
- package/tests/components/eventtopic/EventTopicStreamTest.res +7 -1
- package/tests/components/extensionpoint/ExtensionPointFixtures.res +14 -9
- package/tests/components/extensionpoint/ExtensionPointTest.res +3 -2
- package/tests/components/heartbeat/HeartbeatFixtures.res +5 -6
- package/tests/components/heartbeat/HeartbeatTest.res +56 -47
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +271 -249
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -2
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +361 -276
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -10
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +37 -43
- package/tests/components/querydb/QueryDbFixtures.res +1 -5
- package/tests/components/querydb/QueryDbTest.res +2 -4
- package/tests/components/readmodel/DcbReadModelE2EFixtures.res +15 -18
- package/tests/components/readmodel/MixedSourceReadModelFixtures.res +5 -7
- package/tests/components/readmodel/ReadModelFixtures.res +8 -8
- package/tests/components/scheduler/SchedulerTest.res +76 -58
- package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +4 -4
- package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +5 -7
- package/tests/components/stateviewslice/StateViewSliceFixtures.res +8 -6
- package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +17 -11
- package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +12 -2
- package/tests/components/task/TaskFixtures.res +5 -5
- package/tests/components/task/TaskTest.res +95 -80
- package/tests/plugin/PluginsProjection_GWT.res +3 -1
- package/tests/plugin/UiFragments_GWT.res +6 -1
- package/tests/reset/LocalSeedResetTest.res +1 -0
package/src/Platform.res
CHANGED
|
@@ -45,7 +45,9 @@ let decodePluginEventEnvelope = (eventJson: JSON.t): option<ReventlessCore.Plugi
|
|
|
45
45
|
switch eventJson
|
|
46
46
|
->JSON.Decode.object
|
|
47
47
|
->Option.flatMap(d => d->Dict.get("event"))
|
|
48
|
-
->Option.map(payload =>
|
|
48
|
+
->Option.map(payload =>
|
|
49
|
+
payload->Reventless.Util_Sury.fromJson(ReventlessCore.PluginSpec.eventSchema)
|
|
50
|
+
) {
|
|
49
51
|
| result => result
|
|
50
52
|
| exception _ => None
|
|
51
53
|
}
|
|
@@ -54,9 +56,9 @@ let decodePluginEventEnvelope = (eventJson: JSON.t): option<ReventlessCore.Plugi
|
|
|
54
56
|
// bus envelope — same {id, meta, event} shape as the aggregate topic above
|
|
55
57
|
// (DcbEventLog_Operations publishes via Message.composeEventJson'). Top-level,
|
|
56
58
|
// not inside the functor, so the decode is unit-testable.
|
|
57
|
-
let decodeUiFragmentRegistryEventEnvelope = (
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
let decodeUiFragmentRegistryEventEnvelope = (eventJson: JSON.t): option<
|
|
60
|
+
ReventlessCore.UiFragmentRegistry.event,
|
|
61
|
+
> =>
|
|
60
62
|
switch eventJson
|
|
61
63
|
->JSON.Decode.object
|
|
62
64
|
->Option.flatMap(d => d->Dict.get("event"))
|
|
@@ -128,6 +130,7 @@ module MakeWithConfig = (
|
|
|
128
130
|
}
|
|
129
131
|
let db = SqliteDriver.openDb(~path)
|
|
130
132
|
BackendState.setSqlite(~db, ~path)
|
|
133
|
+
|
|
131
134
|
// The object store persists beside the database under this backend (see
|
|
132
135
|
// LocalObjectStore), so a reset that wipes the events wipes the bytes they
|
|
133
136
|
// reference too — otherwise the session starts with orphaned uploads and
|
|
@@ -304,7 +307,13 @@ module MakeWithConfig = (
|
|
|
304
307
|
EventHistoryResolvers.register(~server=resolveTargetGraphQL(), params),
|
|
305
308
|
// MCP tools and resources — registered during plugin construction.
|
|
306
309
|
// See the large lambda below; it references Bus for QueryDb lookups.
|
|
307
|
-
mcpSchemaRegistrationHook: ({
|
|
310
|
+
mcpSchemaRegistrationHook: ({
|
|
311
|
+
pluginName,
|
|
312
|
+
mutationEntries,
|
|
313
|
+
queryEntries,
|
|
314
|
+
eventLogEntries,
|
|
315
|
+
subscriptionFields,
|
|
316
|
+
}) => {
|
|
308
317
|
let mcp = resolveTargetMCP()
|
|
309
318
|
mcp.registerToolsFromEntries(~pluginName, ~mutationEntries, ~commandHandler=async (
|
|
310
319
|
toolName,
|
|
@@ -421,7 +430,9 @@ module MakeWithConfig = (
|
|
|
421
430
|
let filtered = switch after {
|
|
422
431
|
| Some(afterPos) =>
|
|
423
432
|
let idx =
|
|
424
|
-
events->Array.findIndex(e =>
|
|
433
|
+
events->Array.findIndex(e =>
|
|
434
|
+
getPosition(e)->Option.mapOr(false, p => positionGt(p, afterPos))
|
|
435
|
+
)
|
|
425
436
|
if idx >= 0 {
|
|
426
437
|
events->Array.slice(~start=idx, ~end=events->Array.length)
|
|
427
438
|
} else {
|
|
@@ -526,7 +537,7 @@ module MakeWithConfig = (
|
|
|
526
537
|
)
|
|
527
538
|
|
|
528
539
|
// Register subscription SDL fields and resolvers with the GraphQL server
|
|
529
|
-
let makeEntry = topic =>
|
|
540
|
+
let makeEntry = (topic): subscriptionEntry => {fieldName: topic, topic}
|
|
530
541
|
let sourceAEntries = eventLogEntries->Array.map(e => makeEntry(sourceATopic(e.displayName)))
|
|
531
542
|
registerAll(~server, ~sdlFields=subscriptionFields, ~sourceAEntries, ~sourceBEntries=[])
|
|
532
543
|
}
|
|
@@ -618,8 +629,7 @@ module MakeWithConfig = (
|
|
|
618
629
|
module Mappings: ReventlessInfra.ExtensionPoint.Mappings with module Spec := Spec = {
|
|
619
630
|
module type Mapping = ReventlessInfra.ExtensionPointMapping.T
|
|
620
631
|
with module ExtensionPoint := Spec
|
|
621
|
-
let name =
|
|
622
|
-
Mapping1.Delegate.name ++ "+" ++ Mapping2.Delegate.name
|
|
632
|
+
let name = Mapping1.Delegate.name ++ "+" ++ Mapping2.Delegate.name
|
|
623
633
|
// First mapping's URL — matches the user-extension merge convention.
|
|
624
634
|
let moduleUrl = Mapping1.moduleUrl
|
|
625
635
|
let mappings: array<module(Mapping)> = [module(CM1), module(CM2)]
|
|
@@ -643,11 +653,7 @@ module MakeWithConfig = (
|
|
|
643
653
|
module type Mapping = ReventlessInfra.ExtensionPointMapping.T
|
|
644
654
|
with module ExtensionPoint := Spec
|
|
645
655
|
let name =
|
|
646
|
-
Mapping1.Delegate.name ++
|
|
647
|
-
"+" ++
|
|
648
|
-
Mapping2.Delegate.name ++
|
|
649
|
-
"+" ++
|
|
650
|
-
Mapping3.Delegate.name
|
|
656
|
+
Mapping1.Delegate.name ++ "+" ++ Mapping2.Delegate.name ++ "+" ++ Mapping3.Delegate.name
|
|
651
657
|
// First mapping's URL — matches the user-extension merge convention.
|
|
652
658
|
let moduleUrl = Mapping1.moduleUrl
|
|
653
659
|
let mappings: array<module(Mapping)> = [module(CM1), module(CM2), module(CM3)]
|
|
@@ -668,14 +674,12 @@ module MakeWithConfig = (
|
|
|
668
674
|
): ReventlessInfra.Extension.Blueprint => {
|
|
669
675
|
module Spec = Mapping.ExtensionPoint
|
|
670
676
|
module CompiledMapping = ReventlessInfra.ExtensionMapping.Make(Mapping)
|
|
671
|
-
module type Mapping = ReventlessInfra.ExtensionMapping.T
|
|
672
|
-
with module ExtensionPoint := Spec
|
|
677
|
+
module type Mapping = ReventlessInfra.ExtensionMapping.T with module ExtensionPoint := Spec
|
|
673
678
|
let name = Mapping.Delegate.name
|
|
674
679
|
let moduleUrl = Mapping.moduleUrl
|
|
675
680
|
let delegateModuleUrl = Mapping.delegateModuleUrl
|
|
676
681
|
let mappings: array<module(Mapping)> = [module(CompiledMapping)]
|
|
677
682
|
}
|
|
678
|
-
|
|
679
683
|
}
|
|
680
684
|
|
|
681
685
|
module Task = {
|
|
@@ -690,8 +694,9 @@ module MakeWithConfig = (
|
|
|
690
694
|
module Make = (
|
|
691
695
|
Spec: Reventless.StateChangeSlice.Spec,
|
|
692
696
|
Behavior: Reventless.StateChangeSlice.Behavior with module Spec := Spec,
|
|
693
|
-
): (
|
|
694
|
-
|
|
697
|
+
): (
|
|
698
|
+
ReventlessInfra.StateChangeSlice.T with module Spec = Spec
|
|
699
|
+
) => StateChangeSlice_Builder.Make(Spec, Behavior)
|
|
695
700
|
/** Async variant — in-memory uses the same channel (no FIFO distinction). */
|
|
696
701
|
module MakeAsync = (
|
|
697
702
|
Spec: Reventless.StateChangeSlice.Spec,
|
|
@@ -706,8 +711,10 @@ module MakeWithConfig = (
|
|
|
706
711
|
module Make = (
|
|
707
712
|
Spec: Reventless.StateViewSlice.Spec,
|
|
708
713
|
Projection: Reventless.StateViewSlice.Projection with module Spec := Spec,
|
|
709
|
-
): (ReventlessInfra.StateViewSlice.T with module Spec = Spec) =>
|
|
710
|
-
|
|
714
|
+
): (ReventlessInfra.StateViewSlice.T with module Spec = Spec) => StateViewSliceMaker.Make(
|
|
715
|
+
Spec,
|
|
716
|
+
Projection,
|
|
717
|
+
)
|
|
711
718
|
}
|
|
712
719
|
|
|
713
720
|
// Admin UI-fragment registry (docs/plans/done/event-sourced-fragment-registries.md): the
|
|
@@ -727,32 +734,38 @@ module MakeWithConfig = (
|
|
|
727
734
|
module Make = (
|
|
728
735
|
Spec: Reventless.StateViewSlice.Spec,
|
|
729
736
|
Projection: Reventless.StateViewSlice.Projection with module Spec := Spec,
|
|
730
|
-
): (ReventlessInfra.StateViewSlice.T with module Spec = Spec) =>
|
|
731
|
-
|
|
737
|
+
): (ReventlessInfra.StateViewSlice.T with module Spec = Spec) => StateViewSliceMaker.Make(
|
|
738
|
+
Spec,
|
|
739
|
+
Projection,
|
|
740
|
+
)
|
|
732
741
|
}
|
|
733
742
|
|
|
734
743
|
module AutomationSlice = {
|
|
735
744
|
module Make = (
|
|
736
745
|
Spec: Reventless.AutomationSlice.Spec,
|
|
737
746
|
Automation: Reventless.AutomationSlice.Automation with module Spec := Spec,
|
|
738
|
-
): (ReventlessInfra.AutomationSlice.T with module Spec = Spec) =>
|
|
739
|
-
|
|
747
|
+
): (ReventlessInfra.AutomationSlice.T with module Spec = Spec) => AutomationSliceMaker.Make(
|
|
748
|
+
Spec,
|
|
749
|
+
Automation,
|
|
750
|
+
)
|
|
740
751
|
}
|
|
741
752
|
|
|
742
753
|
module OutboundTranslationSlice = {
|
|
743
754
|
module Make = (
|
|
744
755
|
Spec: Reventless.OutboundTranslationSlice.Spec,
|
|
745
756
|
Translation: Reventless.OutboundTranslationSlice.Translation with module Spec := Spec,
|
|
746
|
-
): (
|
|
747
|
-
|
|
757
|
+
): (
|
|
758
|
+
ReventlessInfra.OutboundTranslationSlice.T with module Spec = Spec
|
|
759
|
+
) => OutboundTranslationSliceMaker.Make(Spec, Translation)
|
|
748
760
|
}
|
|
749
761
|
|
|
750
762
|
module InboundTranslationSlice = {
|
|
751
763
|
module Make = (
|
|
752
764
|
Spec: Reventless.InboundTranslationSlice.Spec,
|
|
753
765
|
Translation: Reventless.InboundTranslationSlice.Translation with module Spec := Spec,
|
|
754
|
-
): (
|
|
755
|
-
|
|
766
|
+
): (
|
|
767
|
+
ReventlessInfra.InboundTranslationSlice.T with module Spec = Spec
|
|
768
|
+
) => InboundTranslationSliceMaker.Make(Spec, Translation)
|
|
756
769
|
}
|
|
757
770
|
|
|
758
771
|
module PluginMaker = Plugin_Builder.Make(
|
|
@@ -782,7 +795,8 @@ module MakeWithConfig = (
|
|
|
782
795
|
// CommandGenerator auto-flow (same path user-plugin aggregates use). Internal-protocol
|
|
783
796
|
// variants (Heartbeat, Connect, Disconnect, ReportIncompatibility) carry `@noApi` and
|
|
784
797
|
// are filtered out before SDL/resolver generation.
|
|
785
|
-
module LocalPluginAggregate: ReventlessInfra.Aggregate.T
|
|
798
|
+
module LocalPluginAggregate: ReventlessInfra.Aggregate.T
|
|
799
|
+
with type api = unit = AggregateMaker.Make(
|
|
786
800
|
ReventlessCore.PluginSpec,
|
|
787
801
|
ReventlessCore.PluginBehavior,
|
|
788
802
|
ReventlessInfra.NoEventMappings.Make(ReventlessCore.PluginSpec),
|
|
@@ -812,10 +826,7 @@ module MakeWithConfig = (
|
|
|
812
826
|
EventCollectorChannel,
|
|
813
827
|
QE,
|
|
814
828
|
LocalClonerRunner,
|
|
815
|
-
ReventlessCore.PluginRuntime_Builder_Micro.Make(
|
|
816
|
-
LocalRuntimeEnvironment,
|
|
817
|
-
EventCollectorChannel,
|
|
818
|
-
),
|
|
829
|
+
ReventlessCore.PluginRuntime_Builder_Micro.Make(LocalRuntimeEnvironment, EventCollectorChannel),
|
|
819
830
|
LocalDcbEventLogStorage.Make(Bus),
|
|
820
831
|
LocalEventTopicPublisher.Make(Bus),
|
|
821
832
|
LocalCommandTopicChannel.Make(Bus),
|
|
@@ -930,15 +941,16 @@ module MakeWithConfig = (
|
|
|
930
941
|
// Inside the apply, not after the forEach: the structure only exists
|
|
931
942
|
// once the Output resolves, so anything reading
|
|
932
943
|
// `pluginStructuresStore` synchronously below reads an empty dict.
|
|
933
|
-
declaredStoresOf(def)->Array.forEach(
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
944
|
+
declaredStoresOf(def)->Array.forEach(
|
|
945
|
+
qualified =>
|
|
946
|
+
// Registering the store is what lets an uploaded object be attributed
|
|
947
|
+
// to the plugin that declared it, the way an S3 key's prefix does —
|
|
948
|
+
// under the served `uploads/` prefix locally, for the reason
|
|
949
|
+
// LocalObjectStore.localPrefixFor gives.
|
|
950
|
+
LocalObjectStore.registerStore(
|
|
951
|
+
~qualified,
|
|
952
|
+
~prefix=LocalObjectStore.localPrefixFor(~qualified),
|
|
953
|
+
),
|
|
942
954
|
)
|
|
943
955
|
// Two plugins declaring one store name is refused by the deployed
|
|
944
956
|
// platform before it provisions anything. Local provisions no stores, so
|
|
@@ -950,12 +962,18 @@ module MakeWithConfig = (
|
|
|
950
962
|
// Checked against every store registered so far rather than this
|
|
951
963
|
// plugin's own, since a collision is by definition between two plugins.
|
|
952
964
|
ReventlessCore.StorePrefixCollision.collisionsFor(
|
|
953
|
-
~stores=LocalObjectStore.declaredStoreList()->Array.map(
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
965
|
+
~stores=LocalObjectStore.declaredStoreList()->Array.map(
|
|
966
|
+
((qualified, prefix)) => {
|
|
967
|
+
ReventlessCore.StorePrefixCollision.qualified,
|
|
968
|
+
prefix,
|
|
969
|
+
},
|
|
970
|
+
),
|
|
971
|
+
)->Array.forEach(
|
|
972
|
+
c =>
|
|
973
|
+
log.warn(
|
|
974
|
+
~comp="Platform:plugins",
|
|
975
|
+
ReventlessCore.StorePrefixCollision.collisionMessage(c),
|
|
976
|
+
),
|
|
959
977
|
)
|
|
960
978
|
| None => ()
|
|
961
979
|
}
|
|
@@ -966,15 +984,17 @@ module MakeWithConfig = (
|
|
|
966
984
|
// Bus keys for the admin Plugin aggregate (name-keyed). ComponentType.toName maps
|
|
967
985
|
// CommandTopic → "CmdTopic" and EventTopic → "EventTopic".
|
|
968
986
|
let pluginCmdTopicKey = ReventlessCore.PluginSpec.name ++ "Aggr" ++ "CmdTopic" // "PluginAggrCmdTopic"
|
|
969
|
-
let pluginEventTopicKey =
|
|
970
|
-
ReventlessCore.PluginSpec.name ++ "Aggr" ++ "EventTopic" // "PluginAggrEventTopic"
|
|
987
|
+
let pluginEventTopicKey = ReventlessCore.PluginSpec.name ++ "Aggr" ++ "EventTopic" // "PluginAggrEventTopic"
|
|
971
988
|
|
|
972
989
|
// Dispatch a Plugin aggregate command in-process via the Bus command topic. The
|
|
973
990
|
// body is the {id, meta, command} shape LocalCommandTopicChannel produces; the
|
|
974
991
|
// aggregate id is the plugin NAME. dispatchCommand parks the command until the
|
|
975
992
|
// aggregate's handler is registered (LocalBus pending-queue), so call-before-wire
|
|
976
993
|
// is safe.
|
|
977
|
-
let dispatchPluginCommand = (
|
|
994
|
+
let dispatchPluginCommand = (
|
|
995
|
+
~pluginName: string,
|
|
996
|
+
~command: ReventlessCore.PluginSpec.command,
|
|
997
|
+
) => {
|
|
978
998
|
let cmdJson: Reventless.Message.commandJson = {
|
|
979
999
|
id: pluginName,
|
|
980
1000
|
meta: ReventlessCore.Message.generateMeta(~service=ReventlessCore.PluginSpec.name),
|
|
@@ -1104,7 +1124,11 @@ module MakeWithConfig = (
|
|
|
1104
1124
|
~manifest=manifestJson(newManifest),
|
|
1105
1125
|
)
|
|
1106
1126
|
| Some(UiFragmentDeregistered({pluginId})) =>
|
|
1107
|
-
publishUIFragment(
|
|
1127
|
+
publishUIFragment(
|
|
1128
|
+
~name=pluginId,
|
|
1129
|
+
~changeKind="Deregistered",
|
|
1130
|
+
~manifest=JSON.Encode.null,
|
|
1131
|
+
)
|
|
1108
1132
|
| None => ()
|
|
1109
1133
|
}
|
|
1110
1134
|
}
|
|
@@ -1175,18 +1199,20 @@ module MakeWithConfig = (
|
|
|
1175
1199
|
},
|
|
1176
1200
|
),
|
|
1177
1201
|
extensionProtocols: [],
|
|
1178
|
-
apiSchemaFragment: apiSchemaFragment->Option.map(
|
|
1179
|
-
f
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1202
|
+
apiSchemaFragment: apiSchemaFragment->Option.map(
|
|
1203
|
+
f =>
|
|
1204
|
+
f->ReventlessCore.Plugin_Helpers.offloadPayload(
|
|
1205
|
+
~schema=Reventless.Plugin.apiSchemaFragmentSchema,
|
|
1206
|
+
~store="pluginApiFragments",
|
|
1207
|
+
),
|
|
1183
1208
|
),
|
|
1184
1209
|
apiTarget: None,
|
|
1185
|
-
structure: pluginStructure->Option.map(
|
|
1186
|
-
s
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1210
|
+
structure: pluginStructure->Option.map(
|
|
1211
|
+
s =>
|
|
1212
|
+
s->ReventlessCore.Plugin_Helpers.offloadPayload(
|
|
1213
|
+
~schema=Reventless.Plugin.pluginStructureSchema,
|
|
1214
|
+
~store="pluginStructures",
|
|
1215
|
+
),
|
|
1190
1216
|
),
|
|
1191
1217
|
dcbEventLog: None,
|
|
1192
1218
|
kind: Domain,
|
|
@@ -1227,8 +1253,7 @@ module MakeWithConfig = (
|
|
|
1227
1253
|
exts
|
|
1228
1254
|
->Dict.valuesToArray
|
|
1229
1255
|
->Array.map((ext: ReventlessInfra.Extension.outputs) => {
|
|
1230
|
-
let providerPlugin =
|
|
1231
|
-
ext.extensionPointName->String.split(".")->Array.getUnsafe(0)
|
|
1256
|
+
let providerPlugin = ext.extensionPointName->String.split(".")->Array.getUnsafe(0)
|
|
1232
1257
|
let wiring: ReventlessCore.Plugin_Helpers.extensionWiring = {
|
|
1233
1258
|
extensionName: ext.name,
|
|
1234
1259
|
extensionPointName: ext.extensionPointName,
|
|
@@ -1329,9 +1354,10 @@ module MakeWithConfig = (
|
|
|
1329
1354
|
// After the register, and it fills the tap port in once the socket is bound:
|
|
1330
1355
|
// the port is ephemeral by default, so it is not known until `listen` calls
|
|
1331
1356
|
// back. The entry therefore never names a port that is not listening.
|
|
1332
|
-
LocalEventTap.start(
|
|
1333
|
-
LocalPlatformRegistry.publishTapPort(~port=domainPort, ~tapPort)
|
|
1334
|
-
|
|
1357
|
+
LocalEventTap.start(
|
|
1358
|
+
~onBound=tapPort => LocalPlatformRegistry.publishTapPort(~port=domainPort, ~tapPort),
|
|
1359
|
+
(),
|
|
1360
|
+
)
|
|
1335
1361
|
// Fire onPlatformDeployed after all servers are started so late-deployed
|
|
1336
1362
|
// plugins (e.g. PlatformInspector) have their handler refs populated.
|
|
1337
1363
|
ReventlessCore.Plugin_Helpers.firePlatformDeployedHook({
|
|
@@ -1377,7 +1403,10 @@ module MakeWithConfig = (
|
|
|
1377
1403
|
let connectionResponse = (items: array<JSON.t>): JSON.t => {
|
|
1378
1404
|
let edges =
|
|
1379
1405
|
items->Array.mapWithIndex((item, i) =>
|
|
1380
|
-
Dict.fromArray([
|
|
1406
|
+
Dict.fromArray([
|
|
1407
|
+
("node", item),
|
|
1408
|
+
("cursor", Int.toString(i)->JSON.Encode.string),
|
|
1409
|
+
])->JSON.Encode.object
|
|
1381
1410
|
)
|
|
1382
1411
|
let hasItems = edges->Array.length > 0
|
|
1383
1412
|
Dict.fromArray([
|
|
@@ -1412,28 +1441,30 @@ module MakeWithConfig = (
|
|
|
1412
1441
|
// `<single>Items(id, …)` — all rows under one partition (composite key).
|
|
1413
1442
|
switch entry.subIdField {
|
|
1414
1443
|
| Some(_) =>
|
|
1415
|
-
queryResolvers->Dict.set(
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
}
|
|
1444
|
+
queryResolvers->Dict.set(entry.singleFieldName ++ "Items", async (
|
|
1445
|
+
_root,
|
|
1446
|
+
args,
|
|
1447
|
+
_ctx,
|
|
1448
|
+
): JSON.t =>
|
|
1449
|
+
if !live {
|
|
1450
|
+
connectionResponse([])
|
|
1451
|
+
} else {
|
|
1452
|
+
let id =
|
|
1453
|
+
args
|
|
1454
|
+
->JSON.Decode.object
|
|
1455
|
+
->Option.flatMap(d => d->Dict.get("id"))
|
|
1456
|
+
->Option.flatMap(JSON.Decode.string)
|
|
1457
|
+
->Option.getOr("")
|
|
1458
|
+
let items = switch Bus.getQueryDb(queryDbName) {
|
|
1459
|
+
| Some(ops) =>
|
|
1460
|
+
await ops.loadStream(id)
|
|
1461
|
+
->Stream.runCollect
|
|
1462
|
+
->Effect.catchAll(_ => Effect.succeed([]))
|
|
1463
|
+
->Effect.runPromise
|
|
1464
|
+
| None => []
|
|
1465
|
+
}
|
|
1466
|
+
connectionResponse(items)
|
|
1467
|
+
}
|
|
1437
1468
|
)
|
|
1438
1469
|
| None => ()
|
|
1439
1470
|
}
|
|
@@ -1450,8 +1481,9 @@ module MakeWithConfig = (
|
|
|
1450
1481
|
~singleFieldName=entry.singleFieldName,
|
|
1451
1482
|
~index=ic.index,
|
|
1452
1483
|
)
|
|
1453
|
-
queryResolvers->Dict.set(
|
|
1454
|
-
|
|
1484
|
+
queryResolvers->Dict.set(
|
|
1485
|
+
fieldName,
|
|
1486
|
+
async (_root, _args, _ctx): JSON.t => connectionResponse([]),
|
|
1455
1487
|
)
|
|
1456
1488
|
})
|
|
1457
1489
|
| None => ()
|
|
@@ -1547,16 +1579,20 @@ module MakeWithConfig = (
|
|
|
1547
1579
|
// BEFORE this session appends anything. Catch-up replays only (checkpoint,
|
|
1548
1580
|
// bound] — this session's own events (Connect dispatches, user commands)
|
|
1549
1581
|
// are live-delivered and must not be redelivered.
|
|
1550
|
-
let projectionCatchup =
|
|
1551
|
-
db
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1582
|
+
let projectionCatchup =
|
|
1583
|
+
BackendState.getSqliteDb()->Option.map(db => (
|
|
1584
|
+
db,
|
|
1585
|
+
ProjectionCheckpoint.maxPosition(db, ProjectionPending.Aggregate),
|
|
1586
|
+
ProjectionCheckpoint.maxPosition(db, ProjectionPending.Dcb),
|
|
1587
|
+
))
|
|
1555
1588
|
// Postgres: read models are in-memory (rebuilt on every start), so capture the
|
|
1556
1589
|
// pre-session head NOW — before plugins build or this session appends — and
|
|
1557
1590
|
// full-replay (0, head] after the plugins register their projection handlers.
|
|
1558
1591
|
let pgProjectionCatchup =
|
|
1559
|
-
BackendState.getPostgresPool()->Option.map(pool => (
|
|
1592
|
+
BackendState.getPostgresPool()->Option.map(pool => (
|
|
1593
|
+
pool,
|
|
1594
|
+
PgProjectionCatchup.captureBounds(pool),
|
|
1595
|
+
))
|
|
1560
1596
|
|
|
1561
1597
|
// Create scheduler and populate platform context refs.
|
|
1562
1598
|
let scheduler = makeScheduler()
|
|
@@ -1610,9 +1646,8 @@ module MakeWithConfig = (
|
|
|
1610
1646
|
// receiveRegistry entries are pre-populated (queuing forwarder) so calls
|
|
1611
1647
|
// are parked and drained once bindReceive fires (async, via Output.apply).
|
|
1612
1648
|
ReventlessCore.Plugin_Helpers.onPluginBuiltHook.contents = existingBuiltHook
|
|
1613
|
-
let allPluginOutputs =
|
|
1614
|
-
|
|
1615
|
-
)
|
|
1649
|
+
let allPluginOutputs =
|
|
1650
|
+
plugins->Array.map((p): ReventlessInfra.Plugin.outputs => ReventlessCore.Component.outputs(p))
|
|
1616
1651
|
firePluginDeployedHooks(~builtInfos=builtInfos.contents, ~pluginOutputs=allPluginOutputs)
|
|
1617
1652
|
|
|
1618
1653
|
// Wire cross-plugin Extension → EP EventTopic subscriptions.
|
|
@@ -1625,11 +1660,15 @@ module MakeWithConfig = (
|
|
|
1625
1660
|
(outputs.eventCollector, outputs.extensions)
|
|
1626
1661
|
->Pulumi.Output.all2
|
|
1627
1662
|
->Pulumi.Output.apply(((eventCollector, extensions)) => {
|
|
1628
|
-
extensions
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1663
|
+
extensions
|
|
1664
|
+
->Dict.toArray
|
|
1665
|
+
->Array.forEach(
|
|
1666
|
+
((_, ext: ReventlessInfra.Extension.outputs)) => {
|
|
1667
|
+
let epTopicKey =
|
|
1668
|
+
ext.extensionPointName->String.replace(".", "") ++ "ExtPointEventTopic"
|
|
1669
|
+
Bus.subscribeEventCollectorToTopic(eventCollector.name, epTopicKey)
|
|
1670
|
+
},
|
|
1671
|
+
)
|
|
1633
1672
|
})
|
|
1634
1673
|
})
|
|
1635
1674
|
ReventlessCore.Plugin_Helpers.firePlatformDeployedHook({
|
|
@@ -1773,7 +1812,9 @@ module MakeWithConfig = (
|
|
|
1773
1812
|
switch Bus.getQueryDbScan(pluginQueryDbName) {
|
|
1774
1813
|
| Some(scanAll) =>
|
|
1775
1814
|
scanAll()->Array.forEach(json =>
|
|
1776
|
-
switch json->Reventless.Util_Sury.fromJson(
|
|
1815
|
+
switch json->Reventless.Util_Sury.fromJson(
|
|
1816
|
+
ReventlessCore.PluginsReadModelSpec.stateSchema,
|
|
1817
|
+
) {
|
|
1777
1818
|
| state => statusByName->Dict.set(state.name, state.status)
|
|
1778
1819
|
| exception _ => ()
|
|
1779
1820
|
}
|
|
@@ -1799,7 +1840,9 @@ module MakeWithConfig = (
|
|
|
1799
1840
|
let adminQueryEntry = ReventlessCore.PluginBaseFragment.queryEntries->Array.getUnsafe(0)
|
|
1800
1841
|
let singleQueryField = adminQueryEntry.singleFieldName
|
|
1801
1842
|
let listQueryField = adminQueryEntry.listFieldName
|
|
1802
|
-
let adminMutationEntries = ReventlessCore.Platform_AdminApi.mutationEntries(
|
|
1843
|
+
let adminMutationEntries = ReventlessCore.Platform_AdminApi.mutationEntries(
|
|
1844
|
+
~cloner=Config.cloner,
|
|
1845
|
+
)
|
|
1803
1846
|
let adminMutationFieldNames = adminMutationEntries->Array.flatMap(entry => entry.fieldNames)
|
|
1804
1847
|
|
|
1805
1848
|
// Register the admin Plugin aggregate's types/queries/mutations to the platform target.
|
|
@@ -1855,7 +1898,9 @@ module MakeWithConfig = (
|
|
|
1855
1898
|
switch Bus.getQueryDbScan(pluginQueryDbName) {
|
|
1856
1899
|
| Some(scanAll) =>
|
|
1857
1900
|
scanAll()->Array.forEach(json =>
|
|
1858
|
-
switch json->Reventless.Util_Sury.fromJson(
|
|
1901
|
+
switch json->Reventless.Util_Sury.fromJson(
|
|
1902
|
+
ReventlessCore.PluginsReadModelSpec.stateSchema,
|
|
1903
|
+
) {
|
|
1859
1904
|
| state =>
|
|
1860
1905
|
let id = state.name ++ "@" ++ state.version
|
|
1861
1906
|
dict->Dict.set(id, state.status)
|
|
@@ -1897,67 +1942,60 @@ module MakeWithConfig = (
|
|
|
1897
1942
|
latestByName->Dict.valuesToArray->Array.map(((_, pair)) => pair)
|
|
1898
1943
|
}
|
|
1899
1944
|
|
|
1900
|
-
queryResolvers->Dict.set(
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
)
|
|
1907
|
-
->JSON.Encode.array,
|
|
1945
|
+
queryResolvers->Dict.set("Platform_ComponentDefinitions", async (_root, _args, _ctx): JSON.t =>
|
|
1946
|
+
connectedLatestStructures()
|
|
1947
|
+
->Array.map(((pluginId, def)) =>
|
|
1948
|
+
ReventlessCore.Platform_ComponentDefinitionsApi.encodePluginStructureEntry(~pluginId, def)
|
|
1949
|
+
)
|
|
1950
|
+
->JSON.Encode.array
|
|
1908
1951
|
)
|
|
1909
1952
|
|
|
1910
1953
|
// Platform_PluginStructures resolver — the same plugins, encoded unfiltered for
|
|
1911
1954
|
// developer tooling (Internal components kept, extension points carried).
|
|
1912
|
-
queryResolvers->Dict.set(
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
)
|
|
1919
|
-
->JSON.Encode.array,
|
|
1955
|
+
queryResolvers->Dict.set("Platform_PluginStructures", async (_root, _args, _ctx): JSON.t =>
|
|
1956
|
+
connectedLatestStructures()
|
|
1957
|
+
->Array.map(((pluginId, def)) =>
|
|
1958
|
+
ReventlessCore.Platform_PluginStructuresApi.encodePluginStructureEntry(~pluginId, def)
|
|
1959
|
+
)
|
|
1960
|
+
->JSON.Encode.array
|
|
1920
1961
|
)
|
|
1921
1962
|
|
|
1922
1963
|
// Platform_UIFragments resolver — reads the UiFragments StateViewSlice
|
|
1923
1964
|
// QueryDb (populated by the UiFragmentRegistry slice's projection) and
|
|
1924
1965
|
// encodes via the shared Platform_UIFragmentsApi encoder so AWS and
|
|
1925
1966
|
// in-memory return the same JSON shape.
|
|
1926
|
-
queryResolvers->Dict.set(
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
if ReventlessCore.Plugin.compareVersions(version, prevVersion) > 0 {
|
|
1946
|
-
latestByName->Dict.set(name, (version, state))
|
|
1947
|
-
}
|
|
1948
|
-
| None => latestByName->Dict.set(name, (version, state))
|
|
1967
|
+
queryResolvers->Dict.set("Platform_UIFragments", async (_root, _args, _ctx): JSON.t => {
|
|
1968
|
+
let items = switch Bus.getQueryDbScan(uiFragmentQueryDbName) {
|
|
1969
|
+
| Some(scanAll) => scanAll()
|
|
1970
|
+
| None => []
|
|
1971
|
+
}
|
|
1972
|
+
// Collapse to one entry per plugin name (highest version). The registry is
|
|
1973
|
+
// keyed by bare plugin name now (a no-op collapse), but rows persisted by
|
|
1974
|
+
// the pre-slice registry were keyed name@version — keep the dedup so a
|
|
1975
|
+
// mixed store never surfaces duplicates (mirrors Platform_UIFragments_Lambda.res).
|
|
1976
|
+
let latestByName = Dict.make()
|
|
1977
|
+
items->Array.forEach(item =>
|
|
1978
|
+
switch item->S.parseOrThrow(~to=ReventlessCore.UiFragments.stateSchema) {
|
|
1979
|
+
| state =>
|
|
1980
|
+
let name = ReventlessCore.Plugin.name(state.pluginId)
|
|
1981
|
+
let version = ReventlessCore.Plugin.version(state.pluginId)
|
|
1982
|
+
switch latestByName->Dict.get(name) {
|
|
1983
|
+
| Some((prevVersion, _)) =>
|
|
1984
|
+
if ReventlessCore.Plugin.compareVersions(version, prevVersion) > 0 {
|
|
1985
|
+
latestByName->Dict.set(name, (version, state))
|
|
1949
1986
|
}
|
|
1950
|
-
|
|
|
1987
|
+
| None => latestByName->Dict.set(name, (version, state))
|
|
1951
1988
|
}
|
|
1952
|
-
)
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1989
|
+
| exception _ => ()
|
|
1990
|
+
}
|
|
1991
|
+
)
|
|
1992
|
+
latestByName
|
|
1993
|
+
->Dict.valuesToArray
|
|
1994
|
+
->Array.map(((_, state)) =>
|
|
1995
|
+
ReventlessCore.Platform_UIFragmentsApi.encodeUIFragmentEntry(state)
|
|
1996
|
+
)
|
|
1997
|
+
->JSON.Encode.array
|
|
1998
|
+
})
|
|
1961
1999
|
// Composite-key / index admin query fields. Live loads from the seeded QueryDb stores.
|
|
1962
2000
|
registerAdminItemsAndIndexResolvers(~queryResolvers, ~live=true)
|
|
1963
2001
|
|
|
@@ -1982,7 +2020,9 @@ module MakeWithConfig = (
|
|
|
1982
2020
|
switch Bus.getQueryDbScan(pluginQueryDbName) {
|
|
1983
2021
|
| Some(scanAll) =>
|
|
1984
2022
|
scanAll()->Array.findMap(json =>
|
|
1985
|
-
switch json->Reventless.Util_Sury.fromJson(
|
|
2023
|
+
switch json->Reventless.Util_Sury.fromJson(
|
|
2024
|
+
ReventlessCore.PluginsReadModelSpec.stateSchema,
|
|
2025
|
+
) {
|
|
1986
2026
|
| state if state.name == pluginName => Some(state.version)
|
|
1987
2027
|
| _ => None
|
|
1988
2028
|
| exception _ => None
|
|
@@ -2013,9 +2053,16 @@ module MakeWithConfig = (
|
|
|
2013
2053
|
}
|
|
2014
2054
|
switch version {
|
|
2015
2055
|
| Some(version) =>
|
|
2016
|
-
log.info(
|
|
2056
|
+
log.info(
|
|
2057
|
+
~comp=ReventlessCore.Platform_Admin_Structure.pluginId,
|
|
2058
|
+
`${field}(${pluginName}@${version}): dispatching to aggregate`,
|
|
2059
|
+
)
|
|
2017
2060
|
let _ = dispatchPluginCommand(~pluginName, ~command=makeCommand(version))
|
|
2018
|
-
| None =>
|
|
2061
|
+
| None =>
|
|
2062
|
+
log.warn(
|
|
2063
|
+
~comp=ReventlessCore.Platform_Admin_Structure.pluginId,
|
|
2064
|
+
`${field}(${pluginName}): plugin not found`,
|
|
2065
|
+
)
|
|
2019
2066
|
}
|
|
2020
2067
|
commandAccepted(~msgId, ~entityId=pluginName)
|
|
2021
2068
|
}
|
|
@@ -2024,13 +2071,19 @@ module MakeWithConfig = (
|
|
|
2024
2071
|
let retireField = ReventlessCore.Api_Naming.adminField(~name="Plugin_Retire")
|
|
2025
2072
|
let mutationResolvers = Dict.make()
|
|
2026
2073
|
mutationResolvers->Dict.set(activateField, async (_root, args, _ctx): JSON.t =>
|
|
2027
|
-
dispatchLifecycle(~field=activateField, args, v => ReventlessCore.PluginSpec.Activate({
|
|
2074
|
+
dispatchLifecycle(~field=activateField, args, v => ReventlessCore.PluginSpec.Activate({
|
|
2075
|
+
version: v,
|
|
2076
|
+
}))
|
|
2028
2077
|
)
|
|
2029
2078
|
mutationResolvers->Dict.set(deactivateField, async (_root, args, _ctx): JSON.t =>
|
|
2030
|
-
dispatchLifecycle(~field=deactivateField, args, v => ReventlessCore.PluginSpec.Deactivate({
|
|
2079
|
+
dispatchLifecycle(~field=deactivateField, args, v => ReventlessCore.PluginSpec.Deactivate({
|
|
2080
|
+
version: v,
|
|
2081
|
+
}))
|
|
2031
2082
|
)
|
|
2032
2083
|
mutationResolvers->Dict.set(retireField, async (_root, args, _ctx): JSON.t =>
|
|
2033
|
-
dispatchLifecycle(~field=retireField, args, v => ReventlessCore.PluginSpec.Retire({
|
|
2084
|
+
dispatchLifecycle(~field=retireField, args, v => ReventlessCore.PluginSpec.Retire({
|
|
2085
|
+
version: v,
|
|
2086
|
+
}))
|
|
2034
2087
|
)
|
|
2035
2088
|
// Remaining admin mutations (e.g., Clone) are no-ops in-memory.
|
|
2036
2089
|
adminMutationFieldNames->Array.forEach(field =>
|
|
@@ -2050,7 +2103,7 @@ module MakeWithConfig = (
|
|
|
2050
2103
|
("pluginId", JSON.Encode.string(ReventlessCore.Plugin.name(pluginId))),
|
|
2051
2104
|
("changeKind", JSON.Encode.string(changeKind)),
|
|
2052
2105
|
("manifest", manifest),
|
|
2053
|
-
])
|
|
2106
|
+
]),
|
|
2054
2107
|
)
|
|
2055
2108
|
let addUIFragmentMutation = (fieldName, changeKind) =>
|
|
2056
2109
|
mutationResolvers->Dict.set(fieldName, async (_root, args, _ctx): JSON.t => {
|
|
@@ -2169,6 +2222,7 @@ module MakeWithConfig = (
|
|
|
2169
2222
|
// Geocoding client door (D9 half 2) on the domain server — same mirror. A dev
|
|
2170
2223
|
// stub (no real geocoder locally), so the map picker's search box works offline.
|
|
2171
2224
|
LocalGeocodeResolvers.register(DomainGraphQL_Server.asInterface)
|
|
2225
|
+
|
|
2172
2226
|
// In split mode, inject Relay base types (Node interface, PageInfo) into the platform
|
|
2173
2227
|
// server so SDL fragments compile. The node(id) query is Domain-only — the Platform
|
|
2174
2228
|
// API is consumed by admin tools and agents, not Relay clients.
|
|
@@ -2187,7 +2241,6 @@ module MakeWithConfig = (
|
|
|
2187
2241
|
DomainGraphQL_Server.start()
|
|
2188
2242
|
DomainMCP_Server.start()
|
|
2189
2243
|
}
|
|
2190
|
-
|
|
2191
2244
|
}
|
|
2192
2245
|
|
|
2193
2246
|
let deployPlatform = (
|
|
@@ -2241,9 +2294,8 @@ module MakeWithConfig = (
|
|
|
2241
2294
|
// Platform_UIFragments — empty in the platform-only path (no plugins
|
|
2242
2295
|
// connected → no UI fragments registered). The SDL declares it as a
|
|
2243
2296
|
// non-null array so we must register a resolver returning [].
|
|
2244
|
-
queryResolvers->Dict.set(
|
|
2245
|
-
|
|
2246
|
-
async (_root, _args, _ctx): JSON.t => JSON.Encode.array([]),
|
|
2297
|
+
queryResolvers->Dict.set("Platform_UIFragments", async (_root, _args, _ctx): JSON.t =>
|
|
2298
|
+
JSON.Encode.array([])
|
|
2247
2299
|
)
|
|
2248
2300
|
// Composite-key / index admin query fields — empty stubs in the platform-only
|
|
2249
2301
|
// path (no plugins connected → no QueryDb seeded).
|
|
@@ -2251,7 +2303,9 @@ module MakeWithConfig = (
|
|
|
2251
2303
|
adminGraphQL.registerQueries(~sdlFields=baseParts.queries, ~resolvers=queryResolvers)
|
|
2252
2304
|
|
|
2253
2305
|
let mutationResolvers = Dict.make()
|
|
2254
|
-
let adminMutationEntries = ReventlessCore.Platform_AdminApi.mutationEntries(
|
|
2306
|
+
let adminMutationEntries = ReventlessCore.Platform_AdminApi.mutationEntries(
|
|
2307
|
+
~cloner=Config.cloner,
|
|
2308
|
+
)
|
|
2255
2309
|
let adminMutationFieldNames = adminMutationEntries->Array.flatMap(entry => entry.fieldNames)
|
|
2256
2310
|
adminMutationFieldNames->Array.forEach(field =>
|
|
2257
2311
|
mutationResolvers->Dict.set(field, async (_root, _args, _ctx): JSON.t =>
|
|
@@ -2288,17 +2342,17 @@ module MakeWithConfig = (
|
|
|
2288
2342
|
"Deregistered",
|
|
2289
2343
|
)
|
|
2290
2344
|
CommandGeneratorResolvers_GraphQL.ensureCommandResultTypes(adminGraphQL)
|
|
2291
|
-
adminGraphQL.registerMutations(
|
|
2345
|
+
adminGraphQL.registerMutations(
|
|
2346
|
+
~sdlFields=adminMutationSdl(baseParts.mutations),
|
|
2347
|
+
~resolvers=mutationResolvers,
|
|
2348
|
+
)
|
|
2292
2349
|
adminGraphQL.registerSubscriptions(
|
|
2293
2350
|
~sdlFields=[
|
|
2294
2351
|
" onUIFragmentChange: UIFragmentChangeEvent",
|
|
2295
2352
|
" onPluginStatusChange: PluginStatusChangeEvent",
|
|
2296
2353
|
],
|
|
2297
2354
|
~resolvers=Dict.fromArray([
|
|
2298
|
-
(
|
|
2299
|
-
"onUIFragmentChange",
|
|
2300
|
-
LocalGraphQL_SubscriptionResolvers.makeFieldResolver(dpSubTopic),
|
|
2301
|
-
),
|
|
2355
|
+
("onUIFragmentChange", LocalGraphQL_SubscriptionResolvers.makeFieldResolver(dpSubTopic)),
|
|
2302
2356
|
(
|
|
2303
2357
|
"onPluginStatusChange",
|
|
2304
2358
|
LocalGraphQL_SubscriptionResolvers.makeFieldResolver("onPluginStatusChange"),
|
|
@@ -2317,11 +2371,22 @@ module MakeWithConfig = (
|
|
|
2317
2371
|
// server's `asInterface.start` accepts but ignores `~contextFactory`
|
|
2318
2372
|
// because it always wires its own internal auth context.
|
|
2319
2373
|
adminGraphQL.start(
|
|
2320
|
-
~port=if Config.splitApi {
|
|
2374
|
+
~port=if Config.splitApi {
|
|
2375
|
+
4001
|
|
2376
|
+
} else {
|
|
2377
|
+
4000
|
|
2378
|
+
},
|
|
2321
2379
|
~contextFactory=Auth_GraphqlContext.buildAuthContext,
|
|
2322
2380
|
(),
|
|
2323
2381
|
)
|
|
2324
|
-
adminMCP.start(
|
|
2382
|
+
adminMCP.start(
|
|
2383
|
+
~port=if Config.splitApi {
|
|
2384
|
+
3002
|
|
2385
|
+
} else {
|
|
2386
|
+
3001
|
|
2387
|
+
},
|
|
2388
|
+
(),
|
|
2389
|
+
)
|
|
2325
2390
|
if Config.splitApi {
|
|
2326
2391
|
DomainGraphQL_Server.start()
|
|
2327
2392
|
DomainMCP_Server.start()
|
|
@@ -2346,11 +2411,18 @@ module MakeWithConfig = (
|
|
|
2346
2411
|
}
|
|
2347
2412
|
|
|
2348
2413
|
let deployPlugin = (~plugin: module(PluginMaker), ~apiTarget=Domain) => {
|
|
2349
|
-
log.info(
|
|
2414
|
+
log.info(
|
|
2415
|
+
~comp="Platform",
|
|
2416
|
+
`deployPlugin target=${switch apiTarget {
|
|
2417
|
+
| Domain => "Domain"
|
|
2418
|
+
| Platform => "Platform"
|
|
2419
|
+
}}`,
|
|
2420
|
+
)
|
|
2350
2421
|
|
|
2351
2422
|
// Set the active deploy target so resolveTargetGraphQL/MCP() and QueryDb serverRef/relayRef
|
|
2352
2423
|
// route registrations to the correct server. Mirrors AWS resolveTargetApi() pattern.
|
|
2353
2424
|
currentDeployTarget.contents = apiTarget
|
|
2425
|
+
|
|
2354
2426
|
// Update QueryDb server/relay refs on the shared StateViewSliceMaker.QueryDbResolvers instance.
|
|
2355
2427
|
// Do NOT create a new QueryDbResolvers_GraphQL.Make(Bus) here — that would be a different
|
|
2356
2428
|
// module instance from the one StateViewSlice_Builder captured, leaving its refs unchanged.
|
|
@@ -2417,46 +2489,46 @@ module MakeWithConfig = (
|
|
|
2417
2489
|
|
|
2418
2490
|
let queryResolvers = Dict.make()
|
|
2419
2491
|
let adminQueryEntry = ReventlessCore.PluginBaseFragment.queryEntries->Array.getUnsafe(0)
|
|
2420
|
-
queryResolvers->Dict.set(adminQueryEntry.singleFieldName, async (
|
|
2421
|
-
|
|
2422
|
-
|
|
2492
|
+
queryResolvers->Dict.set(adminQueryEntry.singleFieldName, async (
|
|
2493
|
+
_root,
|
|
2494
|
+
_args,
|
|
2495
|
+
_ctx,
|
|
2496
|
+
): JSON.t => JSON.Encode.null)
|
|
2423
2497
|
queryResolvers->Dict.set(adminQueryEntry.listFieldName, async (_root, _args, _ctx): JSON.t =>
|
|
2424
2498
|
connectionResponse([])
|
|
2425
2499
|
)
|
|
2426
2500
|
// Platform_UIFragments — single-plugin path. Populated by the
|
|
2427
2501
|
// UiFragments StateViewSlice when the plugin registers a manifest through
|
|
2428
2502
|
// the admin EP (connectPlugin below), otherwise scans an empty store.
|
|
2429
|
-
queryResolvers->Dict.set(
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2503
|
+
queryResolvers->Dict.set("Platform_UIFragments", async (_root, _args, _ctx): JSON.t => {
|
|
2504
|
+
let items = switch Bus.getQueryDbScan(ReventlessCore.UiFragments.name) {
|
|
2505
|
+
| Some(scanAll) => scanAll()
|
|
2506
|
+
| None => []
|
|
2507
|
+
}
|
|
2508
|
+
items
|
|
2509
|
+
->Array.filterMap(item =>
|
|
2510
|
+
switch item->S.parseOrThrow(~to=ReventlessCore.UiFragments.stateSchema) {
|
|
2511
|
+
| state => Some(ReventlessCore.Platform_UIFragmentsApi.encodeUIFragmentEntry(state))
|
|
2512
|
+
| exception _ => None
|
|
2435
2513
|
}
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
| state =>
|
|
2440
|
-
Some(ReventlessCore.Platform_UIFragmentsApi.encodeUIFragmentEntry(state))
|
|
2441
|
-
| exception _ => None
|
|
2442
|
-
}
|
|
2443
|
-
)
|
|
2444
|
-
->JSON.Encode.array
|
|
2445
|
-
},
|
|
2446
|
-
)
|
|
2514
|
+
)
|
|
2515
|
+
->JSON.Encode.array
|
|
2516
|
+
})
|
|
2447
2517
|
// Platform_ComponentDefinitions resolver — SDL is already stitched into baseParts via
|
|
2448
2518
|
// Platform_AdminApi.baseFragment so we register only the resolver here. Uses the shared
|
|
2449
2519
|
// encoder so the dynamic-plugin admin server emits the same canonical shape as
|
|
2450
2520
|
// the main platform server (and as AWS).
|
|
2451
|
-
queryResolvers->Dict.set(
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2521
|
+
queryResolvers->Dict.set("Platform_ComponentDefinitions", async (
|
|
2522
|
+
_root,
|
|
2523
|
+
_args,
|
|
2524
|
+
_ctx,
|
|
2525
|
+
): JSON.t =>
|
|
2526
|
+
pluginStructuresStore.contents
|
|
2527
|
+
->Dict.toArray
|
|
2528
|
+
->Array.map(((pluginId, def)) =>
|
|
2529
|
+
ReventlessCore.Platform_ComponentDefinitionsApi.encodePluginStructureEntry(~pluginId, def)
|
|
2530
|
+
)
|
|
2531
|
+
->JSON.Encode.array
|
|
2460
2532
|
)
|
|
2461
2533
|
// Composite-key / index admin query fields.
|
|
2462
2534
|
// Live loads from the seeded QueryDb stores (empty when not yet seeded).
|
|
@@ -2464,7 +2536,9 @@ module MakeWithConfig = (
|
|
|
2464
2536
|
adminGraphQL.registerQueries(~sdlFields=baseParts.queries, ~resolvers=queryResolvers)
|
|
2465
2537
|
|
|
2466
2538
|
let mutationResolvers = Dict.make()
|
|
2467
|
-
let adminMutationEntries = ReventlessCore.Platform_AdminApi.mutationEntries(
|
|
2539
|
+
let adminMutationEntries = ReventlessCore.Platform_AdminApi.mutationEntries(
|
|
2540
|
+
~cloner=Config.cloner,
|
|
2541
|
+
)
|
|
2468
2542
|
let adminMutationFieldNames = adminMutationEntries->Array.flatMap(entry => entry.fieldNames)
|
|
2469
2543
|
adminMutationFieldNames->Array.forEach(field =>
|
|
2470
2544
|
mutationResolvers->Dict.set(field, async (_root, _args, _ctx): JSON.t =>
|
|
@@ -2501,14 +2575,14 @@ module MakeWithConfig = (
|
|
|
2501
2575
|
"Deregistered",
|
|
2502
2576
|
)
|
|
2503
2577
|
CommandGeneratorResolvers_GraphQL.ensureCommandResultTypes(adminGraphQL)
|
|
2504
|
-
adminGraphQL.registerMutations(
|
|
2578
|
+
adminGraphQL.registerMutations(
|
|
2579
|
+
~sdlFields=adminMutationSdl(baseParts.mutations),
|
|
2580
|
+
~resolvers=mutationResolvers,
|
|
2581
|
+
)
|
|
2505
2582
|
adminGraphQL.registerSubscriptions(
|
|
2506
2583
|
~sdlFields=[" onUIFragmentChange: UIFragmentChangeEvent"],
|
|
2507
2584
|
~resolvers=Dict.fromArray([
|
|
2508
|
-
(
|
|
2509
|
-
"onUIFragmentChange",
|
|
2510
|
-
LocalGraphQL_SubscriptionResolvers.makeFieldResolver(dpSubTopic2),
|
|
2511
|
-
),
|
|
2585
|
+
("onUIFragmentChange", LocalGraphQL_SubscriptionResolvers.makeFieldResolver(dpSubTopic2)),
|
|
2512
2586
|
]),
|
|
2513
2587
|
)
|
|
2514
2588
|
adminRegisteredServers.contents->Array.push(adminGraphQL)
|
|
@@ -2527,7 +2601,9 @@ module MakeWithConfig = (
|
|
|
2527
2601
|
seedPluginStructuresStore(~pluginComponents=[pluginComponent])
|
|
2528
2602
|
|
|
2529
2603
|
// Fire onPluginDeployed hooks so subscribers learn about this plugin.
|
|
2530
|
-
let deployedPluginOutputs = [
|
|
2604
|
+
let deployedPluginOutputs = [
|
|
2605
|
+
(ReventlessCore.Component.outputs(pluginComponent): ReventlessInfra.Plugin.outputs),
|
|
2606
|
+
]
|
|
2531
2607
|
firePluginDeployedHooks(~builtInfos=builtInfos.contents, ~pluginOutputs=deployedPluginOutputs)
|
|
2532
2608
|
|
|
2533
2609
|
// Late-deployed plugins may register an onPlatformDeployed hook that missed
|