@reventlessdev/reventless-aws 3.0.0-alpha.201 → 3.0.0-alpha.203
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 +14 -0
- package/package.json +7 -7
- package/src/Platform.res +129 -25
- package/src/Platform.res.mjs +210 -102
- package/src/adapter/Api/ApiFragmentDeregistration.res +5 -4
- package/src/adapter/Api/ApiFragmentDeregistration.res.mjs +2 -2
- package/src/adapter/Api/ApiSchemaPush.res +54 -0
- package/src/adapter/Api/ApiSchemaPush.res.mjs +37 -0
- package/src/adapter/Runtime/ApiSchemaPush_Runtime.mjs +197 -0
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res +47 -0
- package/src/adapter/Runtime/SideEffectHandlerRuntime_Builder_Single.res.mjs +34 -0
- package/src/components/SideEffectHandler_Single.res +8 -0
- package/src/components/SideEffectHandler_Single.res.mjs +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
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.203 (2026-07-13)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **admin:** orphan resolvers on the ApiFragmentRegistry aggregate deploy (deploy [#5](https://github.com/ReventlessDev/reventless-core/issues/5)) ([9572344](https://github.com/ReventlessDev/reventless-core/commit/95723443bad8ae8143ceaea209827ee5bd4d0ae5)), closes [#2](https://github.com/ReventlessDev/reventless-core/issues/2)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.202 (2026-07-13)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **admin:** ApiFragmentRegistry slice→singleton-aggregate + reactive schema-push SideEffect ([cf70903](https://github.com/ReventlessDev/reventless-core/commit/cf709033f0d822248ebbcfe3fe6b4019b3c1e8a0)), closes [#4](https://github.com/ReventlessDev/reventless-core/issues/4)
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# 3.0.0-alpha.201 (2026-07-13)
|
|
7
21
|
|
|
8
22
|
### Bug Fixes
|
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.203",
|
|
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-effect": "0.1.0-alpha.27",
|
|
12
11
|
"@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.21",
|
|
12
|
+
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.50",
|
|
13
13
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.7",
|
|
14
14
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.15",
|
|
15
|
-
"@reventlessdev/rescript-
|
|
15
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.27",
|
|
16
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.162",
|
|
16
17
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.15",
|
|
17
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.160",
|
|
18
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.98",
|
|
19
18
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.26",
|
|
20
|
-
"@reventlessdev/reventless-
|
|
21
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.
|
|
19
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.98",
|
|
20
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.26",
|
|
21
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.76"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"rescript": "^12.3.0",
|
package/src/Platform.res
CHANGED
|
@@ -113,9 +113,9 @@ module MakeWithConfig = (
|
|
|
113
113
|
// Lambda (DynamoDB scan) — keep it off the Postgres selection like the other
|
|
114
114
|
// admin stores.
|
|
115
115
|
QueryDbBackend.exempt(ReventlessCore.UiFragments.name)
|
|
116
|
-
// The ApiFragments
|
|
117
|
-
//
|
|
118
|
-
QueryDbBackend.exempt(ReventlessCore.
|
|
116
|
+
// The ApiFragments read model backs the Platform_ApiFragments status query
|
|
117
|
+
// (DynamoDB scan) and the schema-push SideEffect — admin store, off Postgres too.
|
|
118
|
+
QueryDbBackend.exempt(ReventlessCore.ApiFragmentsReadModelSpec.name)
|
|
119
119
|
})
|
|
120
120
|
type api = Types.AppSync.api
|
|
121
121
|
type role = Types.AppSync.role
|
|
@@ -585,14 +585,8 @@ module MakeWithConfig = (
|
|
|
585
585
|
ReventlessCore.UiFragments,
|
|
586
586
|
ReventlessCore.UiFragments_Projection,
|
|
587
587
|
)
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
ReventlessCore.ApiFragmentRegistry_Behavior,
|
|
591
|
-
)
|
|
592
|
-
module ApiFragmentsViewSlice = StateViewSlice.Make(
|
|
593
|
-
ReventlessCore.ApiFragments,
|
|
594
|
-
ReventlessCore.ApiFragments_Projection,
|
|
595
|
-
)
|
|
588
|
+
// The API-fragment registry is a SINGLETON AGGREGATE now (not a DCB slice) — see
|
|
589
|
+
// ApiFragmentRegistryAggregate / ApiFragmentsReadModel below.
|
|
596
590
|
|
|
597
591
|
// Empty base fragment — no types, no mutations, no queries.
|
|
598
592
|
// Used by the plugin Api in split mode so plugin schema has no core fields.
|
|
@@ -779,8 +773,10 @@ module MakeWithConfig = (
|
|
|
779
773
|
->Pulumi.Output.flatMap(endpoint => {
|
|
780
774
|
let run = async () => {
|
|
781
775
|
let at = Date.make()->Date.toISOString
|
|
776
|
+
// The registry is a SINGLETON aggregate — the mutation `id` arg is the fixed
|
|
777
|
+
// constant, `pluginId` (a payload field) carries the plugin name.
|
|
782
778
|
let variables = {
|
|
783
|
-
"id":
|
|
779
|
+
"id": "registry",
|
|
784
780
|
"pluginId": name,
|
|
785
781
|
"fragment": {"encoded": fragment.encoded, "protocol": fragment.protocol},
|
|
786
782
|
"apiTarget": Util_AppSync_Caller.graphqlEnum(apiTargetName),
|
|
@@ -794,14 +790,14 @@ module MakeWithConfig = (
|
|
|
794
790
|
let result = await Util_AppSync_Caller.sendMutation(
|
|
795
791
|
~endpoint,
|
|
796
792
|
~region,
|
|
797
|
-
~mutation="
|
|
793
|
+
~mutation="Platform_ApiFragmentRegistry_RegisterApiFragment",
|
|
798
794
|
~selection,
|
|
799
795
|
~variables,
|
|
800
796
|
)
|
|
801
797
|
let outcome =
|
|
802
798
|
result
|
|
803
799
|
->Option.flatMap(JSON.Decode.object)
|
|
804
|
-
->Option.flatMap(d => d->Dict.get("
|
|
800
|
+
->Option.flatMap(d => d->Dict.get("Platform_ApiFragmentRegistry_RegisterApiFragment"))
|
|
805
801
|
->Option.flatMap(JSON.Decode.object)
|
|
806
802
|
switch outcome {
|
|
807
803
|
| Some(cmd) =>
|
|
@@ -1480,6 +1476,49 @@ module MakeWithConfig = (
|
|
|
1480
1476
|
PluginReadModelMappings,
|
|
1481
1477
|
)
|
|
1482
1478
|
|
|
1479
|
+
// Admin-internal ApiFragmentRegistry singleton aggregate — the platform API-schema
|
|
1480
|
+
// fragment registry (docs/plans/event-sourced-fragment-registries.md). Threaded into
|
|
1481
|
+
// Admin.construct's ~aggregates so registerAdminAggregateMutations wires
|
|
1482
|
+
// Platform_ApiFragmentRegistry_RegisterApiFragment / _DeregisterApiFragment via the
|
|
1483
|
+
// standard CommandGenerator auto-flow (RecordApiFragmentPush carries `@noApi`). IAM
|
|
1484
|
+
// dual-auth for the SigV4 deploy caller comes from AdminApi.systemCallerFieldNames.
|
|
1485
|
+
module ApiFragmentRegistryAggregate: (
|
|
1486
|
+
ReventlessInfra.Aggregate.T with type api = Types.AppSync.api
|
|
1487
|
+
) = Aggregate_Builder_Single.Make(
|
|
1488
|
+
ReventlessCore.ApiFragmentRegistrySpec,
|
|
1489
|
+
ReventlessCore.ApiFragmentRegistryBehavior,
|
|
1490
|
+
ReventlessInfra.NoEventMappings.Make(ReventlessCore.ApiFragmentRegistrySpec),
|
|
1491
|
+
)
|
|
1492
|
+
|
|
1493
|
+
// Admin ApiFragments read model — per-plugin status rows off the aggregate's event
|
|
1494
|
+
// topic (DynamoDB stream), backing the Platform_ApiFragments status query.
|
|
1495
|
+
module ApiFragmentsReadModelMappings: Reventless.Projection.Mappings
|
|
1496
|
+
with module Target := ReventlessCore.ApiFragmentsReadModelSpec = {
|
|
1497
|
+
module M = ReventlessCore.ApiFragmentsProjection.Mappings
|
|
1498
|
+
module type Mapping = M.Mapping
|
|
1499
|
+
let moduleUrl: string = ReventlessCore.ApiFragmentsProjection.moduleUrl
|
|
1500
|
+
let mappings: array<module(Mapping)> = ReventlessCore.ApiFragmentsProjection.mappings
|
|
1501
|
+
}
|
|
1502
|
+
// NoResolver builder: the ApiFragments RM's query surface is the dedicated
|
|
1503
|
+
// Platform_ApiFragments Lambda (declared in baseFragment), NOT an auto-generated
|
|
1504
|
+
// Connection resolver — so it must not emit AppSync query resolvers (they'd orphan
|
|
1505
|
+
// against the static pushed baseFragment: "No field named apiFragments on type Query").
|
|
1506
|
+
// Still stream-projects the table from the aggregate's event stream. Mirrors local's
|
|
1507
|
+
// MakeNoResolver.
|
|
1508
|
+
module ApiFragmentsReadModel = ReadModel_Builder_NoResolver_Stream.Make(
|
|
1509
|
+
ReventlessCore.ApiFragmentsReadModelSpec,
|
|
1510
|
+
ApiFragmentsReadModelMappings,
|
|
1511
|
+
)
|
|
1512
|
+
|
|
1513
|
+
// Admin SideEffectHandler hosting the reactive schema-push side effect (ApiSchemaPush).
|
|
1514
|
+
// Subscribes to the ApiFragmentRegistry aggregate's DynamoDB stream (single-shard for a
|
|
1515
|
+
// singleton → naturally serialized, no concurrent StartSchemaCreation) and, on each
|
|
1516
|
+
// ApiSchemaComputed, stitches + pushes the schema per target API, then records the
|
|
1517
|
+
// outcome via RecordApiFragmentPush. Wired in deployPlatform (the staged path where the
|
|
1518
|
+
// deploy caller fires RegisterApiFragment); makePlatform builds the schema directly and
|
|
1519
|
+
// never triggers it.
|
|
1520
|
+
module AdminApiSchemaPushHandler = SideEffectHandler_Single.Make()
|
|
1521
|
+
|
|
1483
1522
|
module type PluginMaker = {
|
|
1484
1523
|
let make: unit => Plugin.component
|
|
1485
1524
|
}
|
|
@@ -1649,14 +1688,14 @@ module MakeWithConfig = (
|
|
|
1649
1688
|
let admin = Admin.construct(
|
|
1650
1689
|
~version,
|
|
1651
1690
|
~extensionPoints=[],
|
|
1652
|
-
~aggregates=[module(PluginAggregate)],
|
|
1653
|
-
~readModels=[module(PluginReadModel)],
|
|
1691
|
+
~aggregates=[module(PluginAggregate), module(ApiFragmentRegistryAggregate)],
|
|
1692
|
+
~readModels=[module(PluginReadModel), module(ApiFragmentsReadModel)],
|
|
1654
1693
|
~scheduler,
|
|
1655
1694
|
~resourceNaming=Util_ResourceNaming.operations,
|
|
1656
1695
|
~api=platformApi,
|
|
1657
1696
|
~apiRole=platformApiRole,
|
|
1658
|
-
~stateChangeSlices=[module(UiFragmentRegistrySlice)
|
|
1659
|
-
~stateViewSlices=[module(UiFragmentsViewSlice)
|
|
1697
|
+
~stateChangeSlices=[module(UiFragmentRegistrySlice)],
|
|
1698
|
+
~stateViewSlices=[module(UiFragmentsViewSlice)],
|
|
1660
1699
|
~automationSlices=[],
|
|
1661
1700
|
~outboundTranslationSlices=[],
|
|
1662
1701
|
~inboundTranslationSlices=[],
|
|
@@ -1704,7 +1743,7 @@ module MakeWithConfig = (
|
|
|
1704
1743
|
// StateViewSlice table and returns the push-status row per plugin (the deploy
|
|
1705
1744
|
// waiter polls this). The query field is in the pushed admin base but was
|
|
1706
1745
|
// unresolved on AWS until now.
|
|
1707
|
-
switch admin.
|
|
1746
|
+
switch admin.readModelsOutputs->Dict.get("ApiFragments") {
|
|
1708
1747
|
| Some(rm) =>
|
|
1709
1748
|
switch rm.queryDb.resources->Array.get(0) {
|
|
1710
1749
|
| Some(r) =>
|
|
@@ -1899,19 +1938,84 @@ module MakeWithConfig = (
|
|
|
1899
1938
|
let admin = Admin.construct(
|
|
1900
1939
|
~version,
|
|
1901
1940
|
~extensionPoints=[module(PluginExtensionPoint)],
|
|
1902
|
-
~aggregates=[module(PluginAggregate)],
|
|
1903
|
-
~readModels=[module(PluginReadModel)],
|
|
1941
|
+
~aggregates=[module(PluginAggregate), module(ApiFragmentRegistryAggregate)],
|
|
1942
|
+
~readModels=[module(PluginReadModel), module(ApiFragmentsReadModel)],
|
|
1904
1943
|
~scheduler,
|
|
1905
1944
|
~resourceNaming=Util_ResourceNaming.operations,
|
|
1906
1945
|
~api=platformApi,
|
|
1907
1946
|
~apiRole=platformApiRole,
|
|
1908
|
-
~stateChangeSlices=[module(UiFragmentRegistrySlice)
|
|
1909
|
-
~stateViewSlices=[module(UiFragmentsViewSlice)
|
|
1947
|
+
~stateChangeSlices=[module(UiFragmentRegistrySlice)],
|
|
1948
|
+
~stateViewSlices=[module(UiFragmentsViewSlice)],
|
|
1910
1949
|
~automationSlices=[],
|
|
1911
1950
|
~outboundTranslationSlices=[],
|
|
1912
1951
|
~inboundTranslationSlices=[],
|
|
1913
1952
|
)
|
|
1914
1953
|
|
|
1954
|
+
// Reactive schema-push SideEffect (docs/plans/event-sourced-fragment-registries.md §
|
|
1955
|
+
// Reactive writer design). Host ApiSchemaPush on an admin SideEffectHandler subscribed to
|
|
1956
|
+
// the ApiFragmentRegistry aggregate's event topic (its DynamoDB stream). Deploy-derived
|
|
1957
|
+
// config the runtime-pure push reads at invocation time is injected as Lambda env
|
|
1958
|
+
// (~extraEnvVars); ~targets grants SQS send to the aggregate command topic (for the
|
|
1959
|
+
// RecordApiFragmentPush write-back). Only in deployPlatform (the staged path where the
|
|
1960
|
+
// deploy caller fires RegisterApiFragment); makePlatform pushes the schema directly.
|
|
1961
|
+
let apiSchemaPushEventTopics = ReventlessCore.Aggregate.allEventTopics(admin.aggregatesOutputs)
|
|
1962
|
+
let apiSchemaPushCmdTopics = ReventlessCore.Aggregate.allCommandTopics(admin.aggregatesOutputs)
|
|
1963
|
+
let apiSchemaPushCmdTopicUrl =
|
|
1964
|
+
admin.aggregatesOutputs
|
|
1965
|
+
->Dict.get(ReventlessCore.ApiFragmentRegistrySpec.name)
|
|
1966
|
+
->Option.map(agg =>
|
|
1967
|
+
agg.commandTopic->Pulumi.Output.flatMap(ct =>
|
|
1968
|
+
switch ct.resources->Array.get(0) {
|
|
1969
|
+
| Some(r) => r.id
|
|
1970
|
+
| None => Pulumi.Output.make("")
|
|
1971
|
+
}
|
|
1972
|
+
)
|
|
1973
|
+
)
|
|
1974
|
+
->Option.getOr(Pulumi.Output.make(""))
|
|
1975
|
+
let apiSchemaPushEnv = Dict.fromArray([
|
|
1976
|
+
("API_SCHEMA_PUSH_DOMAIN_API_ID", domainApiId->Pulumi.Output.asInput),
|
|
1977
|
+
(
|
|
1978
|
+
"API_SCHEMA_PUSH_PLATFORM_API_ID",
|
|
1979
|
+
platformApi->Pulumi.Output.flatMap(api => api.id)->Pulumi.Output.asInput,
|
|
1980
|
+
),
|
|
1981
|
+
(
|
|
1982
|
+
"API_SCHEMA_PUSH_SPLIT_API",
|
|
1983
|
+
Pulumi.Output.make(Config.splitApi ? "true" : "false")->Pulumi.Output.asInput,
|
|
1984
|
+
),
|
|
1985
|
+
(
|
|
1986
|
+
"API_SCHEMA_PUSH_CLONER",
|
|
1987
|
+
Pulumi.Output.make(Config.cloner ? "true" : "false")->Pulumi.Output.asInput,
|
|
1988
|
+
),
|
|
1989
|
+
("API_SCHEMA_PUSH_CMD_TOPIC_URL", apiSchemaPushCmdTopicUrl->Pulumi.Output.asInput),
|
|
1990
|
+
])
|
|
1991
|
+
// ApiSchemaPush ignores the injected queryEngine (it self-wires push + write-back from
|
|
1992
|
+
// env); an empty one satisfies the SideEffectHandler contract. `scheduler`/`queryEngine`
|
|
1993
|
+
// are Outputs, so make() runs inside their apply; finish() (which builds the shared
|
|
1994
|
+
// Lambda from the registered handlers) is registered on apiSchemaPushCmdTopics
|
|
1995
|
+
// immediately after make() so it runs AFTER make's own deferred forEventCollector apply
|
|
1996
|
+
// on that same Output (Pulumi runs same-Output apply callbacks in registration order).
|
|
1997
|
+
let apiSchemaPushQueryEngine = QueryEngine.DynamoDb.make(Dict.make())
|
|
1998
|
+
let _ =
|
|
1999
|
+
(scheduler, apiSchemaPushQueryEngine)
|
|
2000
|
+
->Pulumi.Output.all2
|
|
2001
|
+
->Pulumi.Output.apply(((sched, qe)) => {
|
|
2002
|
+
let _ = AdminApiSchemaPushHandler.make(
|
|
2003
|
+
~name="AdminApiSchemaPush",
|
|
2004
|
+
~sideEffects=[module(ApiSchemaPush)],
|
|
2005
|
+
~allEventTopics=apiSchemaPushEventTopics,
|
|
2006
|
+
~allCommandTopics=apiSchemaPushCmdTopics,
|
|
2007
|
+
~targets=[ReventlessCore.ApiFragmentRegistrySpec.name],
|
|
2008
|
+
~queryEngine=qe,
|
|
2009
|
+
~scheduler=sched,
|
|
2010
|
+
~resourceNaming=Util_ResourceNaming.operations,
|
|
2011
|
+
~extraEnvVars=apiSchemaPushEnv,
|
|
2012
|
+
~opts={},
|
|
2013
|
+
)
|
|
2014
|
+
let _ = apiSchemaPushCmdTopics->Pulumi.Output.apply(_ =>
|
|
2015
|
+
SideEffectHandlerRuntime_Builder_Single.finish()
|
|
2016
|
+
)
|
|
2017
|
+
})
|
|
2018
|
+
|
|
1915
2019
|
// Extract Plugin RM table name as Output.t<string>.
|
|
1916
2020
|
// IMPORTANT: Do NOT use option<Pulumi.Output.t<…>> — Pulumi Outputs use property
|
|
1917
2021
|
// lifting, which breaks ReScript's internal option encoding (BS_PRIVATE_NESTED_SOME_NONE).
|
|
@@ -1991,7 +2095,7 @@ module MakeWithConfig = (
|
|
|
1991
2095
|
// so the consumer's `tableOutput->Pulumi.Output.apply` crashes with
|
|
1992
2096
|
// "apply is not a function". A `Some(r.name)` LITERAL compiles unboxed (bare
|
|
1993
2097
|
// r.name), preserving the Output — the same dodge `pluginReadModelTableName` uses.
|
|
1994
|
-
~apiFragmentRegistryTableName=?switch admin.
|
|
2098
|
+
~apiFragmentRegistryTableName=?switch admin.readModelsOutputs
|
|
1995
2099
|
->Dict.get("ApiFragments")
|
|
1996
2100
|
->Option.flatMap(rm => rm.queryDb.resources->Array.get(0)) {
|
|
1997
2101
|
| Some(r) => Some(r.name)
|
|
@@ -2040,7 +2144,7 @@ module MakeWithConfig = (
|
|
|
2040
2144
|
// StateViewSlice table and returns the push-status row per plugin (the deploy
|
|
2041
2145
|
// waiter polls this). The query field is in the pushed admin base but was
|
|
2042
2146
|
// unresolved on AWS until now.
|
|
2043
|
-
switch admin.
|
|
2147
|
+
switch admin.readModelsOutputs->Dict.get("ApiFragments") {
|
|
2044
2148
|
| Some(rm) =>
|
|
2045
2149
|
switch rm.queryDb.resources->Array.get(0) {
|
|
2046
2150
|
| Some(r) =>
|