@reventlessdev/reventless-local 3.0.0-alpha.145 → 3.0.0-alpha.147
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 +15 -0
- package/package.json +9 -9
- package/src/Platform.res +109 -74
- package/src/Platform.res.mjs +251 -227
- package/src/adapter/LocalSideEffectHandler.res +1 -0
- package/src/adapter/LocalSideEffectHandler.res.mjs +1 -1
- package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +1 -1
- package/tests/plugin/ApiFragmentRegistryBehavior_GWT.res +131 -0
- package/tests/plugin/{ApiFragmentRegistry_GWT.res.mjs → ApiFragmentRegistryBehavior_GWT.res.mjs} +114 -42
- package/tests/plugin/{ApiFragments_GWT.res → ApiFragmentsProjection_GWT.res} +16 -11
- package/tests/plugin/{ApiFragments_GWT.res.mjs → ApiFragmentsProjection_GWT.res.mjs} +15 -13
- package/tests/plugin/ApiFragmentRegistry_GWT.res +0 -103
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
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.147 (2026-07-13)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **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)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.146 (2026-07-13)
|
|
14
|
+
|
|
15
|
+
**Note:** Version bump only for package @reventlessdev/reventless-local
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 3.0.0-alpha.145 (2026-07-12)
|
|
7
22
|
|
|
8
23
|
**Note:** Version bump only for package @reventlessdev/reventless-local
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-local",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.147",
|
|
4
4
|
"description": "Local platform for Reventless (in-memory or SQLite backend, for development and testing without AWS)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -31,17 +31,17 @@
|
|
|
31
31
|
"graphql-yoga": "^5.21.0",
|
|
32
32
|
"sury": "11.0.0-alpha.4",
|
|
33
33
|
"ws": "^8.18.0",
|
|
34
|
-
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.20",
|
|
35
34
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.27",
|
|
36
|
-
"@reventlessdev/rescript-
|
|
37
|
-
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.15",
|
|
35
|
+
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.20",
|
|
38
36
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.7",
|
|
39
|
-
"@reventlessdev/
|
|
40
|
-
"@reventlessdev/
|
|
37
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.15",
|
|
38
|
+
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.18",
|
|
39
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.161",
|
|
40
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.107",
|
|
41
41
|
"@reventlessdev/reventless-infra": "3.0.0-alpha.98",
|
|
42
|
-
"@reventlessdev/reventless-
|
|
43
|
-
"@reventlessdev/reventless-
|
|
44
|
-
"@reventlessdev/reventless-
|
|
42
|
+
"@reventlessdev/reventless-graphql-server": "1.0.0-alpha.8",
|
|
43
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.25",
|
|
44
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.76"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"rescript": "^12.3.0",
|
package/src/Platform.res
CHANGED
|
@@ -61,17 +61,18 @@ let decodeUiFragmentRegistryEventEnvelope = (
|
|
|
61
61
|
| exception _ => None
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// Same envelope
|
|
65
|
-
//
|
|
66
|
-
//
|
|
64
|
+
// Same {id, meta, event} envelope for the ApiFragmentRegistry singleton aggregate's
|
|
65
|
+
// events on its own aggregate event topic (the registry is an aggregate now, not a
|
|
66
|
+
// DCB slice — its events no longer ride the admin DcbEventLog). Top-level so the
|
|
67
|
+
// decode is unit-testable.
|
|
67
68
|
let decodeApiFragmentRegistryEventEnvelope = (
|
|
68
69
|
eventJson: JSON.t,
|
|
69
|
-
): option<ReventlessCore.
|
|
70
|
+
): option<ReventlessCore.ApiFragmentRegistrySpec.event> =>
|
|
70
71
|
switch eventJson
|
|
71
72
|
->JSON.Decode.object
|
|
72
73
|
->Option.flatMap(d => d->Dict.get("event"))
|
|
73
74
|
->Option.map(payload =>
|
|
74
|
-
payload->S.parseJsonOrThrow(ReventlessCore.
|
|
75
|
+
payload->S.parseJsonOrThrow(ReventlessCore.ApiFragmentRegistrySpec.eventSchema)
|
|
75
76
|
) {
|
|
76
77
|
| result => result
|
|
77
78
|
| exception _ => None
|
|
@@ -669,9 +670,10 @@ module MakeWithConfig = (
|
|
|
669
670
|
StateViewSliceMaker.Make(Spec, Projection)
|
|
670
671
|
}
|
|
671
672
|
|
|
672
|
-
// Admin fragment registry
|
|
673
|
-
// platform UI-
|
|
674
|
-
//
|
|
673
|
+
// Admin UI-fragment registry (docs/plans/event-sourced-fragment-registries.md): the
|
|
674
|
+
// platform UI-fragment registry hosted as an admin DCB slice on the admin DcbEventLog.
|
|
675
|
+
// Built once and passed into Admin.construct below. (The API-fragment registry is a
|
|
676
|
+
// singleton aggregate — see LocalApiFragmentRegistryAggregate — not a slice.)
|
|
675
677
|
module UiFragmentRegistrySlice = StateChangeSlice.Make(
|
|
676
678
|
ReventlessCore.UiFragmentRegistry,
|
|
677
679
|
ReventlessCore.UiFragmentRegistry_Behavior,
|
|
@@ -680,14 +682,6 @@ module MakeWithConfig = (
|
|
|
680
682
|
ReventlessCore.UiFragments,
|
|
681
683
|
ReventlessCore.UiFragments_Projection,
|
|
682
684
|
)
|
|
683
|
-
module ApiFragmentRegistrySlice = StateChangeSlice.Make(
|
|
684
|
-
ReventlessCore.ApiFragmentRegistry,
|
|
685
|
-
ReventlessCore.ApiFragmentRegistry_Behavior,
|
|
686
|
-
)
|
|
687
|
-
module ApiFragmentsViewSlice = StateViewSlice.Make(
|
|
688
|
-
ReventlessCore.ApiFragments,
|
|
689
|
-
ReventlessCore.ApiFragments_Projection,
|
|
690
|
-
)
|
|
691
685
|
|
|
692
686
|
// In-memory has no DynamoDB streams — stream variant is identical to plain StateViewSlice.
|
|
693
687
|
module StateViewSliceStream = {
|
|
@@ -803,6 +797,32 @@ module MakeWithConfig = (
|
|
|
803
797
|
PluginsReadModelMappings,
|
|
804
798
|
)
|
|
805
799
|
|
|
800
|
+
// Admin-internal ApiFragmentRegistry singleton aggregate — the platform API-schema
|
|
801
|
+
// fragment registry (docs/plans/event-sourced-fragment-registries.md). Threaded into
|
|
802
|
+
// Admin.construct's ~aggregates so registerAdminAggregateMutations wires
|
|
803
|
+
// Platform_ApiFragmentRegistry_RegisterApiFragment / _DeregisterApiFragment through the
|
|
804
|
+
// standard CommandGenerator auto-flow. RecordApiFragmentPush carries `@noApi`.
|
|
805
|
+
module LocalApiFragmentRegistryAggregate: ReventlessInfra.Aggregate.T
|
|
806
|
+
with type api = unit = AggregateMaker.Make(
|
|
807
|
+
ReventlessCore.ApiFragmentRegistrySpec,
|
|
808
|
+
ReventlessCore.ApiFragmentRegistryBehavior,
|
|
809
|
+
ReventlessInfra.NoEventMappings.Make(ReventlessCore.ApiFragmentRegistrySpec),
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
// Admin ApiFragments read model — per-plugin status rows off the aggregate's event
|
|
813
|
+
// topic, backing the Platform_ApiFragments status query.
|
|
814
|
+
module ApiFragmentsReadModelMappings: Reventless.Projection.Mappings
|
|
815
|
+
with module Target := ReventlessCore.ApiFragmentsReadModelSpec = {
|
|
816
|
+
module M = ReventlessCore.ApiFragmentsProjection.Mappings
|
|
817
|
+
module type Mapping = M.Mapping
|
|
818
|
+
let moduleUrl: string = ReventlessCore.ApiFragmentsProjection.moduleUrl
|
|
819
|
+
let mappings: array<module(Mapping)> = ReventlessCore.ApiFragmentsProjection.mappings
|
|
820
|
+
}
|
|
821
|
+
module ApiFragmentsReadModel = ReadModelMaker.MakeNoResolver(
|
|
822
|
+
ReventlessCore.ApiFragmentsReadModelSpec,
|
|
823
|
+
ApiFragmentsReadModelMappings,
|
|
824
|
+
)
|
|
825
|
+
|
|
806
826
|
|
|
807
827
|
module Admin = ReventlessCore.Platform_Admin.Make(
|
|
808
828
|
LocalRuntimeEnvironment,
|
|
@@ -934,38 +954,50 @@ module MakeWithConfig = (
|
|
|
934
954
|
Bus.dispatchCommand(adminDcbCmdTopicKey, ReventlessCore.CommandTopic.encodeCommandJson(cmdJson))
|
|
935
955
|
}
|
|
936
956
|
|
|
937
|
-
//
|
|
938
|
-
//
|
|
939
|
-
//
|
|
940
|
-
//
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
957
|
+
// The ApiFragmentRegistry is a SINGLETON aggregate now (not a DCB slice): the
|
|
958
|
+
// whole registry lives on one aggregate instance keyed by a fixed id, so a
|
|
959
|
+
// fragment change reads the whole registry from one consistency boundary. Its
|
|
960
|
+
// commands ride the aggregate command topic (name-derived, like the Plugin
|
|
961
|
+
// aggregate above) and its events its own aggregate event topic.
|
|
962
|
+
let apiFragmentRegistryId = "registry"
|
|
963
|
+
let apiFragmentRegistryCmdTopicKey =
|
|
964
|
+
ReventlessCore.ApiFragmentRegistrySpec.name ++ "Aggr" ++ "CmdTopic"
|
|
965
|
+
let apiFragmentRegistryEventTopicKey =
|
|
966
|
+
ReventlessCore.ApiFragmentRegistrySpec.name ++ "Aggr" ++ "EventTopic"
|
|
967
|
+
|
|
968
|
+
// Dispatch an ApiFragmentRegistry aggregate command in-process. On the real
|
|
969
|
+
// platform the deploy is the caller (SigV4 Platform_ApiFragmentRegistry_*
|
|
970
|
+
// mutation); locally we dispatch straight to the aggregate command topic. The
|
|
971
|
+
// aggregate id is the fixed singleton constant — `pluginId` is a payload field.
|
|
972
|
+
let dispatchApiFragmentCommand = (~command: ReventlessCore.ApiFragmentRegistrySpec.command) => {
|
|
945
973
|
let cmdJson: Reventless.Message.commandJson = {
|
|
946
|
-
id:
|
|
947
|
-
meta: ReventlessCore.Message.generateMeta(
|
|
974
|
+
id: apiFragmentRegistryId,
|
|
975
|
+
meta: ReventlessCore.Message.generateMeta(
|
|
976
|
+
~service=ReventlessCore.ApiFragmentRegistrySpec.name,
|
|
977
|
+
),
|
|
948
978
|
commandJson: command->S.reverseConvertToJsonOrThrow(
|
|
949
|
-
ReventlessCore.
|
|
979
|
+
ReventlessCore.ApiFragmentRegistrySpec.commandSchema,
|
|
950
980
|
),
|
|
951
981
|
}
|
|
952
|
-
Bus.dispatchCommand(
|
|
982
|
+
Bus.dispatchCommand(
|
|
983
|
+
apiFragmentRegistryCmdTopicKey,
|
|
984
|
+
ReventlessCore.CommandTopic.encodeCommandJson(cmdJson),
|
|
985
|
+
)
|
|
953
986
|
}
|
|
954
987
|
|
|
955
|
-
// Platform_ApiFragments status-query resolver — scans the ApiFragments
|
|
956
|
-
//
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
// server site below.
|
|
988
|
+
// Platform_ApiFragments status-query resolver — scans the ApiFragments read
|
|
989
|
+
// model (populated by the ApiFragmentRegistry aggregate's projection) and
|
|
990
|
+
// encodes via the shared Platform_ApiFragmentsApi encoder, so the deploy-facing
|
|
991
|
+
// status shape matches AWS. Keyed by bare plugin name (one row per plugin).
|
|
992
|
+
// Registered at each admin server site below.
|
|
961
993
|
let apiFragmentsQueryResolver = async (_root, _args, _ctx): JSON.t => {
|
|
962
|
-
let items = switch Bus.getQueryDbScan(ReventlessCore.
|
|
994
|
+
let items = switch Bus.getQueryDbScan(ReventlessCore.ApiFragmentsReadModelSpec.name) {
|
|
963
995
|
| Some(scanAll) => scanAll()
|
|
964
996
|
| None => []
|
|
965
997
|
}
|
|
966
998
|
items
|
|
967
999
|
->Array.filterMap(item =>
|
|
968
|
-
switch item->S.parseOrThrow(ReventlessCore.
|
|
1000
|
+
switch item->S.parseOrThrow(ReventlessCore.ApiFragmentsReadModelSpec.stateSchema) {
|
|
969
1001
|
| state => Some(ReventlessCore.Platform_ApiFragmentsApi.encodeApiFragmentEntry(state))
|
|
970
1002
|
| exception _ => None
|
|
971
1003
|
}
|
|
@@ -1063,29 +1095,33 @@ module MakeWithConfig = (
|
|
|
1063
1095
|
publishUIFragment(~name=pluginId, ~changeKind="Deregistered", ~manifest=JSON.Encode.null)
|
|
1064
1096
|
| None => ()
|
|
1065
1097
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1098
|
+
}
|
|
1099
|
+
)
|
|
1100
|
+
// Local single writer for the API-schema fragment registry — the analogue of
|
|
1101
|
+
// the AWS ApiSchemaPush SideEffect. Sourced from the ApiFragmentRegistry
|
|
1102
|
+
// aggregate's own event topic (the registry is an aggregate now). On AWS the
|
|
1103
|
+
// SideEffect stitches + pushes the schema then records the outcome; on local
|
|
1104
|
+
// the plugin's fields are already served by resolver registration, so the
|
|
1105
|
+
// "push" is a no-op and we simply record success — the honest status for an
|
|
1106
|
+
// in-process schema. Only Registered/Updated trigger a record (never
|
|
1107
|
+
// PushRecorded — no re-entrant loop; ApiSchemaComputed is the AWS push trigger
|
|
1108
|
+
// and is a no-op here).
|
|
1109
|
+
Bus.subscribeToEvents(apiFragmentRegistryEventTopicKey, async (_service, _meta, eventJson) =>
|
|
1110
|
+
switch decodeApiFragmentRegistryEventEnvelope(eventJson) {
|
|
1111
|
+
| Some(ApiFragmentRegistered({pluginId}))
|
|
1112
|
+
| Some(ApiFragmentUpdated({pluginId})) =>
|
|
1113
|
+
let _ = dispatchApiFragmentCommand(
|
|
1114
|
+
~command=ReventlessCore.ApiFragmentRegistrySpec.RecordApiFragmentPush({
|
|
1115
|
+
pluginId,
|
|
1116
|
+
ok: true,
|
|
1117
|
+
message: "",
|
|
1118
|
+
at: Date.make()->Date.toISOString,
|
|
1119
|
+
}),
|
|
1120
|
+
)
|
|
1121
|
+
| Some(ApiFragmentDeregistered(_))
|
|
1122
|
+
| Some(ApiFragmentPushRecorded(_))
|
|
1123
|
+
| Some(ApiSchemaComputed(_))
|
|
1124
|
+
| None => ()
|
|
1089
1125
|
}
|
|
1090
1126
|
)
|
|
1091
1127
|
}
|
|
@@ -1181,7 +1217,7 @@ module MakeWithConfig = (
|
|
|
1181
1217
|
)
|
|
1182
1218
|
| None => ()
|
|
1183
1219
|
}
|
|
1184
|
-
// Register the API-schema fragment with the ApiFragmentRegistry
|
|
1220
|
+
// Register the API-schema fragment with the ApiFragmentRegistry aggregate.
|
|
1185
1221
|
// On the real platform the deploy sends this as a SigV4 caller; locally
|
|
1186
1222
|
// connect stands in. apiTarget is Domain (mirrors pluginDefinition.kind
|
|
1187
1223
|
// = Domain / apiTarget = None above — the local platform has no
|
|
@@ -1190,8 +1226,7 @@ module MakeWithConfig = (
|
|
|
1190
1226
|
switch apiSchemaFragment {
|
|
1191
1227
|
| Some(fragment) =>
|
|
1192
1228
|
let _ = dispatchApiFragmentCommand(
|
|
1193
|
-
~
|
|
1194
|
-
~command=ReventlessCore.ApiFragmentRegistry.RegisterApiFragment({
|
|
1229
|
+
~command=ReventlessCore.ApiFragmentRegistrySpec.RegisterApiFragment({
|
|
1195
1230
|
pluginId: pluginName,
|
|
1196
1231
|
fragment,
|
|
1197
1232
|
apiTarget: Domain,
|
|
@@ -1465,14 +1500,14 @@ module MakeWithConfig = (
|
|
|
1465
1500
|
let admin = Admin.construct(
|
|
1466
1501
|
~version,
|
|
1467
1502
|
~extensionPoints=[],
|
|
1468
|
-
~aggregates=[module(LocalPluginAggregate)],
|
|
1469
|
-
~readModels=[module(PluginsReadModel)],
|
|
1503
|
+
~aggregates=[module(LocalPluginAggregate), module(LocalApiFragmentRegistryAggregate)],
|
|
1504
|
+
~readModels=[module(PluginsReadModel), module(ApiFragmentsReadModel)],
|
|
1470
1505
|
~scheduler,
|
|
1471
1506
|
~resourceNaming=LocalPluginSpec.resourceNaming,
|
|
1472
1507
|
~api=(),
|
|
1473
1508
|
~apiRole=(),
|
|
1474
|
-
~stateChangeSlices=[module(UiFragmentRegistrySlice)
|
|
1475
|
-
~stateViewSlices=[module(UiFragmentsViewSlice)
|
|
1509
|
+
~stateChangeSlices=[module(UiFragmentRegistrySlice)],
|
|
1510
|
+
~stateViewSlices=[module(UiFragmentsViewSlice)],
|
|
1476
1511
|
~automationSlices=[],
|
|
1477
1512
|
~outboundTranslationSlices=[],
|
|
1478
1513
|
~inboundTranslationSlices=[],
|
|
@@ -2031,14 +2066,14 @@ module MakeWithConfig = (
|
|
|
2031
2066
|
let _admin = Admin.construct(
|
|
2032
2067
|
~version,
|
|
2033
2068
|
~extensionPoints=[],
|
|
2034
|
-
~aggregates=[],
|
|
2035
|
-
~readModels=[],
|
|
2069
|
+
~aggregates=[module(LocalApiFragmentRegistryAggregate)],
|
|
2070
|
+
~readModels=[module(ApiFragmentsReadModel)],
|
|
2036
2071
|
~scheduler,
|
|
2037
2072
|
~resourceNaming=LocalPluginSpec.resourceNaming,
|
|
2038
2073
|
~api=(),
|
|
2039
2074
|
~apiRole=(),
|
|
2040
|
-
~stateChangeSlices=[module(UiFragmentRegistrySlice)
|
|
2041
|
-
~stateViewSlices=[module(UiFragmentsViewSlice)
|
|
2075
|
+
~stateChangeSlices=[module(UiFragmentRegistrySlice)],
|
|
2076
|
+
~stateViewSlices=[module(UiFragmentsViewSlice)],
|
|
2042
2077
|
~automationSlices=[],
|
|
2043
2078
|
~outboundTranslationSlices=[],
|
|
2044
2079
|
~inboundTranslationSlices=[],
|
|
@@ -2198,14 +2233,14 @@ module MakeWithConfig = (
|
|
|
2198
2233
|
let admin = Admin.construct(
|
|
2199
2234
|
~version="",
|
|
2200
2235
|
~extensionPoints=[],
|
|
2201
|
-
~aggregates=[module(LocalPluginAggregate)],
|
|
2202
|
-
~readModels=[module(PluginsReadModel)],
|
|
2236
|
+
~aggregates=[module(LocalPluginAggregate), module(LocalApiFragmentRegistryAggregate)],
|
|
2237
|
+
~readModels=[module(PluginsReadModel), module(ApiFragmentsReadModel)],
|
|
2203
2238
|
~scheduler,
|
|
2204
2239
|
~resourceNaming=LocalPluginSpec.resourceNaming,
|
|
2205
2240
|
~api=(),
|
|
2206
2241
|
~apiRole=(),
|
|
2207
|
-
~stateChangeSlices=[module(UiFragmentRegistrySlice)
|
|
2208
|
-
~stateViewSlices=[module(UiFragmentsViewSlice)
|
|
2242
|
+
~stateChangeSlices=[module(UiFragmentRegistrySlice)],
|
|
2243
|
+
~stateViewSlices=[module(UiFragmentsViewSlice)],
|
|
2209
2244
|
~automationSlices=[],
|
|
2210
2245
|
~outboundTranslationSlices=[],
|
|
2211
2246
|
~inboundTranslationSlices=[],
|