@reventlessdev/reventless-aws 3.0.0-alpha.197 → 3.0.0-alpha.199
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 +22 -0
- package/package.json +7 -7
- package/src/Platform.res +294 -44
- package/src/Platform.res.mjs +420 -94
- package/src/adapter/Api/ApiFragmentDeregistration.res +137 -0
- package/src/adapter/Api/ApiFragmentDeregistration.res.mjs +108 -0
- package/src/adapter/Api/CommandSubscriptionResolvers_AppSync.res +4 -3
- package/src/adapter/Api/Platform_ApiFragments_Lambda.res +222 -0
- package/src/adapter/Api/Platform_ApiFragments_Lambda.res.mjs +202 -0
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +27 -17
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +13 -13
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res +2 -1
- package/src/adapter/QueryDb/QueryDbBackend.res +1 -1
- package/src/adapter/Runtime/AdminEventCollectorEntryPoint.mjs +235 -9
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +4 -0
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +5 -0
- package/src/components/Api/AppSync_Adapter.res +36 -59
- package/src/components/Api/AppSync_Adapter.res.mjs +18 -74
- package/src/components/Api/AppSync_SdlDecorate.res +189 -0
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +125 -0
- package/src/plugin/runtime/PluginRuntime_Builder.res +107 -2
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +70 -7
- package/src/util/Util_AppSync_Caller.res +12 -4
- package/src/util/Util_AppSync_Caller.res.mjs +9 -3
- package/tests/AppSync_AdapterTest.res +6 -0
- package/tests/AppSync_AdapterTest.res.mjs +12 -6
- package/tests/AppSync_SdlDecorateTest.res +157 -0
- package/tests/AppSync_SdlDecorateTest.res.mjs +147 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.199 (2026-07-12)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **deploy:** gate admin status-query resolvers on the schema push ([2634694](https://github.com/ReventlessDev/reventless-core/commit/2634694b3a122bc1db44d129769f5442e124d9b7))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.198 (2026-07-12)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **admin:** event-source the API fragment registry as admin DCB slices (Phase 2b) ([3fc40d2](https://github.com/ReventlessDev/reventless-core/commit/3fc40d247c54bf66ed84d625959f08299aa04a98))
|
|
18
|
+
* **admin:** event-source the UI fragment registry as admin DCB slices ([711581e](https://github.com/ReventlessDev/reventless-core/commit/711581e77c626e7d0fc35db8ec351f62a70bd8f2))
|
|
19
|
+
* **admin:** expose the API fragment registry as system-callable mutations (Phase 2d) ([8b1ec49](https://github.com/ReventlessDev/reventless-core/commit/8b1ec49813e67f303bc86d60470c9b95d2a8408e))
|
|
20
|
+
* **admin:** react to ApiFragment* events with a schema single writer (Phase 2e) ([59f9e46](https://github.com/ReventlessDev/reventless-core/commit/59f9e46c154d8ca2a34537974722014fc279de3e))
|
|
21
|
+
* **admin:** retire the Plugin-aggregate UI-fragment path in favour of the registry slices ([1dbc708](https://github.com/ReventlessDev/reventless-core/commit/1dbc708e7439b34ff970cc3d963d7835a8c6fd48))
|
|
22
|
+
* **admin:** serve Platform_UIFragments from the new UiFragmentRegistry slices ([7a47533](https://github.com/ReventlessDev/reventless-core/commit/7a47533acc95977fbeeb6375e94449056b655abc))
|
|
23
|
+
* **api:** neutralize core-emitted SDL — [@aws](https://github.com/aws)_subscribe moves to the AWS adapter (Phase 2a) ([9b27ec8](https://github.com/ReventlessDev/reventless-core/commit/9b27ec8f6ab2b7453b1c50b9891e89781ec3b31a))
|
|
24
|
+
* **deploy:** deregister API fragments on plugin-stack destroy (Phase 3 tail) ([4a10614](https://github.com/ReventlessDev/reventless-core/commit/4a10614e785359ca4c8021da9813c3d485abe9ca))
|
|
25
|
+
* **deploy:** register API fragments via the Platform API on staged deploys (Phase 3) ([7ddb045](https://github.com/ReventlessDev/reventless-core/commit/7ddb045392380497494a0109712e7a10008f9207))
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
# 3.0.0-alpha.197 (2026-07-11)
|
|
7
29
|
|
|
8
30
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.199",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -8,17 +8,17 @@
|
|
|
8
8
|
"@aws-sdk/client-cloudfront": "3.970.0",
|
|
9
9
|
"sury": "11.0.0-alpha.4",
|
|
10
10
|
"uuid": "^13.0.0",
|
|
11
|
-
"@reventlessdev/rescript-
|
|
11
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.7",
|
|
12
12
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.27",
|
|
13
|
+
"@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.21",
|
|
13
14
|
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.50",
|
|
14
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.7",
|
|
15
15
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.15",
|
|
16
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.158",
|
|
16
17
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.15",
|
|
17
|
-
"@reventlessdev/reventless-
|
|
18
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.97",
|
|
18
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.98",
|
|
19
19
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.26",
|
|
20
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
21
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.
|
|
20
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.76",
|
|
21
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.22"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"rescript": "^12.3.0",
|
package/src/Platform.res
CHANGED
|
@@ -109,7 +109,13 @@ module MakeWithConfig = (
|
|
|
109
109
|
subnetIds: pg.subnetIds,
|
|
110
110
|
})
|
|
111
111
|
QueryDbBackend.exempt(ReventlessCore.PluginsReadModelSpec.name)
|
|
112
|
-
|
|
112
|
+
// The UiFragments StateViewSlice table is scanned by the Platform_UIFragments
|
|
113
|
+
// Lambda (DynamoDB scan) — keep it off the Postgres selection like the other
|
|
114
|
+
// admin stores.
|
|
115
|
+
QueryDbBackend.exempt(ReventlessCore.UiFragments.name)
|
|
116
|
+
// The ApiFragments StateViewSlice is the durable source of the schema-push
|
|
117
|
+
// automation and the runtime re-stitcher — admin store, off Postgres too.
|
|
118
|
+
QueryDbBackend.exempt(ReventlessCore.ApiFragments.name)
|
|
113
119
|
})
|
|
114
120
|
type api = Types.AppSync.api
|
|
115
121
|
type role = Types.AppSync.role
|
|
@@ -567,6 +573,27 @@ module MakeWithConfig = (
|
|
|
567
573
|
}
|
|
568
574
|
module InboundTranslationSlice = InboundTranslationSlice_Builder.Make(ApiConfig)
|
|
569
575
|
|
|
576
|
+
// Admin UI-fragment registry slices (docs/plans/event-sourced-fragment-registries.md): the
|
|
577
|
+
// platform UI-fragment registry hosted as admin DCB slices sharing the admin DcbEventLog.
|
|
578
|
+
// Built once at platform-module level and passed into Admin.construct below (registers the
|
|
579
|
+
// slice's command handler exactly once).
|
|
580
|
+
module UiFragmentRegistrySlice = StateChangeSlice.Make(
|
|
581
|
+
ReventlessCore.UiFragmentRegistry,
|
|
582
|
+
ReventlessCore.UiFragmentRegistry_Behavior,
|
|
583
|
+
)
|
|
584
|
+
module UiFragmentsViewSlice = StateViewSlice.Make(
|
|
585
|
+
ReventlessCore.UiFragments,
|
|
586
|
+
ReventlessCore.UiFragments_Projection,
|
|
587
|
+
)
|
|
588
|
+
module ApiFragmentRegistrySlice = StateChangeSlice.Make(
|
|
589
|
+
ReventlessCore.ApiFragmentRegistry,
|
|
590
|
+
ReventlessCore.ApiFragmentRegistry_Behavior,
|
|
591
|
+
)
|
|
592
|
+
module ApiFragmentsViewSlice = StateViewSlice.Make(
|
|
593
|
+
ReventlessCore.ApiFragments,
|
|
594
|
+
ReventlessCore.ApiFragments_Projection,
|
|
595
|
+
)
|
|
596
|
+
|
|
570
597
|
// Empty base fragment — no types, no mutations, no queries.
|
|
571
598
|
// Used by the plugin Api in split mode so plugin schema has no core fields.
|
|
572
599
|
let emptyBaseFragment = ReventlessCore.GraphQL_Stitcher.encode({
|
|
@@ -574,6 +601,7 @@ module MakeWithConfig = (
|
|
|
574
601
|
mutations: [],
|
|
575
602
|
queries: [],
|
|
576
603
|
subscriptions: [],
|
|
604
|
+
subscriptionSources: [],
|
|
577
605
|
})
|
|
578
606
|
|
|
579
607
|
module Api = {
|
|
@@ -639,6 +667,11 @@ module MakeWithConfig = (
|
|
|
639
667
|
// targetApi that was set before plugin.make() and is never reset.
|
|
640
668
|
let hooksApiRef: ref<option<ReventlessCore.Plugin_Helpers.hookedValue<unknown>>> = ref(None)
|
|
641
669
|
let hooksApiRoleRef: ref<option<ReventlessCore.Plugin_Helpers.hookedValue<unknown>>> = ref(None)
|
|
670
|
+
// Admin DCB mutation resolvers bind here (the Platform API in split mode). Set by
|
|
671
|
+
// makePlatform / deployPlatform once the Platform API resource exists. Distinct from hooksApiRef
|
|
672
|
+
// because both are read in *deferred* callbacks — admin resolvers need the Platform API while the
|
|
673
|
+
// plugins built afterwards read hooksApiRef (Domain/deploy-target). See platformHooks.adminApi.
|
|
674
|
+
let hooksAdminApiRef: ref<option<ReventlessCore.Plugin_Helpers.hookedValue<unknown>>> = ref(None)
|
|
642
675
|
|
|
643
676
|
let resolveHookedApi = (): Types.AppSync.api =>
|
|
644
677
|
switch hooksApiRef.contents {
|
|
@@ -646,6 +679,160 @@ module MakeWithConfig = (
|
|
|
646
679
|
| None => resolveTargetApi()
|
|
647
680
|
}
|
|
648
681
|
|
|
682
|
+
// Admin DCB resolvers resolve to the Platform API when it has been recorded; otherwise fall back
|
|
683
|
+
// to the same api plugins use (unified mode, or before the split Platform API exists).
|
|
684
|
+
let resolveAdminHookedApi = (): Types.AppSync.api =>
|
|
685
|
+
switch hooksAdminApiRef.contents {
|
|
686
|
+
| Some({val}) => Obj.magic(val)
|
|
687
|
+
| None => resolveHookedApi()
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// ── Phase 3: staged-deploy API-fragment registration ────────────────────────
|
|
691
|
+
// When a plugin deploys against an ALREADY-RUNNING platform (plugin-stack mode),
|
|
692
|
+
// it registers its API-schema fragment with the platform via the Platform API
|
|
693
|
+
// (SigV4 system caller) instead of writing deploy-schema:* rows and pushing the
|
|
694
|
+
// schema itself. The platform-side reactive single writer (Plan 2e) stitches and
|
|
695
|
+
// pushes; this waits on the push status so resolver creation (gated on the
|
|
696
|
+
// returned Output) proceeds only once the plugin's fields are ACTIVE — or fails
|
|
697
|
+
// with the stitch error. Used only in plugin-stack mode; makePlatform (all-at-once)
|
|
698
|
+
// keeps the direct deploy-time push (the reactive writer is dormant there).
|
|
699
|
+
@val external registerWaiterSetTimeout: (unit => unit, int) => unit = "setTimeout"
|
|
700
|
+
let deployWaiterDelay = (ms: int): promise<unit> =>
|
|
701
|
+
Promise.make((resolve, _) => registerWaiterSetTimeout(() => resolve(), ms))
|
|
702
|
+
|
|
703
|
+
// Poll the platform's Platform_ApiFragments status query until the plugin's row
|
|
704
|
+
// shows a push that landed at/after our registration time (`sinceAt`) — a fresh
|
|
705
|
+
// "ok" means our fields are live; a fresh "error" surfaces the stitch failure. An
|
|
706
|
+
// older "ok" is a stale prior deploy's and is ignored (ISO strings sort lexically).
|
|
707
|
+
let waitForApiFragmentPush = async (~endpoint, ~region, ~pluginId, ~sinceAt) => {
|
|
708
|
+
let query = "query { Platform_ApiFragments { pluginId pushStatus pushMessage pushedAt } }"
|
|
709
|
+
let maxAttempts = 90 // ~3 min at 2s intervals
|
|
710
|
+
let rec loop = async attempt =>
|
|
711
|
+
if attempt >= maxAttempts {
|
|
712
|
+
JsError.throwWithMessage(
|
|
713
|
+
`Timed out (>3min) waiting for the platform to push ${pluginId}'s API fragment — check the AdminEventCollector Lambda logs.`,
|
|
714
|
+
)
|
|
715
|
+
} else {
|
|
716
|
+
let data = await Util_AppSync_Caller.sendQuery(~endpoint, ~region, ~queryString=query)
|
|
717
|
+
let row =
|
|
718
|
+
data
|
|
719
|
+
->Option.flatMap(JSON.Decode.object)
|
|
720
|
+
->Option.flatMap(d => d->Dict.get("Platform_ApiFragments"))
|
|
721
|
+
->Option.flatMap(JSON.Decode.array)
|
|
722
|
+
->Option.getOr([])
|
|
723
|
+
->Array.find(entry =>
|
|
724
|
+
entry
|
|
725
|
+
->JSON.Decode.object
|
|
726
|
+
->Option.flatMap(e => e->Dict.get("pluginId"))
|
|
727
|
+
->Option.flatMap(JSON.Decode.string) == Some(pluginId)
|
|
728
|
+
)
|
|
729
|
+
->Option.flatMap(JSON.Decode.object)
|
|
730
|
+
switch row {
|
|
731
|
+
| Some(e) =>
|
|
732
|
+
let field = k => e->Dict.get(k)->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
733
|
+
let status = field("pushStatus")
|
|
734
|
+
let fresh = field("pushedAt") >= sinceAt
|
|
735
|
+
if fresh && status == "ok" {
|
|
736
|
+
log.info(~comp="registerFragmentViaApi", `${pluginId} schema push confirmed ACTIVE`)
|
|
737
|
+
} else if fresh && status == "error" {
|
|
738
|
+
JsError.throwWithMessage(`Schema push failed for ${pluginId}: ${field("pushMessage")}`)
|
|
739
|
+
} else {
|
|
740
|
+
await deployWaiterDelay(2000)
|
|
741
|
+
await loop(attempt + 1)
|
|
742
|
+
}
|
|
743
|
+
| None =>
|
|
744
|
+
await deployWaiterDelay(2000)
|
|
745
|
+
await loop(attempt + 1)
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
await loop(0)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
let registerFragmentViaApi = (
|
|
752
|
+
~name: string,
|
|
753
|
+
~fragment: Reventless.Plugin.apiSchemaFragment,
|
|
754
|
+
~apiTargetName: string,
|
|
755
|
+
): Pulumi.Output.t<unit> => {
|
|
756
|
+
// Platform_RegisterApiFragment is an admin systemCallable mutation on the Platform
|
|
757
|
+
// API (regardless of the plugin's apiTarget, which is passed as the enum argument).
|
|
758
|
+
let platformApi = switch apiConfigRef.contents {
|
|
759
|
+
| Some({platformApi}) => platformApi
|
|
760
|
+
| None => domainApi
|
|
761
|
+
}
|
|
762
|
+
let region =
|
|
763
|
+
Pulumi.Config.make(Some("aws"))->Pulumi.Config.get("region")->Option.getOr("unknown")
|
|
764
|
+
let endpointOutput =
|
|
765
|
+
platformApi
|
|
766
|
+
->Pulumi.Output.flatMap(api => api.uris)
|
|
767
|
+
->Pulumi.Output.apply(uris => uris.graphQL)
|
|
768
|
+
// Deregister-on-destroy: a dynamic resource whose `delete` handler sends
|
|
769
|
+
// Platform_DeregisterApiFragment when the plugin stack is destroyed (final
|
|
770
|
+
// retirement). It never replaces on a version bump, so supersession does NOT
|
|
771
|
+
// deregister — only a genuine `pulumi destroy` removes the fields.
|
|
772
|
+
let _ = ApiFragmentDeregistration.make(
|
|
773
|
+
~name=`${name}ApiFragmentRegistration`,
|
|
774
|
+
~pluginId=name,
|
|
775
|
+
~endpoint=endpointOutput->Pulumi.Output.asInput,
|
|
776
|
+
~region,
|
|
777
|
+
)
|
|
778
|
+
endpointOutput
|
|
779
|
+
->Pulumi.Output.flatMap(endpoint => {
|
|
780
|
+
let run = async () => {
|
|
781
|
+
let at = Date.make()->Date.toISOString
|
|
782
|
+
let variables = {
|
|
783
|
+
"id": name,
|
|
784
|
+
"pluginId": name,
|
|
785
|
+
"fragment": {"encoded": fragment.encoded, "protocol": fragment.protocol},
|
|
786
|
+
"apiTarget": Util_AppSync_Caller.graphqlEnum(apiTargetName),
|
|
787
|
+
"at": at,
|
|
788
|
+
}
|
|
789
|
+
let selection = "{ __typename ... on CommandAccepted { eventCount } ... on CommandRejected { errorCode errorDetail } }"
|
|
790
|
+
log.info(
|
|
791
|
+
~comp="registerFragmentViaApi",
|
|
792
|
+
`Registering API fragment for ${name} (${apiTargetName}) via ${endpoint}`,
|
|
793
|
+
)
|
|
794
|
+
let result = await Util_AppSync_Caller.sendMutation(
|
|
795
|
+
~endpoint,
|
|
796
|
+
~region,
|
|
797
|
+
~mutation="Platform_RegisterApiFragment",
|
|
798
|
+
~selection,
|
|
799
|
+
~variables,
|
|
800
|
+
)
|
|
801
|
+
let outcome =
|
|
802
|
+
result
|
|
803
|
+
->Option.flatMap(JSON.Decode.object)
|
|
804
|
+
->Option.flatMap(d => d->Dict.get("Platform_RegisterApiFragment"))
|
|
805
|
+
->Option.flatMap(JSON.Decode.object)
|
|
806
|
+
switch outcome {
|
|
807
|
+
| Some(cmd) =>
|
|
808
|
+
let typename =
|
|
809
|
+
cmd->Dict.get("__typename")->Option.flatMap(JSON.Decode.string)->Option.getOr("")
|
|
810
|
+
switch typename {
|
|
811
|
+
| "CommandRejected" =>
|
|
812
|
+
let code =
|
|
813
|
+
cmd->Dict.get("errorCode")->Option.flatMap(JSON.Decode.string)->Option.getOr("unknown")
|
|
814
|
+
JsError.throwWithMessage(`RegisterApiFragment rejected for ${name}: ${code}`)
|
|
815
|
+
| "CommandAccepted" =>
|
|
816
|
+
// eventCount 0 ⇒ idempotent no-op (unchanged fragment) — fields already live,
|
|
817
|
+
// proceed without waiting; >0 ⇒ a real change, wait for the reactive push.
|
|
818
|
+
let eventCount =
|
|
819
|
+
cmd->Dict.get("eventCount")->Option.flatMap(JSON.Decode.float)->Option.getOr(0.0)
|
|
820
|
+
if eventCount > 0.0 {
|
|
821
|
+
await waitForApiFragmentPush(~endpoint, ~region, ~pluginId=name, ~sinceAt=at)
|
|
822
|
+
} else {
|
|
823
|
+
log.info(~comp="registerFragmentViaApi", `${name} fragment unchanged — no push needed`)
|
|
824
|
+
}
|
|
825
|
+
| _ => await waitForApiFragmentPush(~endpoint, ~region, ~pluginId=name, ~sinceAt=at)
|
|
826
|
+
}
|
|
827
|
+
| None =>
|
|
828
|
+
// Unparseable result (mutation may have failed) — fall back to polling.
|
|
829
|
+
await waitForApiFragmentPush(~endpoint, ~region, ~pluginId=name, ~sinceAt=at)
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
run()->Pulumi.Output.fromPromise
|
|
833
|
+
})
|
|
834
|
+
}
|
|
835
|
+
|
|
649
836
|
let hooks: ReventlessCore.Plugin_Helpers.platformHooks = {
|
|
650
837
|
// AWS uses Interstack for admin extension points — leave ref at empty dict.
|
|
651
838
|
adminExtensionPoints: ref(Pulumi.Output.make(Dict.make())),
|
|
@@ -654,6 +841,7 @@ module MakeWithConfig = (
|
|
|
654
841
|
schedulerRoleUrn: ref(Pulumi.Output.make("")),
|
|
655
842
|
api: hooksApiRef,
|
|
656
843
|
apiRole: hooksApiRoleRef,
|
|
844
|
+
adminApi: hooksAdminApiRef,
|
|
657
845
|
deployTarget: ref("Domain"),
|
|
658
846
|
inboundAppSyncResolverHook: ({runtime, fieldNames, externalInputSchemas: _, opts}) => {
|
|
659
847
|
let runtimeTyped: ReventlessCore.Runtime.environment<Util.Lambda.runtimeParts> =
|
|
@@ -665,11 +853,12 @@ module MakeWithConfig = (
|
|
|
665
853
|
~opts,
|
|
666
854
|
)
|
|
667
855
|
},
|
|
668
|
-
dcbAppSyncResolverHook: ({runtime, fieldNames, tags, opts}) => {
|
|
856
|
+
dcbAppSyncResolverHook: ({runtime, fieldNames, tags, onAdminApi, opts}) => {
|
|
669
857
|
let runtimeTyped: ReventlessCore.Runtime.environment<Util.Lambda.runtimeParts> =
|
|
670
858
|
runtime->asLambdaRuntime
|
|
671
859
|
CommandGeneratorResolvers_AppSync.makeDcb(
|
|
672
|
-
|
|
860
|
+
// Admin slices bind on the Platform API (split mode); plugins on the deploy-target api.
|
|
861
|
+
~api=onAdminApi ? resolveAdminHookedApi() : resolveHookedApi(),
|
|
673
862
|
~runtime=runtimeTyped,
|
|
674
863
|
~fieldNames,
|
|
675
864
|
~tags,
|
|
@@ -714,11 +903,15 @@ module MakeWithConfig = (
|
|
|
714
903
|
let adminBaseFragment = AppSync_Adapter.injectAwsAuthAll(
|
|
715
904
|
ReventlessCore.AdminApi.baseFragment(~cloner=Config.cloner),
|
|
716
905
|
~group="Admin",
|
|
906
|
+
// The ApiFragmentRegistry register/deregister mutations + the Platform_ApiFragments
|
|
907
|
+
// status query are invoked by the plugin/standalone deploy as a SigV4 system caller, so
|
|
908
|
+
// they carry the dual-auth (@aws_cognito_user_pools @aws_iam) directive.
|
|
909
|
+
~iamFieldNames=ReventlessCore.AdminApi.systemCallerFieldNames,
|
|
717
910
|
)
|
|
718
|
-
let sdl =
|
|
911
|
+
let sdl = AppSync_Adapter.stitchWithAwsDirectives(
|
|
719
912
|
~baseFragment=adminBaseFragment,
|
|
720
913
|
~pluginFragments=[],
|
|
721
|
-
)
|
|
914
|
+
)
|
|
722
915
|
(targetApi, adminBarrier)
|
|
723
916
|
->Pulumi.Output.all2
|
|
724
917
|
->Pulumi.Output.flatMap(((api, _)) =>
|
|
@@ -756,6 +949,19 @@ module MakeWithConfig = (
|
|
|
756
949
|
// Domain after P.make() returns, before any Pulumi.Output async callbacks run.
|
|
757
950
|
let capturedDeployTarget = currentDeployTarget.contents
|
|
758
951
|
|
|
952
|
+
switch platformStackRef {
|
|
953
|
+
| Some(_) =>
|
|
954
|
+
// Phase 3 — staged deploy against a running platform (deployPlugin): register the
|
|
955
|
+
// fragment via the Platform API; the reactive single writer (2e) stitches + pushes.
|
|
956
|
+
let apiTargetName = switch capturedDeployTarget {
|
|
957
|
+
| Domain => "Domain"
|
|
958
|
+
| Platform => "Platform"
|
|
959
|
+
}
|
|
960
|
+
registerFragmentViaApi(~name, ~fragment=pluginFragment, ~apiTargetName)
|
|
961
|
+
| None =>
|
|
962
|
+
// All-at-once (makePlatform): platform + plugins deploy in one stack, so the
|
|
963
|
+
// reactive writer is dormant — keep the direct deploy-time stitch + push below.
|
|
964
|
+
|
|
759
965
|
// Select DynamoDB key prefix and target AppSync API based on the current deploy target.
|
|
760
966
|
// Domain plugins use "deploy-schema:" and the Domain API (default behaviour).
|
|
761
967
|
// Platform plugins use a separate "deploy-schema-platform:" namespace and the Core API,
|
|
@@ -933,6 +1139,7 @@ module MakeWithConfig = (
|
|
|
933
1139
|
AppSync_Adapter.injectAwsAuthAll(
|
|
934
1140
|
ReventlessCore.AdminApi.baseFragment(~cloner=Config.cloner),
|
|
935
1141
|
~group="Admin",
|
|
1142
|
+
~iamFieldNames=ReventlessCore.AdminApi.systemCallerFieldNames,
|
|
936
1143
|
)
|
|
937
1144
|
| Domain =>
|
|
938
1145
|
if Config.splitApi {
|
|
@@ -944,10 +1151,10 @@ module MakeWithConfig = (
|
|
|
944
1151
|
)
|
|
945
1152
|
}
|
|
946
1153
|
}
|
|
947
|
-
let sdl =
|
|
1154
|
+
let sdl = AppSync_Adapter.stitchWithAwsDirectives(
|
|
948
1155
|
~baseFragment,
|
|
949
1156
|
~pluginFragments=allPluginFragments,
|
|
950
|
-
)
|
|
1157
|
+
)
|
|
951
1158
|
let currentHash = AppSync_Adapter.sha256Hex(sdl)
|
|
952
1159
|
let storedHash = switch tableNameOpt {
|
|
953
1160
|
| Some(tn) => await readSchemaHash(~tableName=tn, ~apiId)
|
|
@@ -1088,6 +1295,7 @@ module MakeWithConfig = (
|
|
|
1088
1295
|
})
|
|
1089
1296
|
)
|
|
1090
1297
|
})
|
|
1298
|
+
}
|
|
1091
1299
|
},
|
|
1092
1300
|
// DCB EventLog created hook — extracts DynamoDB table name for DCB CommandTopic Lambda handler.
|
|
1093
1301
|
// Postgres-backed DCB logs (B2.3c) create no table, so there is no resource to read:
|
|
@@ -1271,23 +1479,6 @@ module MakeWithConfig = (
|
|
|
1271
1479
|
PluginReadModelMappings,
|
|
1272
1480
|
)
|
|
1273
1481
|
|
|
1274
|
-
// Admin-internal UIFragmentRegistry read model — projects UIFragmentRegistered/
|
|
1275
|
-
// Updated/Deregistered events from the Plugin aggregate into a DynamoDB table the
|
|
1276
|
-
// Platform_UIFragments admin query Lambda scans.
|
|
1277
|
-
module UIFragmentRegistryReadModelMappings: Reventless.Projection.Mappings
|
|
1278
|
-
with module Target := ReventlessCore.UIFragmentRegistryReadModelSpec = {
|
|
1279
|
-
module M = ReventlessCore.UIFragmentRegistryProjection.Mappings
|
|
1280
|
-
module type Mapping = M.Mapping
|
|
1281
|
-
// See PluginReadModelMappings.moduleUrl note above.
|
|
1282
|
-
let moduleUrl: string = ReventlessCore.UIFragmentRegistryProjection.moduleUrl
|
|
1283
|
-
let mappings: array<module(Mapping)> = ReventlessCore.UIFragmentRegistryProjection.mappings
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
module UIFragmentRegistryReadModel = ReadModel_Builder_NoResolver_Stream.Make(
|
|
1287
|
-
ReventlessCore.UIFragmentRegistryReadModelSpec,
|
|
1288
|
-
UIFragmentRegistryReadModelMappings,
|
|
1289
|
-
)
|
|
1290
|
-
|
|
1291
1482
|
module type PluginMaker = {
|
|
1292
1483
|
let make: unit => Plugin.component
|
|
1293
1484
|
}
|
|
@@ -1432,6 +1623,11 @@ module MakeWithConfig = (
|
|
|
1432
1623
|
(domainApi, domainApiRole)
|
|
1433
1624
|
}
|
|
1434
1625
|
|
|
1626
|
+
// Admin DCB mutation resolvers bind to the Platform API (split mode) or the shared api
|
|
1627
|
+
// (unified). Recorded now that the Platform API resource exists, so the admin's deferred
|
|
1628
|
+
// dcbConnectFn (via ~onAdminApi) targets it rather than the Domain/deploy-target hooksApiRef.
|
|
1629
|
+
hooks.adminApi := Some(platformApi->wrapHookedValue)
|
|
1630
|
+
|
|
1435
1631
|
// Update splitApiOutputsRef and apiConfig with the now-known Platform API.
|
|
1436
1632
|
if Config.splitApi {
|
|
1437
1633
|
splitApiOutputsRef := Some({platformApi, platformApiRole})
|
|
@@ -1453,16 +1649,13 @@ module MakeWithConfig = (
|
|
|
1453
1649
|
~version,
|
|
1454
1650
|
~extensionPoints=[],
|
|
1455
1651
|
~aggregates=[module(PluginAggregate)],
|
|
1456
|
-
~readModels=[
|
|
1457
|
-
module(PluginReadModel),
|
|
1458
|
-
module(UIFragmentRegistryReadModel),
|
|
1459
|
-
],
|
|
1652
|
+
~readModels=[module(PluginReadModel)],
|
|
1460
1653
|
~scheduler,
|
|
1461
1654
|
~resourceNaming=Util_ResourceNaming.operations,
|
|
1462
1655
|
~api=platformApi,
|
|
1463
1656
|
~apiRole=platformApiRole,
|
|
1464
|
-
~stateChangeSlices=[],
|
|
1465
|
-
~stateViewSlices=[],
|
|
1657
|
+
~stateChangeSlices=[module(UiFragmentRegistrySlice), module(ApiFragmentRegistrySlice)],
|
|
1658
|
+
~stateViewSlices=[module(UiFragmentsViewSlice), module(ApiFragmentsViewSlice)],
|
|
1466
1659
|
~automationSlices=[],
|
|
1467
1660
|
~outboundTranslationSlices=[],
|
|
1468
1661
|
~inboundTranslationSlices=[],
|
|
@@ -1488,15 +1681,36 @@ module MakeWithConfig = (
|
|
|
1488
1681
|
| None => ()
|
|
1489
1682
|
}
|
|
1490
1683
|
|
|
1491
|
-
// Mount the Platform_UIFragments Lambda resolver — scans the
|
|
1492
|
-
// table provisioned above and returns one entry per registered
|
|
1493
|
-
|
|
1684
|
+
// Mount the Platform_UIFragments Lambda resolver — scans the UiFragments
|
|
1685
|
+
// StateViewSlice table provisioned above and returns one entry per registered
|
|
1686
|
+
// plugin UI.
|
|
1687
|
+
switch admin.stateViewSlicesOutputs->Dict.get("UiFragments") {
|
|
1494
1688
|
| Some(rm) =>
|
|
1495
1689
|
switch rm.queryDb.resources->Array.get(0) {
|
|
1496
1690
|
| Some(r) =>
|
|
1497
1691
|
Platform_UIFragments_Lambda.make(
|
|
1498
1692
|
~api=platformApi,
|
|
1499
1693
|
~uiFragmentRegistryTableName=r.name,
|
|
1694
|
+
~schemaReady=admin.adminSchemaPushed,
|
|
1695
|
+
~opts={},
|
|
1696
|
+
)
|
|
1697
|
+
| None => ()
|
|
1698
|
+
}
|
|
1699
|
+
| None => ()
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// Mount the Platform_ApiFragments Lambda resolver — scans the ApiFragments
|
|
1703
|
+
// StateViewSlice table and returns the push-status row per plugin (the deploy
|
|
1704
|
+
// waiter polls this). The query field is in the pushed admin base but was
|
|
1705
|
+
// unresolved on AWS until now.
|
|
1706
|
+
switch admin.stateViewSlicesOutputs->Dict.get("ApiFragments") {
|
|
1707
|
+
| Some(rm) =>
|
|
1708
|
+
switch rm.queryDb.resources->Array.get(0) {
|
|
1709
|
+
| Some(r) =>
|
|
1710
|
+
Platform_ApiFragments_Lambda.make(
|
|
1711
|
+
~api=platformApi,
|
|
1712
|
+
~apiFragmentRegistryTableName=r.name,
|
|
1713
|
+
~schemaReady=admin.adminSchemaPushed,
|
|
1500
1714
|
~opts={},
|
|
1501
1715
|
)
|
|
1502
1716
|
| None => ()
|
|
@@ -1599,6 +1813,11 @@ module MakeWithConfig = (
|
|
|
1599
1813
|
(domainApi, domainApiRole)
|
|
1600
1814
|
}
|
|
1601
1815
|
|
|
1816
|
+
// Admin DCB mutation resolvers bind to the Platform API (split mode) or the shared api
|
|
1817
|
+
// (unified). Recorded now that the Platform API resource exists, so the admin's deferred
|
|
1818
|
+
// dcbConnectFn (via ~onAdminApi) targets it rather than the Domain/deploy-target hooksApiRef.
|
|
1819
|
+
hooks.adminApi := Some(platformApi->wrapHookedValue)
|
|
1820
|
+
|
|
1602
1821
|
// Update splitApiOutputsRef and apiConfig with the now-known Platform API.
|
|
1603
1822
|
if Config.splitApi {
|
|
1604
1823
|
splitApiOutputsRef := Some({platformApi, platformApiRole})
|
|
@@ -1660,12 +1879,13 @@ module MakeWithConfig = (
|
|
|
1660
1879
|
AppSync_Adapter.injectAwsAuthAll(
|
|
1661
1880
|
ReventlessCore.AdminApi.baseFragment(~cloner=Config.cloner),
|
|
1662
1881
|
~group="Admin",
|
|
1882
|
+
~iamFieldNames=ReventlessCore.AdminApi.systemCallerFieldNames,
|
|
1663
1883
|
)
|
|
1664
1884
|
}
|
|
1665
|
-
let sdl =
|
|
1885
|
+
let sdl = AppSync_Adapter.stitchWithAwsDirectives(
|
|
1666
1886
|
~baseFragment,
|
|
1667
1887
|
~pluginFragments=fragments,
|
|
1668
|
-
)
|
|
1888
|
+
)
|
|
1669
1889
|
await AppSync_Adapter.getClient()->AppSync_Adapter.startSchemaCreationRetrying({
|
|
1670
1890
|
apiId,
|
|
1671
1891
|
definition: sdl,
|
|
@@ -1679,16 +1899,13 @@ module MakeWithConfig = (
|
|
|
1679
1899
|
~version,
|
|
1680
1900
|
~extensionPoints=[module(PluginExtensionPoint)],
|
|
1681
1901
|
~aggregates=[module(PluginAggregate)],
|
|
1682
|
-
~readModels=[
|
|
1683
|
-
module(PluginReadModel),
|
|
1684
|
-
module(UIFragmentRegistryReadModel),
|
|
1685
|
-
],
|
|
1902
|
+
~readModels=[module(PluginReadModel)],
|
|
1686
1903
|
~scheduler,
|
|
1687
1904
|
~resourceNaming=Util_ResourceNaming.operations,
|
|
1688
1905
|
~api=platformApi,
|
|
1689
1906
|
~apiRole=platformApiRole,
|
|
1690
|
-
~stateChangeSlices=[],
|
|
1691
|
-
~stateViewSlices=[],
|
|
1907
|
+
~stateChangeSlices=[module(UiFragmentRegistrySlice), module(ApiFragmentRegistrySlice)],
|
|
1908
|
+
~stateViewSlices=[module(UiFragmentsViewSlice), module(ApiFragmentsViewSlice)],
|
|
1692
1909
|
~automationSlices=[],
|
|
1693
1910
|
~outboundTranslationSlices=[],
|
|
1694
1911
|
~inboundTranslationSlices=[],
|
|
@@ -1758,6 +1975,18 @@ module MakeWithConfig = (
|
|
|
1758
1975
|
~pluginSchemaPersistenceTableName=pluginSchemaPersistenceTable.name,
|
|
1759
1976
|
~schedulerRoleArn=hooks.schedulerRoleUrn.contents,
|
|
1760
1977
|
~clonerEnabled=Config.cloner,
|
|
1978
|
+
// 2e: the reactive ApiFragmentRegistry single writer (admin EventCollector).
|
|
1979
|
+
// The Platform AppSync id it pushes Platform-target fragments to (unified →
|
|
1980
|
+
// domainApi, since platformApi == domainApi); the ApiFragments StateViewSlice
|
|
1981
|
+
// table it re-folds from; the admin DCB command-topic FIFO URL it dispatches
|
|
1982
|
+
// RecordApiFragmentPush to (captured during Admin.construct); and the mode flag.
|
|
1983
|
+
~platformApiId=platformApi->Pulumi.Output.flatMap(api => api.id),
|
|
1984
|
+
~apiFragmentRegistryTableName=?admin.stateViewSlicesOutputs
|
|
1985
|
+
->Dict.get("ApiFragments")
|
|
1986
|
+
->Option.flatMap(rm => rm.queryDb.resources->Array.get(0))
|
|
1987
|
+
->Option.map(r => r.name),
|
|
1988
|
+
~adminDcbCmdTopicUrl=?AutomationSliceRuntime_Builder_Single.getDcbQueueUrl(),
|
|
1989
|
+
~splitApi=Config.splitApi,
|
|
1761
1990
|
(),
|
|
1762
1991
|
)
|
|
1763
1992
|
|
|
@@ -1777,15 +2006,36 @@ module MakeWithConfig = (
|
|
|
1777
2006
|
| None => ()
|
|
1778
2007
|
}
|
|
1779
2008
|
|
|
1780
|
-
// Mount the Platform_UIFragments Lambda resolver — scans the
|
|
1781
|
-
// table provisioned above and returns one entry per registered
|
|
1782
|
-
|
|
2009
|
+
// Mount the Platform_UIFragments Lambda resolver — scans the UiFragments
|
|
2010
|
+
// StateViewSlice table provisioned above and returns one entry per registered
|
|
2011
|
+
// plugin UI.
|
|
2012
|
+
switch admin.stateViewSlicesOutputs->Dict.get("UiFragments") {
|
|
1783
2013
|
| Some(rm) =>
|
|
1784
2014
|
switch rm.queryDb.resources->Array.get(0) {
|
|
1785
2015
|
| Some(r) =>
|
|
1786
2016
|
Platform_UIFragments_Lambda.make(
|
|
1787
2017
|
~api=platformApi,
|
|
1788
2018
|
~uiFragmentRegistryTableName=r.name,
|
|
2019
|
+
~schemaReady=admin.adminSchemaPushed,
|
|
2020
|
+
~opts={},
|
|
2021
|
+
)
|
|
2022
|
+
| None => ()
|
|
2023
|
+
}
|
|
2024
|
+
| None => ()
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// Mount the Platform_ApiFragments Lambda resolver — scans the ApiFragments
|
|
2028
|
+
// StateViewSlice table and returns the push-status row per plugin (the deploy
|
|
2029
|
+
// waiter polls this). The query field is in the pushed admin base but was
|
|
2030
|
+
// unresolved on AWS until now.
|
|
2031
|
+
switch admin.stateViewSlicesOutputs->Dict.get("ApiFragments") {
|
|
2032
|
+
| Some(rm) =>
|
|
2033
|
+
switch rm.queryDb.resources->Array.get(0) {
|
|
2034
|
+
| Some(r) =>
|
|
2035
|
+
Platform_ApiFragments_Lambda.make(
|
|
2036
|
+
~api=platformApi,
|
|
2037
|
+
~apiFragmentRegistryTableName=r.name,
|
|
2038
|
+
~schemaReady=admin.adminSchemaPushed,
|
|
1789
2039
|
~opts={},
|
|
1790
2040
|
)
|
|
1791
2041
|
| None => ()
|