@reventlessdev/reventless-core 3.0.0-alpha.155 → 3.0.0-alpha.157
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 +23 -0
- package/README.md +49 -150
- package/package.json +13 -13
- package/src/admin/AdminApi.res +80 -10
- package/src/admin/AdminApi.res.mjs +45 -7
- package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry.res +65 -0
- package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry.res.mjs +87 -0
- package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry_Behavior.res +58 -0
- package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry_Behavior.res.mjs +135 -0
- package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments.res +44 -0
- package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments.res.mjs +74 -0
- package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments_Projection.res +50 -0
- package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments_Projection.res.mjs +80 -0
- package/src/admin/Platform_Admin.res +37 -1
- package/src/admin/Platform_Admin.res.mjs +18 -5
- package/src/admin/Platform_ApiFragmentsApi.res +32 -0
- package/src/admin/Platform_ApiFragmentsApi.res.mjs +52 -0
- package/src/admin/Platform_UIFragmentsApi.res +1 -1
- package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res +50 -0
- package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +70 -0
- package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry_Behavior.res +33 -0
- package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry_Behavior.res.mjs +80 -0
- package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments.res +33 -0
- package/src/admin/{UIFragmentRegistryReadModelSpec.res.mjs → UiFragmentRegistry/StateViewSlice/UiFragments.res.mjs} +24 -2
- package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res +34 -0
- package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res.mjs +54 -0
- package/src/components/Api/Api_Naming.res +1 -1
- package/src/components/Api/GraphQL_FragmentGenerator.res +7 -11
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +8 -22
- package/src/components/Api/GraphQL_PushPlanner.res +65 -0
- package/src/components/Api/GraphQL_PushPlanner.res.mjs +46 -0
- package/src/components/Api/GraphQL_Stitcher.res +72 -1
- package/src/components/Api/GraphQL_Stitcher.res.mjs +53 -2
- package/src/components/Dcb/Dcb_Builder.res +16 -6
- package/src/components/Dcb/Dcb_Builder.res.mjs +10 -7
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +6 -1
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +1 -1
- package/src/plugin/api/PluginBaseFragment.res +5 -6
- package/src/plugin/api/PluginBaseFragment.res.mjs +0 -1
- package/src/plugin/component/Plugin_Builder.res +4 -1
- package/src/plugin/component/Plugin_Builder.res.mjs +7 -6
- package/src/plugin/component/Plugin_Helpers.res +30 -0
- package/src/plugin/component/Plugin_Helpers.res.mjs +13 -3
- package/src/plugin/component/Plugin_SubscriptionSchema.res +19 -9
- package/src/plugin/component/Plugin_SubscriptionSchema.res.mjs +23 -14
- package/src/plugin/connect/PluginConnectExtension_Mapping.res +28 -6
- package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +34 -14
- package/src/plugin/connect/PluginExtensionPoint_Builder.res +7 -1
- package/src/plugin/connect/PluginExtensionPoint_Builder.res.mjs +5 -1
- package/src/plugin/connect/PluginExtensionPoint_Plugin.res +3 -1
- package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +4 -2
- package/src/plugin/connect/PluginExtensionPoint_UiFragment.res +52 -0
- package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +74 -0
- package/src/plugin/lifecycle/PluginBehavior.res +9 -51
- package/src/plugin/lifecycle/PluginBehavior.res.mjs +39 -68
- package/src/plugin/lifecycle/PluginSpec.res +0 -18
- package/src/plugin/lifecycle/PluginSpec.res.mjs +0 -30
- package/src/plugin/lifecycle/PluginsProjection.res +2 -6
- package/src/plugin/lifecycle/PluginsProjection.res.mjs +0 -2
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
- package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +0 -1
- package/tests/admin/Platform_ApiFragmentsApiTest.res +66 -0
- package/tests/admin/Platform_ApiFragmentsApiTest.res.mjs +68 -0
- package/tests/admin/Platform_UIFragmentsApiTest.res +1 -1
- package/tests/api/GraphQL_PushPlannerTest.res +62 -0
- package/tests/api/GraphQL_PushPlannerTest.res.mjs +80 -0
- package/tests/api/GraphQL_StitcherTest.res +63 -0
- package/tests/api/GraphQL_StitcherTest.res.mjs +75 -0
- package/tests/logger/LogFormatTest.res +1 -2
- package/tests/logger/LogFormatTest.res.mjs +1 -2
- package/tests/message/MessageTest.res +0 -3
- package/tests/message/MessageTest.res.mjs +0 -3
- package/src/admin/UIFragmentRegistryProjection.res +0 -43
- package/src/admin/UIFragmentRegistryProjection.res.mjs +0 -81
- package/src/admin/UIFragmentRegistryReadModelSpec.res +0 -13
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
|
|
5
|
+
import * as Plugin$ReventlessCore from "../plugin/component/Plugin.res.mjs";
|
|
6
|
+
|
|
7
|
+
let sdlTypes = [`type Platform_ApiFragmentEntry {\n pluginId: String!\n apiTarget: String!\n pushStatus: String!\n pushMessage: String!\n pushedAt: String!\n registeredAt: String!\n updatedAt: String!\n}`];
|
|
8
|
+
|
|
9
|
+
let queryFieldName = "Platform_ApiFragments";
|
|
10
|
+
|
|
11
|
+
let sdlQueryField = ` ` + queryFieldName + `: [Platform_ApiFragmentEntry!]!`;
|
|
12
|
+
|
|
13
|
+
function encodeApiFragmentEntry(entry) {
|
|
14
|
+
return Object.fromEntries([
|
|
15
|
+
[
|
|
16
|
+
"pluginId",
|
|
17
|
+
Plugin$ReventlessCore.name(entry.pluginId)
|
|
18
|
+
],
|
|
19
|
+
[
|
|
20
|
+
"apiTarget",
|
|
21
|
+
S.reverseConvertToJsonOrThrow(entry.apiTarget, Plugin$Reventless.apiTargetSchema)
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"pushStatus",
|
|
25
|
+
entry.pushStatus
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"pushMessage",
|
|
29
|
+
entry.pushMessage
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
"pushedAt",
|
|
33
|
+
entry.pushedAt
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"registeredAt",
|
|
37
|
+
entry.registeredAt
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"updatedAt",
|
|
41
|
+
entry.updatedAt
|
|
42
|
+
]
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
sdlTypes,
|
|
48
|
+
queryFieldName,
|
|
49
|
+
sdlQueryField,
|
|
50
|
+
encodeApiFragmentEntry,
|
|
51
|
+
}
|
|
52
|
+
/* S Not a pure module */
|
|
@@ -44,7 +44,7 @@ let encodePage = (p: pageManifestEntry): JSON.t =>
|
|
|
44
44
|
("requiredAccess", p.requiredAccess->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
|
|
45
45
|
])->JSON.Encode.object
|
|
46
46
|
|
|
47
|
-
let encodeUIFragmentEntry = (entry:
|
|
47
|
+
let encodeUIFragmentEntry = (entry: UiFragments.state): JSON.t =>
|
|
48
48
|
Dict.fromArray([
|
|
49
49
|
("pluginId", JSON.Encode.string(Plugin.name(entry.pluginId))),
|
|
50
50
|
("remoteEntryUrl", JSON.Encode.string(entry.remoteEntryUrl)),
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// UiFragmentRegistry StateChangeSlice — the write side of the platform UI-fragment
|
|
2
|
+
// registry, extracted off the Plugin aggregate (see
|
|
3
|
+
// docs/plans/event-sourced-fragment-registries.md).
|
|
4
|
+
//
|
|
5
|
+
// Commands are driven by the plugin runtime's connect handshake and routed here by the
|
|
6
|
+
// admin PluginExtensionPoint's UI-fragment mapping (RegisterUiFragment on ConnectPlugin,
|
|
7
|
+
// DeregisterUiFragment on DisconnectPlugin — the disconnect schedule sends DisconnectPlugin
|
|
8
|
+
// on heartbeat timeout too, so timeout deregistration flows through the same path).
|
|
9
|
+
//
|
|
10
|
+
// Keyed by plugin NAME (the `pluginId` tag carries the bare name), so a single row tracks
|
|
11
|
+
// the current manifest across version supersession. The registry is idempotent: re-sending
|
|
12
|
+
// an identical manifest is a no-op, deregistering an absent fragment is a no-op — it never
|
|
13
|
+
// rejects a command.
|
|
14
|
+
@@reventless.spec
|
|
15
|
+
|
|
16
|
+
open Reventless.Plugin
|
|
17
|
+
|
|
18
|
+
// `at` is the registration timestamp, stamped from the incoming command meta by the EP mapping
|
|
19
|
+
// — the StateViewSlice projection has no event meta, so time must ride the payload. Inline
|
|
20
|
+
// records let the ppx auto-tag `pluginId` (scoping the decision read to one plugin name).
|
|
21
|
+
//
|
|
22
|
+
// Whole-command `@noApi`: this registry has NO GraphQL surface — commands are driven by the
|
|
23
|
+
// plugin runtime's connect/disconnect handshake and routed here by the admin
|
|
24
|
+
// PluginExtensionPoint mapping (AWS) / direct admin-DCB dispatch (local), never by a mutation.
|
|
25
|
+
// Without this marker the DCB builder would emit orphaned `Platform_RegisterUiFragment`
|
|
26
|
+
// resolvers against a schema that never declares those fields.
|
|
27
|
+
@noApi @schema
|
|
28
|
+
type command =
|
|
29
|
+
| RegisterUiFragment({pluginId: string, manifest: uiFragmentManifest, at: string})
|
|
30
|
+
| DeregisterUiFragment({pluginId: string})
|
|
31
|
+
|
|
32
|
+
// The registry never rejects (idempotent register/deregister); this variant is never returned.
|
|
33
|
+
@schema
|
|
34
|
+
type error = | RegistryError
|
|
35
|
+
|
|
36
|
+
@schema
|
|
37
|
+
type event =
|
|
38
|
+
| UiFragmentRegistered({pluginId: string, manifest: uiFragmentManifest, at: string})
|
|
39
|
+
| UiFragmentUpdated({
|
|
40
|
+
pluginId: string,
|
|
41
|
+
previousManifest: uiFragmentManifest,
|
|
42
|
+
newManifest: uiFragmentManifest,
|
|
43
|
+
at: string,
|
|
44
|
+
})
|
|
45
|
+
| UiFragmentDeregistered({pluginId: string})
|
|
46
|
+
|
|
47
|
+
// This slice reads exactly the events it writes, so consumedEvent IS event — one inline event
|
|
48
|
+
// type, no duplicate-constructor collision, ppx auto-tags `pluginId` on both roles.
|
|
49
|
+
@schema
|
|
50
|
+
type consumedEvent = event
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
5
|
+
import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
|
|
6
|
+
import * as Api$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/Api.res.mjs";
|
|
7
|
+
|
|
8
|
+
let commandSchema = S.union([
|
|
9
|
+
S.schema(s => ({
|
|
10
|
+
TAG: "RegisterUiFragment",
|
|
11
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
12
|
+
manifest: s.m(Plugin$Reventless.uiFragmentManifestSchema),
|
|
13
|
+
at: s.m(S.string)
|
|
14
|
+
})),
|
|
15
|
+
S.schema(s => ({
|
|
16
|
+
TAG: "DeregisterUiFragment",
|
|
17
|
+
pluginId: s.m(DcbTag$Reventless.string)
|
|
18
|
+
}))
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
let errorSchema = S.literal("RegistryError");
|
|
22
|
+
|
|
23
|
+
let eventSchema = S.union([
|
|
24
|
+
S.schema(s => ({
|
|
25
|
+
TAG: "UiFragmentRegistered",
|
|
26
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
27
|
+
manifest: s.m(Plugin$Reventless.uiFragmentManifestSchema),
|
|
28
|
+
at: s.m(S.string)
|
|
29
|
+
})),
|
|
30
|
+
S.schema(s => ({
|
|
31
|
+
TAG: "UiFragmentUpdated",
|
|
32
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
33
|
+
previousManifest: s.m(Plugin$Reventless.uiFragmentManifestSchema),
|
|
34
|
+
newManifest: s.m(Plugin$Reventless.uiFragmentManifestSchema),
|
|
35
|
+
at: s.m(S.string)
|
|
36
|
+
})),
|
|
37
|
+
S.schema(s => ({
|
|
38
|
+
TAG: "UiFragmentDeregistered",
|
|
39
|
+
pluginId: s.m(DcbTag$Reventless.string)
|
|
40
|
+
}))
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
let commandSchema$1 = Api$ReventlessInfra.markNoApi(commandSchema);
|
|
44
|
+
|
|
45
|
+
function commandAuthorization(param) {
|
|
46
|
+
return "AllowAuthenticated";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let name = "UiFragmentRegistry";
|
|
50
|
+
|
|
51
|
+
let Id;
|
|
52
|
+
|
|
53
|
+
let consumedEventSchema = eventSchema;
|
|
54
|
+
|
|
55
|
+
let moduleUrl = "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs";
|
|
56
|
+
|
|
57
|
+
let readConsistency = "EscalateOnRetry";
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
name,
|
|
61
|
+
Id,
|
|
62
|
+
errorSchema,
|
|
63
|
+
eventSchema,
|
|
64
|
+
consumedEventSchema,
|
|
65
|
+
commandSchema$1 as commandSchema,
|
|
66
|
+
moduleUrl,
|
|
67
|
+
commandAuthorization,
|
|
68
|
+
readConsistency,
|
|
69
|
+
}
|
|
70
|
+
/* commandSchema Not a pure module */
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
@@reventless.behavior
|
|
2
|
+
|
|
3
|
+
open Reventless.Plugin
|
|
4
|
+
|
|
5
|
+
// The current manifest for this plugin name, or None when no fragment is registered.
|
|
6
|
+
type state = option<uiFragmentManifest>
|
|
7
|
+
|
|
8
|
+
let initialState = None
|
|
9
|
+
|
|
10
|
+
let evolve = (_state, event) =>
|
|
11
|
+
switch event {
|
|
12
|
+
| UiFragmentRegistered({manifest}) => Some(manifest)
|
|
13
|
+
| UiFragmentUpdated({newManifest}) => Some(newManifest)
|
|
14
|
+
| UiFragmentDeregistered(_) => None
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let decide = (state, command) =>
|
|
18
|
+
switch command {
|
|
19
|
+
| RegisterUiFragment({pluginId, manifest, at}) =>
|
|
20
|
+
switch state {
|
|
21
|
+
| None => Ok([UiFragmentRegistered({pluginId, manifest, at})])
|
|
22
|
+
// Idempotent: an unchanged re-registration (e.g. a redeploy carrying the same
|
|
23
|
+
// manifest, or a reconnect) emits nothing.
|
|
24
|
+
| Some(current) if current == manifest => Ok([])
|
|
25
|
+
| Some(current) =>
|
|
26
|
+
Ok([UiFragmentUpdated({pluginId, previousManifest: current, newManifest: manifest, at})])
|
|
27
|
+
}
|
|
28
|
+
| DeregisterUiFragment({pluginId}) =>
|
|
29
|
+
switch state {
|
|
30
|
+
| None => Ok([]) // idempotent — nothing registered
|
|
31
|
+
| Some(_) => Ok([UiFragmentDeregistered({pluginId: pluginId})])
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
4
|
+
|
|
5
|
+
function evolve(_state, event) {
|
|
6
|
+
switch (event.TAG) {
|
|
7
|
+
case "UiFragmentRegistered" :
|
|
8
|
+
return event.manifest;
|
|
9
|
+
case "UiFragmentUpdated" :
|
|
10
|
+
return event.newManifest;
|
|
11
|
+
case "UiFragmentDeregistered" :
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function decide(state, command) {
|
|
17
|
+
if (command.TAG !== "RegisterUiFragment") {
|
|
18
|
+
if (state !== undefined) {
|
|
19
|
+
return {
|
|
20
|
+
TAG: "Ok",
|
|
21
|
+
_0: [{
|
|
22
|
+
TAG: "UiFragmentDeregistered",
|
|
23
|
+
pluginId: command.pluginId
|
|
24
|
+
}]
|
|
25
|
+
};
|
|
26
|
+
} else {
|
|
27
|
+
return {
|
|
28
|
+
TAG: "Ok",
|
|
29
|
+
_0: []
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
let at = command.at;
|
|
34
|
+
let manifest = command.manifest;
|
|
35
|
+
let pluginId = command.pluginId;
|
|
36
|
+
if (state !== undefined) {
|
|
37
|
+
if (Primitive_object.equal(state, manifest)) {
|
|
38
|
+
return {
|
|
39
|
+
TAG: "Ok",
|
|
40
|
+
_0: []
|
|
41
|
+
};
|
|
42
|
+
} else {
|
|
43
|
+
return {
|
|
44
|
+
TAG: "Ok",
|
|
45
|
+
_0: [{
|
|
46
|
+
TAG: "UiFragmentUpdated",
|
|
47
|
+
pluginId: pluginId,
|
|
48
|
+
previousManifest: state,
|
|
49
|
+
newManifest: manifest,
|
|
50
|
+
at: at
|
|
51
|
+
}]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
return {
|
|
56
|
+
TAG: "Ok",
|
|
57
|
+
_0: [{
|
|
58
|
+
TAG: "UiFragmentRegistered",
|
|
59
|
+
pluginId: pluginId,
|
|
60
|
+
manifest: manifest,
|
|
61
|
+
at: at
|
|
62
|
+
}]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let Spec;
|
|
68
|
+
|
|
69
|
+
let initialState;
|
|
70
|
+
|
|
71
|
+
let moduleUrl = "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry_Behavior.res.mjs";
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
Spec,
|
|
75
|
+
initialState,
|
|
76
|
+
evolve,
|
|
77
|
+
decide,
|
|
78
|
+
moduleUrl,
|
|
79
|
+
}
|
|
80
|
+
/* No side effect */
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// UiFragments StateViewSlice — the read side of the platform UI-fragment registry,
|
|
2
|
+
// replacing UIFragmentRegistryReadModelSpec + UIFragmentRegistryProjection. Projects the
|
|
3
|
+
// UiFragmentRegistry slice's events (from the shared admin DcbEventLog) into the
|
|
4
|
+
// manifest-per-plugin table that backs the Platform_UIFragments query. The wire shape is
|
|
5
|
+
// owned by Platform_UIFragmentsApi.res (SDL + encoder), pinned by the golden test
|
|
6
|
+
// Platform_UIFragmentsApiTest.res — `state` here matches that encoder byte-for-byte.
|
|
7
|
+
@@reventless.spec
|
|
8
|
+
|
|
9
|
+
open Reventless.Plugin
|
|
10
|
+
|
|
11
|
+
@schema
|
|
12
|
+
type state = {
|
|
13
|
+
pluginId: string,
|
|
14
|
+
remoteEntryUrl: string,
|
|
15
|
+
panels: array<panelManifestEntry>,
|
|
16
|
+
pages: array<pageManifestEntry>,
|
|
17
|
+
registeredAt: string,
|
|
18
|
+
updatedAt: string,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Consumes the UiFragmentRegistry slice's events (from the shared admin DcbEventLog). Inline
|
|
22
|
+
// records with the same constructors/fields as the producer's `event` — decodes byte-identically
|
|
23
|
+
// and the ppx auto-tags `pluginId`.
|
|
24
|
+
@schema
|
|
25
|
+
type consumedEvent =
|
|
26
|
+
| UiFragmentRegistered({pluginId: string, manifest: uiFragmentManifest, at: string})
|
|
27
|
+
| UiFragmentUpdated({
|
|
28
|
+
pluginId: string,
|
|
29
|
+
previousManifest: uiFragmentManifest,
|
|
30
|
+
newManifest: uiFragmentManifest,
|
|
31
|
+
at: string,
|
|
32
|
+
})
|
|
33
|
+
| UiFragmentDeregistered({pluginId: string})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
4
5
|
import * as Plugin$Reventless from "@reventlessdev/reventless-spec/src/components/Plugin.res.mjs";
|
|
5
6
|
import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
|
|
6
7
|
|
|
@@ -13,15 +14,35 @@ let stateSchema = S.schema(s => ({
|
|
|
13
14
|
updatedAt: s.m(S.string)
|
|
14
15
|
}));
|
|
15
16
|
|
|
17
|
+
let consumedEventSchema = S.union([
|
|
18
|
+
S.schema(s => ({
|
|
19
|
+
TAG: "UiFragmentRegistered",
|
|
20
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
21
|
+
manifest: s.m(Plugin$Reventless.uiFragmentManifestSchema),
|
|
22
|
+
at: s.m(S.string)
|
|
23
|
+
})),
|
|
24
|
+
S.schema(s => ({
|
|
25
|
+
TAG: "UiFragmentUpdated",
|
|
26
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
27
|
+
previousManifest: s.m(Plugin$Reventless.uiFragmentManifestSchema),
|
|
28
|
+
newManifest: s.m(Plugin$Reventless.uiFragmentManifestSchema),
|
|
29
|
+
at: s.m(S.string)
|
|
30
|
+
})),
|
|
31
|
+
S.schema(s => ({
|
|
32
|
+
TAG: "UiFragmentDeregistered",
|
|
33
|
+
pluginId: s.m(DcbTag$Reventless.string)
|
|
34
|
+
}))
|
|
35
|
+
]);
|
|
36
|
+
|
|
16
37
|
let config = ReadModel$Reventless.config(undefined, undefined, undefined);
|
|
17
38
|
|
|
18
|
-
let name = "
|
|
39
|
+
let name = "UiFragments";
|
|
19
40
|
|
|
20
41
|
let Id;
|
|
21
42
|
|
|
22
43
|
let subIdConfig;
|
|
23
44
|
|
|
24
|
-
let moduleUrl = "@reventlessdev/reventless-core/src/admin/
|
|
45
|
+
let moduleUrl = "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments.res.mjs";
|
|
25
46
|
|
|
26
47
|
let authorization = "AllowAuthenticated";
|
|
27
48
|
|
|
@@ -31,6 +52,7 @@ export {
|
|
|
31
52
|
name,
|
|
32
53
|
Id,
|
|
33
54
|
stateSchema,
|
|
55
|
+
consumedEventSchema,
|
|
34
56
|
config,
|
|
35
57
|
subIdConfig,
|
|
36
58
|
moduleUrl,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@@reventless.projection
|
|
2
|
+
|
|
3
|
+
// No `open Reventless.Plugin`: it would shadow the state labels (panels/pages/...) brought in
|
|
4
|
+
// by the projection's spec open. `manifest.<field>` resolves type-directed from the event's
|
|
5
|
+
// uiFragmentManifest payload.
|
|
6
|
+
let project = event =>
|
|
7
|
+
switch event {
|
|
8
|
+
| UiFragmentRegistered({pluginId, manifest, at}) => [
|
|
9
|
+
Set(
|
|
10
|
+
pluginId,
|
|
11
|
+
{
|
|
12
|
+
pluginId,
|
|
13
|
+
remoteEntryUrl: manifest.remoteEntryUrl,
|
|
14
|
+
panels: manifest.panels,
|
|
15
|
+
pages: manifest.pages,
|
|
16
|
+
registeredAt: at,
|
|
17
|
+
updatedAt: at,
|
|
18
|
+
},
|
|
19
|
+
),
|
|
20
|
+
]
|
|
21
|
+
| UiFragmentUpdated({pluginId, newManifest, at}) => [
|
|
22
|
+
Update(
|
|
23
|
+
pluginId,
|
|
24
|
+
state => {
|
|
25
|
+
...state,
|
|
26
|
+
remoteEntryUrl: newManifest.remoteEntryUrl,
|
|
27
|
+
panels: newManifest.panels,
|
|
28
|
+
pages: newManifest.pages,
|
|
29
|
+
updatedAt: at,
|
|
30
|
+
},
|
|
31
|
+
),
|
|
32
|
+
]
|
|
33
|
+
| UiFragmentDeregistered({pluginId}) => [Delete(pluginId)]
|
|
34
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function project(event) {
|
|
5
|
+
switch (event.TAG) {
|
|
6
|
+
case "UiFragmentRegistered" :
|
|
7
|
+
let at = event.at;
|
|
8
|
+
let manifest = event.manifest;
|
|
9
|
+
let pluginId = event.pluginId;
|
|
10
|
+
return [{
|
|
11
|
+
TAG: "Set",
|
|
12
|
+
_0: pluginId,
|
|
13
|
+
_1: {
|
|
14
|
+
pluginId: pluginId,
|
|
15
|
+
remoteEntryUrl: manifest.remoteEntryUrl,
|
|
16
|
+
panels: manifest.panels,
|
|
17
|
+
pages: manifest.pages,
|
|
18
|
+
registeredAt: at,
|
|
19
|
+
updatedAt: at
|
|
20
|
+
}
|
|
21
|
+
}];
|
|
22
|
+
case "UiFragmentUpdated" :
|
|
23
|
+
let at$1 = event.at;
|
|
24
|
+
let newManifest = event.newManifest;
|
|
25
|
+
return [{
|
|
26
|
+
TAG: "Update",
|
|
27
|
+
_0: event.pluginId,
|
|
28
|
+
_1: state => ({
|
|
29
|
+
pluginId: state.pluginId,
|
|
30
|
+
remoteEntryUrl: newManifest.remoteEntryUrl,
|
|
31
|
+
panels: newManifest.panels,
|
|
32
|
+
pages: newManifest.pages,
|
|
33
|
+
registeredAt: state.registeredAt,
|
|
34
|
+
updatedAt: at$1
|
|
35
|
+
})
|
|
36
|
+
}];
|
|
37
|
+
case "UiFragmentDeregistered" :
|
|
38
|
+
return [{
|
|
39
|
+
TAG: "Delete",
|
|
40
|
+
_0: event.pluginId
|
|
41
|
+
}];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let Spec;
|
|
46
|
+
|
|
47
|
+
let moduleUrl = "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res.mjs";
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
Spec,
|
|
51
|
+
project,
|
|
52
|
+
moduleUrl,
|
|
53
|
+
}
|
|
54
|
+
/* No side effect */
|
|
@@ -49,7 +49,7 @@ let sliceMutationField = (~plugin: string, ~slice: string) => `${plugin}_${slice
|
|
|
49
49
|
let dcbCommandMutationField = (~plugin: string, ~command: string) => `${plugin}_${command}`
|
|
50
50
|
|
|
51
51
|
// AppSync hard limit: subscription field names may be at most 50 chars. Each mutation
|
|
52
|
-
// field `f` produces a
|
|
52
|
+
// field `f` produces a mutation-sourced subscription `on${f}` (see
|
|
53
53
|
// Plugin_SubscriptionSchema.sourceCFields), so the real cap on a mutation field is 48.
|
|
54
54
|
// Fail at build with an actionable message rather than letting AppSync reject the
|
|
55
55
|
// schema push at deploy time with an opaque 500.
|
|
@@ -598,15 +598,11 @@ let generate = (
|
|
|
598
598
|
commandResultSdlTypes->Array.forEach(t => types->Array.push(t))
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
]),
|
|
609
|
-
)->JSON.stringify
|
|
610
|
-
|
|
611
|
-
{Reventless.Plugin.encoded, protocol: "graphql"}
|
|
601
|
+
GraphQL_Stitcher.encode({
|
|
602
|
+
types,
|
|
603
|
+
mutations,
|
|
604
|
+
queries,
|
|
605
|
+
subscriptions: [],
|
|
606
|
+
subscriptionSources: [],
|
|
607
|
+
})
|
|
612
608
|
}
|
|
@@ -5,6 +5,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
5
5
|
import * as Stdlib_String from "@rescript/runtime/lib/es6/Stdlib_String.js";
|
|
6
6
|
import * as SchemaType$ReventlessCore from "./SchemaType.res.mjs";
|
|
7
7
|
import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
|
|
8
|
+
import * as GraphQL_Stitcher$ReventlessCore from "./GraphQL_Stitcher.res.mjs";
|
|
8
9
|
|
|
9
10
|
let commandResultSdlTypes = [
|
|
10
11
|
`union CommandResult = CommandAccepted | CommandRejected | CommandPending`,
|
|
@@ -453,28 +454,13 @@ function generate(mutationEntries, queryEntries) {
|
|
|
453
454
|
types.push(t);
|
|
454
455
|
});
|
|
455
456
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
],
|
|
461
|
-
[
|
|
462
|
-
|
|
463
|
-
mutations.map(prim => prim)
|
|
464
|
-
],
|
|
465
|
-
[
|
|
466
|
-
"queries",
|
|
467
|
-
queries.map(prim => prim)
|
|
468
|
-
],
|
|
469
|
-
[
|
|
470
|
-
"subscriptions",
|
|
471
|
-
[]
|
|
472
|
-
]
|
|
473
|
-
]));
|
|
474
|
-
return {
|
|
475
|
-
encoded: encoded,
|
|
476
|
-
protocol: "graphql"
|
|
477
|
-
};
|
|
457
|
+
return GraphQL_Stitcher$ReventlessCore.encode({
|
|
458
|
+
types: types,
|
|
459
|
+
mutations: mutations,
|
|
460
|
+
queries: queries,
|
|
461
|
+
subscriptions: [],
|
|
462
|
+
subscriptionSources: []
|
|
463
|
+
});
|
|
478
464
|
}
|
|
479
465
|
|
|
480
466
|
export {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Generic, provider-neutral push planner for the event-sourced API-schema fragment registry.
|
|
2
|
+
//
|
|
3
|
+
// Given the fragments currently in the ApiFragmentRegistry (each tagged with the API it belongs to)
|
|
4
|
+
// it groups them by target and produces one stitched-SDL push plan per API, honouring split vs.
|
|
5
|
+
// unified mode. It encodes the SAME base-selection rules the deploy-time push
|
|
6
|
+
// (`reventless-aws Platform.preResolversSchemaHook`) applies, so the reactive single writer stays
|
|
7
|
+
// consistent with the deploy path:
|
|
8
|
+
// - split mode: the Platform API carries the admin base + Platform-target fragments; the Domain
|
|
9
|
+
// API carries an EMPTY base + Domain-target fragments (admin lives only on the Platform API).
|
|
10
|
+
// - unified mode: a single API carries the admin base + ALL fragments regardless of target.
|
|
11
|
+
//
|
|
12
|
+
// Neutral SDL only — the AWS adapter injects @aws_subscribe per plan and pushes each to the
|
|
13
|
+
// matching AppSync API id; the local platform (scope 2) will consume the same plans in-process.
|
|
14
|
+
|
|
15
|
+
open Reventless.Plugin
|
|
16
|
+
|
|
17
|
+
type targetedFragment = {
|
|
18
|
+
fragment: apiSchemaFragment,
|
|
19
|
+
target: apiTarget,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// The destination API of a push plan. In unified mode every fragment collapses onto `DomainApi`
|
|
23
|
+
// (the single shared API); the AWS adapter maps `PlatformApi` → the Platform AppSync id and
|
|
24
|
+
// `DomainApi` → the Domain AppSync id.
|
|
25
|
+
@schema
|
|
26
|
+
type planTarget = DomainApi | PlatformApi
|
|
27
|
+
|
|
28
|
+
type pushPlan = {
|
|
29
|
+
api: planTarget,
|
|
30
|
+
// Stitched neutral SDL for that API (admin/empty base + the target's fragments).
|
|
31
|
+
sdl: string,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// An empty base fragment — the Domain API's base in split mode (admin is on the Platform API).
|
|
35
|
+
let emptyBase: apiSchemaFragment = GraphQL_Stitcher.encode({
|
|
36
|
+
types: [],
|
|
37
|
+
queries: [],
|
|
38
|
+
mutations: [],
|
|
39
|
+
subscriptions: [],
|
|
40
|
+
subscriptionSources: [],
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
let planPushes = (
|
|
44
|
+
~adminBase: apiSchemaFragment,
|
|
45
|
+
~fragments: array<targetedFragment>,
|
|
46
|
+
~splitApi: bool,
|
|
47
|
+
): array<pushPlan> =>
|
|
48
|
+
if splitApi {
|
|
49
|
+
let platformFrags =
|
|
50
|
+
fragments->Array.filter(f => f.target == Platform)->Array.map(f => f.fragment)
|
|
51
|
+
let domainFrags = fragments->Array.filter(f => f.target == Domain)->Array.map(f => f.fragment)
|
|
52
|
+
[
|
|
53
|
+
{
|
|
54
|
+
api: PlatformApi,
|
|
55
|
+
sdl: GraphQL_Stitcher.stitch(~baseFragment=adminBase, ~pluginFragments=platformFrags),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
api: DomainApi,
|
|
59
|
+
sdl: GraphQL_Stitcher.stitch(~baseFragment=emptyBase, ~pluginFragments=domainFrags),
|
|
60
|
+
},
|
|
61
|
+
]
|
|
62
|
+
} else {
|
|
63
|
+
let allFrags = fragments->Array.map(f => f.fragment)
|
|
64
|
+
[{api: DomainApi, sdl: GraphQL_Stitcher.stitch(~baseFragment=adminBase, ~pluginFragments=allFrags)}]
|
|
65
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as GraphQL_Stitcher$ReventlessCore from "./GraphQL_Stitcher.res.mjs";
|
|
5
|
+
|
|
6
|
+
let planTargetSchema = S.union([
|
|
7
|
+
S.literal("DomainApi"),
|
|
8
|
+
S.literal("PlatformApi")
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
let emptyBase = GraphQL_Stitcher$ReventlessCore.encode({
|
|
12
|
+
types: [],
|
|
13
|
+
mutations: [],
|
|
14
|
+
queries: [],
|
|
15
|
+
subscriptions: [],
|
|
16
|
+
subscriptionSources: []
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function planPushes(adminBase, fragments, splitApi) {
|
|
20
|
+
if (splitApi) {
|
|
21
|
+
let platformFrags = fragments.filter(f => f.target === "Platform").map(f => f.fragment);
|
|
22
|
+
let domainFrags = fragments.filter(f => f.target === "Domain").map(f => f.fragment);
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
api: "PlatformApi",
|
|
26
|
+
sdl: GraphQL_Stitcher$ReventlessCore.stitch(adminBase, platformFrags)
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
api: "DomainApi",
|
|
30
|
+
sdl: GraphQL_Stitcher$ReventlessCore.stitch(emptyBase, domainFrags)
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
let allFrags = fragments.map(f => f.fragment);
|
|
35
|
+
return [{
|
|
36
|
+
api: "DomainApi",
|
|
37
|
+
sdl: GraphQL_Stitcher$ReventlessCore.stitch(adminBase, allFrags)
|
|
38
|
+
}];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
planTargetSchema,
|
|
43
|
+
emptyBase,
|
|
44
|
+
planPushes,
|
|
45
|
+
}
|
|
46
|
+
/* planTargetSchema Not a pure module */
|