@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,87 @@
|
|
|
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: "RegisterApiFragment",
|
|
11
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
12
|
+
fragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
13
|
+
apiTarget: s.m(Plugin$Reventless.apiTargetSchema),
|
|
14
|
+
at: s.m(S.string)
|
|
15
|
+
})),
|
|
16
|
+
S.schema(s => ({
|
|
17
|
+
TAG: "DeregisterApiFragment",
|
|
18
|
+
pluginId: s.m(DcbTag$Reventless.string)
|
|
19
|
+
})),
|
|
20
|
+
S.schema(s => ({
|
|
21
|
+
TAG: "RecordApiFragmentPush",
|
|
22
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
23
|
+
ok: s.m(S.bool),
|
|
24
|
+
message: s.m(S.string),
|
|
25
|
+
at: s.m(S.string)
|
|
26
|
+
}))
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
let errorSchema = S.literal("RegistryError");
|
|
30
|
+
|
|
31
|
+
let eventSchema = S.union([
|
|
32
|
+
S.schema(s => ({
|
|
33
|
+
TAG: "ApiFragmentRegistered",
|
|
34
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
35
|
+
fragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
36
|
+
apiTarget: s.m(Plugin$Reventless.apiTargetSchema),
|
|
37
|
+
at: s.m(S.string)
|
|
38
|
+
})),
|
|
39
|
+
S.schema(s => ({
|
|
40
|
+
TAG: "ApiFragmentUpdated",
|
|
41
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
42
|
+
previousFragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
43
|
+
newFragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
44
|
+
apiTarget: s.m(Plugin$Reventless.apiTargetSchema),
|
|
45
|
+
at: s.m(S.string)
|
|
46
|
+
})),
|
|
47
|
+
S.schema(s => ({
|
|
48
|
+
TAG: "ApiFragmentDeregistered",
|
|
49
|
+
pluginId: s.m(DcbTag$Reventless.string)
|
|
50
|
+
})),
|
|
51
|
+
S.schema(s => ({
|
|
52
|
+
TAG: "ApiFragmentPushRecorded",
|
|
53
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
54
|
+
ok: s.m(S.bool),
|
|
55
|
+
message: s.m(S.string),
|
|
56
|
+
at: s.m(S.string)
|
|
57
|
+
}))
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
let commandSchema$1 = Api$ReventlessInfra.markNoApiVariants(commandSchema, ["RecordApiFragmentPush"]);
|
|
61
|
+
|
|
62
|
+
function commandAuthorization(param) {
|
|
63
|
+
return "AllowAuthenticated";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let name = "ApiFragmentRegistry";
|
|
67
|
+
|
|
68
|
+
let Id;
|
|
69
|
+
|
|
70
|
+
let consumedEventSchema = eventSchema;
|
|
71
|
+
|
|
72
|
+
let moduleUrl = "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry.res.mjs";
|
|
73
|
+
|
|
74
|
+
let readConsistency = "EscalateOnRetry";
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
name,
|
|
78
|
+
Id,
|
|
79
|
+
errorSchema,
|
|
80
|
+
eventSchema,
|
|
81
|
+
consumedEventSchema,
|
|
82
|
+
commandSchema$1 as commandSchema,
|
|
83
|
+
moduleUrl,
|
|
84
|
+
commandAuthorization,
|
|
85
|
+
readConsistency,
|
|
86
|
+
}
|
|
87
|
+
/* commandSchema Not a pure module */
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
@@reventless.behavior
|
|
2
|
+
|
|
3
|
+
open Reventless.Plugin
|
|
4
|
+
|
|
5
|
+
// The current fragment for this plugin name plus its target API and the last recorded push
|
|
6
|
+
// outcome (kept only to dedupe at-least-once redelivery of RecordApiFragmentPush), or None
|
|
7
|
+
// when no fragment is registered.
|
|
8
|
+
type registryEntry = {
|
|
9
|
+
fragment: apiSchemaFragment,
|
|
10
|
+
apiTarget: apiTarget,
|
|
11
|
+
lastPush: option<(bool, string, string)>, // (ok, message, at)
|
|
12
|
+
}
|
|
13
|
+
type state = option<registryEntry>
|
|
14
|
+
|
|
15
|
+
let initialState = None
|
|
16
|
+
|
|
17
|
+
let evolve = (state, event) =>
|
|
18
|
+
switch event {
|
|
19
|
+
| ApiFragmentRegistered({fragment, apiTarget}) => Some({fragment, apiTarget, lastPush: None})
|
|
20
|
+
| ApiFragmentUpdated({newFragment, apiTarget}) =>
|
|
21
|
+
Some({fragment: newFragment, apiTarget, lastPush: None})
|
|
22
|
+
| ApiFragmentDeregistered(_) => None
|
|
23
|
+
| ApiFragmentPushRecorded({ok, message, at}) =>
|
|
24
|
+
state->Option.map(entry => {...entry, lastPush: Some((ok, message, at))})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let decide = (state, command) =>
|
|
28
|
+
switch command {
|
|
29
|
+
| RegisterApiFragment({pluginId, fragment, apiTarget, at}) =>
|
|
30
|
+
switch state {
|
|
31
|
+
| None => Ok([ApiFragmentRegistered({pluginId, fragment, apiTarget, at})])
|
|
32
|
+
// Idempotent: an unchanged re-registration (same fragment AND same target — e.g. a
|
|
33
|
+
// redeploy carrying the same schema, or a version supersession that didn't change the
|
|
34
|
+
// SDL) emits nothing. A target change with an identical fragment IS meaningful (the
|
|
35
|
+
// fields move between the Domain and Platform APIs), so it is not a no-op.
|
|
36
|
+
| Some({fragment: current, apiTarget: currentTarget})
|
|
37
|
+
if current == fragment && currentTarget == apiTarget =>
|
|
38
|
+
Ok([])
|
|
39
|
+
| Some({fragment: current}) =>
|
|
40
|
+
Ok([
|
|
41
|
+
ApiFragmentUpdated({pluginId, previousFragment: current, newFragment: fragment, apiTarget, at}),
|
|
42
|
+
])
|
|
43
|
+
}
|
|
44
|
+
| DeregisterApiFragment({pluginId}) =>
|
|
45
|
+
switch state {
|
|
46
|
+
| None => Ok([]) // idempotent — nothing registered
|
|
47
|
+
| Some(_) => Ok([ApiFragmentDeregistered({pluginId: pluginId})])
|
|
48
|
+
}
|
|
49
|
+
| RecordApiFragmentPush({pluginId, ok, message, at}) =>
|
|
50
|
+
switch state {
|
|
51
|
+
| None => Ok([]) // fragment deregistered in the meantime — nowhere to record
|
|
52
|
+
// Idempotent: an at-least-once redelivery carries the identical payload.
|
|
53
|
+
| Some({lastPush: Some((lastOk, lastMessage, lastAt))})
|
|
54
|
+
if lastOk == ok && lastMessage == message && lastAt == at =>
|
|
55
|
+
Ok([])
|
|
56
|
+
| Some(_) => Ok([ApiFragmentPushRecorded({pluginId, ok, message, at})])
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
5
|
+
|
|
6
|
+
function evolve(state, event) {
|
|
7
|
+
switch (event.TAG) {
|
|
8
|
+
case "ApiFragmentRegistered" :
|
|
9
|
+
return {
|
|
10
|
+
fragment: event.fragment,
|
|
11
|
+
apiTarget: event.apiTarget,
|
|
12
|
+
lastPush: undefined
|
|
13
|
+
};
|
|
14
|
+
case "ApiFragmentUpdated" :
|
|
15
|
+
return {
|
|
16
|
+
fragment: event.newFragment,
|
|
17
|
+
apiTarget: event.apiTarget,
|
|
18
|
+
lastPush: undefined
|
|
19
|
+
};
|
|
20
|
+
case "ApiFragmentDeregistered" :
|
|
21
|
+
return;
|
|
22
|
+
case "ApiFragmentPushRecorded" :
|
|
23
|
+
let at = event.at;
|
|
24
|
+
let message = event.message;
|
|
25
|
+
let ok = event.ok;
|
|
26
|
+
return Stdlib_Option.map(state, entry => ({
|
|
27
|
+
fragment: entry.fragment,
|
|
28
|
+
apiTarget: entry.apiTarget,
|
|
29
|
+
lastPush: [
|
|
30
|
+
ok,
|
|
31
|
+
message,
|
|
32
|
+
at
|
|
33
|
+
]
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function decide(state, command) {
|
|
39
|
+
switch (command.TAG) {
|
|
40
|
+
case "RegisterApiFragment" :
|
|
41
|
+
let at = command.at;
|
|
42
|
+
let apiTarget = command.apiTarget;
|
|
43
|
+
let fragment = command.fragment;
|
|
44
|
+
let pluginId = command.pluginId;
|
|
45
|
+
if (state === undefined) {
|
|
46
|
+
return {
|
|
47
|
+
TAG: "Ok",
|
|
48
|
+
_0: [{
|
|
49
|
+
TAG: "ApiFragmentRegistered",
|
|
50
|
+
pluginId: pluginId,
|
|
51
|
+
fragment: fragment,
|
|
52
|
+
apiTarget: apiTarget,
|
|
53
|
+
at: at
|
|
54
|
+
}]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
let current = state.fragment;
|
|
58
|
+
if (Primitive_object.equal(current, fragment) && state.apiTarget === apiTarget) {
|
|
59
|
+
return {
|
|
60
|
+
TAG: "Ok",
|
|
61
|
+
_0: []
|
|
62
|
+
};
|
|
63
|
+
} else {
|
|
64
|
+
return {
|
|
65
|
+
TAG: "Ok",
|
|
66
|
+
_0: [{
|
|
67
|
+
TAG: "ApiFragmentUpdated",
|
|
68
|
+
pluginId: pluginId,
|
|
69
|
+
previousFragment: current,
|
|
70
|
+
newFragment: fragment,
|
|
71
|
+
apiTarget: apiTarget,
|
|
72
|
+
at: at
|
|
73
|
+
}]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
case "DeregisterApiFragment" :
|
|
77
|
+
if (state !== undefined) {
|
|
78
|
+
return {
|
|
79
|
+
TAG: "Ok",
|
|
80
|
+
_0: [{
|
|
81
|
+
TAG: "ApiFragmentDeregistered",
|
|
82
|
+
pluginId: command.pluginId
|
|
83
|
+
}]
|
|
84
|
+
};
|
|
85
|
+
} else {
|
|
86
|
+
return {
|
|
87
|
+
TAG: "Ok",
|
|
88
|
+
_0: []
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
case "RecordApiFragmentPush" :
|
|
92
|
+
if (state === undefined) {
|
|
93
|
+
return {
|
|
94
|
+
TAG: "Ok",
|
|
95
|
+
_0: []
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
let match = state.lastPush;
|
|
99
|
+
let at$1 = command.at;
|
|
100
|
+
let message = command.message;
|
|
101
|
+
let ok = command.ok;
|
|
102
|
+
if (match !== undefined && match[0] === ok && match[1] === message && match[2] === at$1) {
|
|
103
|
+
return {
|
|
104
|
+
TAG: "Ok",
|
|
105
|
+
_0: []
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
TAG: "Ok",
|
|
110
|
+
_0: [{
|
|
111
|
+
TAG: "ApiFragmentPushRecorded",
|
|
112
|
+
pluginId: command.pluginId,
|
|
113
|
+
ok: ok,
|
|
114
|
+
message: message,
|
|
115
|
+
at: at$1
|
|
116
|
+
}]
|
|
117
|
+
};
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let Spec;
|
|
123
|
+
|
|
124
|
+
let initialState;
|
|
125
|
+
|
|
126
|
+
let moduleUrl = "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry_Behavior.res.mjs";
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
Spec,
|
|
130
|
+
initialState,
|
|
131
|
+
evolve,
|
|
132
|
+
decide,
|
|
133
|
+
moduleUrl,
|
|
134
|
+
}
|
|
135
|
+
/* No side effect */
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// ApiFragments StateViewSlice — the read side of the platform API-schema fragment
|
|
2
|
+
// registry: the current fragment per plugin name plus the outcome of the last schema
|
|
3
|
+
// push that included it. Replaces the raw `deploy-schema:*` rows as the durable source
|
|
4
|
+
// the single-writer stitch automation and the runtime re-stitcher read, and backs the
|
|
5
|
+
// push-status poll of the deploy waiter.
|
|
6
|
+
//
|
|
7
|
+
// `pushStatus` is "pending" from registration/update until the automation records the
|
|
8
|
+
// push outcome ("ok" / "error"); `pushMessage`/`pushedAt` are "" until then (plain
|
|
9
|
+
// strings, no option fields — see the producer spec).
|
|
10
|
+
@@reventless.spec
|
|
11
|
+
|
|
12
|
+
@schema
|
|
13
|
+
type state = {
|
|
14
|
+
pluginId: string,
|
|
15
|
+
encoded: string,
|
|
16
|
+
protocol: string,
|
|
17
|
+
apiTarget: Reventless.Plugin.apiTarget,
|
|
18
|
+
registeredAt: string,
|
|
19
|
+
updatedAt: string,
|
|
20
|
+
pushStatus: string,
|
|
21
|
+
pushMessage: string,
|
|
22
|
+
pushedAt: string,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Consumes the ApiFragmentRegistry slice's events (from the shared admin DcbEventLog).
|
|
26
|
+
// Inline records with the same constructors/fields as the producer's `event` — decodes
|
|
27
|
+
// byte-identically and the ppx auto-tags `pluginId`.
|
|
28
|
+
@schema
|
|
29
|
+
type consumedEvent =
|
|
30
|
+
| ApiFragmentRegistered({
|
|
31
|
+
pluginId: string,
|
|
32
|
+
fragment: Reventless.Plugin.apiSchemaFragment,
|
|
33
|
+
apiTarget: Reventless.Plugin.apiTarget,
|
|
34
|
+
at: string,
|
|
35
|
+
})
|
|
36
|
+
| ApiFragmentUpdated({
|
|
37
|
+
pluginId: string,
|
|
38
|
+
previousFragment: Reventless.Plugin.apiSchemaFragment,
|
|
39
|
+
newFragment: Reventless.Plugin.apiSchemaFragment,
|
|
40
|
+
apiTarget: Reventless.Plugin.apiTarget,
|
|
41
|
+
at: string,
|
|
42
|
+
})
|
|
43
|
+
| ApiFragmentDeregistered({pluginId: string})
|
|
44
|
+
| ApiFragmentPushRecorded({pluginId: string, ok: bool, message: string, at: string})
|
|
@@ -0,0 +1,74 @@
|
|
|
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 ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
|
|
7
|
+
|
|
8
|
+
let stateSchema = S.schema(s => ({
|
|
9
|
+
pluginId: s.m(S.string),
|
|
10
|
+
encoded: s.m(S.string),
|
|
11
|
+
protocol: s.m(S.string),
|
|
12
|
+
apiTarget: s.m(Plugin$Reventless.apiTargetSchema),
|
|
13
|
+
registeredAt: s.m(S.string),
|
|
14
|
+
updatedAt: s.m(S.string),
|
|
15
|
+
pushStatus: s.m(S.string),
|
|
16
|
+
pushMessage: s.m(S.string),
|
|
17
|
+
pushedAt: s.m(S.string)
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
let consumedEventSchema = S.union([
|
|
21
|
+
S.schema(s => ({
|
|
22
|
+
TAG: "ApiFragmentRegistered",
|
|
23
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
24
|
+
fragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
25
|
+
apiTarget: s.m(Plugin$Reventless.apiTargetSchema),
|
|
26
|
+
at: s.m(S.string)
|
|
27
|
+
})),
|
|
28
|
+
S.schema(s => ({
|
|
29
|
+
TAG: "ApiFragmentUpdated",
|
|
30
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
31
|
+
previousFragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
32
|
+
newFragment: s.m(Plugin$Reventless.apiSchemaFragmentSchema),
|
|
33
|
+
apiTarget: s.m(Plugin$Reventless.apiTargetSchema),
|
|
34
|
+
at: s.m(S.string)
|
|
35
|
+
})),
|
|
36
|
+
S.schema(s => ({
|
|
37
|
+
TAG: "ApiFragmentDeregistered",
|
|
38
|
+
pluginId: s.m(DcbTag$Reventless.string)
|
|
39
|
+
})),
|
|
40
|
+
S.schema(s => ({
|
|
41
|
+
TAG: "ApiFragmentPushRecorded",
|
|
42
|
+
pluginId: s.m(DcbTag$Reventless.string),
|
|
43
|
+
ok: s.m(S.bool),
|
|
44
|
+
message: s.m(S.string),
|
|
45
|
+
at: s.m(S.string)
|
|
46
|
+
}))
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
let config = ReadModel$Reventless.config(undefined, undefined, undefined);
|
|
50
|
+
|
|
51
|
+
let name = "ApiFragments";
|
|
52
|
+
|
|
53
|
+
let Id;
|
|
54
|
+
|
|
55
|
+
let subIdConfig;
|
|
56
|
+
|
|
57
|
+
let moduleUrl = "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments.res.mjs";
|
|
58
|
+
|
|
59
|
+
let authorization = "AllowAuthenticated";
|
|
60
|
+
|
|
61
|
+
let visibility = "Public";
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
name,
|
|
65
|
+
Id,
|
|
66
|
+
stateSchema,
|
|
67
|
+
consumedEventSchema,
|
|
68
|
+
config,
|
|
69
|
+
subIdConfig,
|
|
70
|
+
moduleUrl,
|
|
71
|
+
authorization,
|
|
72
|
+
visibility,
|
|
73
|
+
}
|
|
74
|
+
/* stateSchema Not a pure module */
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
@@reventless.projection
|
|
2
|
+
|
|
3
|
+
// A fragment change resets the row to "pending" — the automation's push outcome for the
|
|
4
|
+
// new fragment arrives as a subsequent ApiFragmentPushRecorded. The Update on a deleted
|
|
5
|
+
// row (a push recorded after deregistration) is a no-op by Update semantics.
|
|
6
|
+
let project = event =>
|
|
7
|
+
switch event {
|
|
8
|
+
| ApiFragmentRegistered({pluginId, fragment, apiTarget, at}) => [
|
|
9
|
+
Set(
|
|
10
|
+
pluginId,
|
|
11
|
+
{
|
|
12
|
+
pluginId,
|
|
13
|
+
encoded: fragment.encoded,
|
|
14
|
+
protocol: fragment.protocol,
|
|
15
|
+
apiTarget,
|
|
16
|
+
registeredAt: at,
|
|
17
|
+
updatedAt: at,
|
|
18
|
+
pushStatus: "pending",
|
|
19
|
+
pushMessage: "",
|
|
20
|
+
pushedAt: "",
|
|
21
|
+
},
|
|
22
|
+
),
|
|
23
|
+
]
|
|
24
|
+
| ApiFragmentUpdated({pluginId, newFragment, apiTarget, at}) => [
|
|
25
|
+
Update(
|
|
26
|
+
pluginId,
|
|
27
|
+
state => {
|
|
28
|
+
...state,
|
|
29
|
+
encoded: newFragment.encoded,
|
|
30
|
+
protocol: newFragment.protocol,
|
|
31
|
+
apiTarget,
|
|
32
|
+
updatedAt: at,
|
|
33
|
+
pushStatus: "pending",
|
|
34
|
+
pushMessage: "",
|
|
35
|
+
},
|
|
36
|
+
),
|
|
37
|
+
]
|
|
38
|
+
| ApiFragmentDeregistered({pluginId}) => [Delete(pluginId)]
|
|
39
|
+
| ApiFragmentPushRecorded({pluginId, ok, message, at}) => [
|
|
40
|
+
Update(
|
|
41
|
+
pluginId,
|
|
42
|
+
state => {
|
|
43
|
+
...state,
|
|
44
|
+
pushStatus: ok ? "ok" : "error",
|
|
45
|
+
pushMessage: message,
|
|
46
|
+
pushedAt: at,
|
|
47
|
+
},
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function project(event) {
|
|
5
|
+
switch (event.TAG) {
|
|
6
|
+
case "ApiFragmentRegistered" :
|
|
7
|
+
let at = event.at;
|
|
8
|
+
let fragment = event.fragment;
|
|
9
|
+
let pluginId = event.pluginId;
|
|
10
|
+
return [{
|
|
11
|
+
TAG: "Set",
|
|
12
|
+
_0: pluginId,
|
|
13
|
+
_1: {
|
|
14
|
+
pluginId: pluginId,
|
|
15
|
+
encoded: fragment.encoded,
|
|
16
|
+
protocol: fragment.protocol,
|
|
17
|
+
apiTarget: event.apiTarget,
|
|
18
|
+
registeredAt: at,
|
|
19
|
+
updatedAt: at,
|
|
20
|
+
pushStatus: "pending",
|
|
21
|
+
pushMessage: "",
|
|
22
|
+
pushedAt: ""
|
|
23
|
+
}
|
|
24
|
+
}];
|
|
25
|
+
case "ApiFragmentUpdated" :
|
|
26
|
+
let at$1 = event.at;
|
|
27
|
+
let apiTarget = event.apiTarget;
|
|
28
|
+
let newFragment = event.newFragment;
|
|
29
|
+
return [{
|
|
30
|
+
TAG: "Update",
|
|
31
|
+
_0: event.pluginId,
|
|
32
|
+
_1: state => ({
|
|
33
|
+
pluginId: state.pluginId,
|
|
34
|
+
encoded: newFragment.encoded,
|
|
35
|
+
protocol: newFragment.protocol,
|
|
36
|
+
apiTarget: apiTarget,
|
|
37
|
+
registeredAt: state.registeredAt,
|
|
38
|
+
updatedAt: at$1,
|
|
39
|
+
pushStatus: "pending",
|
|
40
|
+
pushMessage: "",
|
|
41
|
+
pushedAt: state.pushedAt
|
|
42
|
+
})
|
|
43
|
+
}];
|
|
44
|
+
case "ApiFragmentDeregistered" :
|
|
45
|
+
return [{
|
|
46
|
+
TAG: "Delete",
|
|
47
|
+
_0: event.pluginId
|
|
48
|
+
}];
|
|
49
|
+
case "ApiFragmentPushRecorded" :
|
|
50
|
+
let at$2 = event.at;
|
|
51
|
+
let message = event.message;
|
|
52
|
+
let ok = event.ok;
|
|
53
|
+
return [{
|
|
54
|
+
TAG: "Update",
|
|
55
|
+
_0: event.pluginId,
|
|
56
|
+
_1: state => ({
|
|
57
|
+
pluginId: state.pluginId,
|
|
58
|
+
encoded: state.encoded,
|
|
59
|
+
protocol: state.protocol,
|
|
60
|
+
apiTarget: state.apiTarget,
|
|
61
|
+
registeredAt: state.registeredAt,
|
|
62
|
+
updatedAt: state.updatedAt,
|
|
63
|
+
pushStatus: ok ? "ok" : "error",
|
|
64
|
+
pushMessage: message,
|
|
65
|
+
pushedAt: at$2
|
|
66
|
+
})
|
|
67
|
+
}];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let Spec;
|
|
72
|
+
|
|
73
|
+
let moduleUrl = "@reventlessdev/reventless-core/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments_Projection.res.mjs";
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
Spec,
|
|
77
|
+
project,
|
|
78
|
+
moduleUrl,
|
|
79
|
+
}
|
|
80
|
+
/* No side effect */
|
|
@@ -77,7 +77,6 @@ module Make = (
|
|
|
77
77
|
extensionProtocols: [],
|
|
78
78
|
apiSchemaFragment: None,
|
|
79
79
|
apiTarget: None,
|
|
80
|
-
uiFragments: None,
|
|
81
80
|
structure: None,
|
|
82
81
|
dcbEventLog: None,
|
|
83
82
|
kind: Domain,
|
|
@@ -141,6 +140,17 @@ module Make = (
|
|
|
141
140
|
let dcbResult = DcbBuilder.construct(
|
|
142
141
|
~name,
|
|
143
142
|
~childName=name,
|
|
143
|
+
// Admin slice command mutations render `Platform_*` (not `Admin_*`), byte-aligned with the
|
|
144
|
+
// hand-declared admin SDL (`AdminApi.baseFragment`, via `Api_Naming.adminField`) so the
|
|
145
|
+
// auto-bound DCB resolver targets the exact field name that is pushed to the schema.
|
|
146
|
+
~apiNamePrefix="Platform",
|
|
147
|
+
// The API-schema registry's Register/Deregister mutations are invoked by the plugin/standalone
|
|
148
|
+
// deploy as a SigV4 system caller — mark them systemCallable so the AWS provider stamps the
|
|
149
|
+
// dual-auth (`@aws_cognito_user_pools @aws_iam`) directive on the constructed fragment entries.
|
|
150
|
+
~systemCallableComponents=["ApiFragmentRegistry"],
|
|
151
|
+
// Admin DCB mutation resolvers bind to the Platform API (split mode), not the deploy-target
|
|
152
|
+
// Domain API — routes the provider hook to `hooks.adminApi`.
|
|
153
|
+
~onAdminApi=true,
|
|
144
154
|
~aggregateEventTopics,
|
|
145
155
|
~stateChangeSlices,
|
|
146
156
|
~stateViewSlices,
|
|
@@ -150,6 +160,19 @@ module Make = (
|
|
|
150
160
|
~opts,
|
|
151
161
|
)
|
|
152
162
|
|
|
163
|
+
// Register admin DCB StateChangeSlice publish functions in publishToAggregates so an
|
|
164
|
+
// ExtensionPointMapping whose Delegate is an admin slice (e.g. the UI-fragment mapping →
|
|
165
|
+
// UiFragmentRegistry) can dispatch commands to it. Mirrors Plugin_Builder's merge; admin
|
|
166
|
+
// slices share the single admin DCB command topic (same publishJsons). Absent when no
|
|
167
|
+
// StateChangeSlice was passed (dcbPublishJsons = None) — the historical empty-slice case.
|
|
168
|
+
switch dcbResult.dcbPublishJsons {
|
|
169
|
+
| Some(slicePublishJsons) =>
|
|
170
|
+
stateChangeSlices->Array.forEach((module(Sc: ReventlessInfra.StateChangeSlice.T)) =>
|
|
171
|
+
publishToAggregates->Dict.set(Sc.Spec.name, slicePublishJsons)
|
|
172
|
+
)
|
|
173
|
+
| None => ()
|
|
174
|
+
}
|
|
175
|
+
|
|
153
176
|
// Admin schema — composed from actual config. `AdminApi.mutationEntries`
|
|
154
177
|
// already includes the auto-derived `Platform_Plugin_Activate`/`Deactivate`
|
|
155
178
|
// entries (via `PluginBaseFragment.pluginAggregateMutationEntries`) so the
|
|
@@ -348,6 +371,19 @@ module Make = (
|
|
|
348
371
|
|
|
349
372
|
let aggregatesOutputs = addEventMappers(allEventTopics, queryEngine)
|
|
350
373
|
let eventTopics = aggregatesOutputs->Aggregate.filterEventTopics(aggregateNames)
|
|
374
|
+
// 2e: also subscribe the admin EventCollector to the admin DcbEventLog's
|
|
375
|
+
// event topic (a DynamoDB stream on AWS) so the reactive ApiFragmentRegistry
|
|
376
|
+
// single writer receives ApiFragment* events. The EventCollector channel
|
|
377
|
+
// already provisions the stream subscription + read IAM for stream-backed
|
|
378
|
+
// event-topic resources. Copy the filtered dict so the shared aggregate
|
|
379
|
+
// outputs stay untouched.
|
|
380
|
+
let eventTopics = switch dcbResult.dcbEventLogOutputs {
|
|
381
|
+
| Some(dcbOutputs) =>
|
|
382
|
+
let augmented = eventTopics->Dict.copy
|
|
383
|
+
augmented->Dict.set(name ++ "DcbEventLog", dcbOutputs.eventTopic)
|
|
384
|
+
augmented
|
|
385
|
+
| None => eventTopics
|
|
386
|
+
}
|
|
351
387
|
|
|
352
388
|
module EventCollectorHelper = MakeEventCollectorHelper(
|
|
353
389
|
RuntimeEnvironment,
|
|
@@ -44,7 +44,6 @@ function Make(RuntimeEnvironment) {
|
|
|
44
44
|
extensionProtocols: [],
|
|
45
45
|
apiSchemaFragment: undefined,
|
|
46
46
|
apiTarget: undefined,
|
|
47
|
-
uiFragments: undefined,
|
|
48
47
|
structure: undefined,
|
|
49
48
|
dcbEventLog: undefined,
|
|
50
49
|
kind: "Domain"
|
|
@@ -72,7 +71,13 @@ function Make(RuntimeEnvironment) {
|
|
|
72
71
|
let aggregatesWithoutEventMappers = Builder_Helpers$ReventlessCore.createAggregatesWithoutEventMappers(aggregates, api, opts);
|
|
73
72
|
let aggregateEventTopics = Aggregate$ReventlessCore.allEventTopics(aggregatesWithoutEventMappers);
|
|
74
73
|
Plugin_Helpers$ReventlessCore.registerAdminAggregateMutations(aggregates, Config.hooks);
|
|
75
|
-
let dcbResult = DcbBuilder.construct(name, name, undefined, undefined, aggregateEventTopics, stateChangeSlices, stateViewSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices,
|
|
74
|
+
let dcbResult = DcbBuilder.construct(name, name, "Platform", true, undefined, undefined, aggregateEventTopics, stateChangeSlices, stateViewSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, ["ApiFragmentRegistry"], undefined, opts);
|
|
75
|
+
let slicePublishJsons = dcbResult.dcbPublishJsons;
|
|
76
|
+
if (slicePublishJsons !== undefined) {
|
|
77
|
+
stateChangeSlices.forEach(Sc => {
|
|
78
|
+
Builder_Helpers$ReventlessCore.publishToAggregates[Sc.Spec.name] = slicePublishJsons;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
76
81
|
let adminMutationEntries = AdminApi$ReventlessCore.mutationEntries(Config.cloner);
|
|
77
82
|
let allMutationEntries = adminMutationEntries.concat(dcbResult.mutationEntries);
|
|
78
83
|
let allQueryEntries = AdminApi$ReventlessCore.queryEntries.concat(dcbResult.queryEntries);
|
|
@@ -164,6 +169,15 @@ function Make(RuntimeEnvironment) {
|
|
|
164
169
|
let aggregateNames = Stdlib_Array.reduce(extensionPointsOutputs.map(extensionPointOutputs => Belt_SetString.fromArray(extensionPointOutputs.aggregateNames)), undefined, Belt_SetString.union);
|
|
165
170
|
let aggregatesOutputs = Builder_Helpers$ReventlessCore.addEventMappers(aggregateEventTopics, queryEngine);
|
|
166
171
|
let eventTopics = Aggregate$ReventlessCore.filterEventTopics(aggregatesOutputs, aggregateNames);
|
|
172
|
+
let dcbOutputs = dcbResult.dcbEventLogOutputs;
|
|
173
|
+
let eventTopics$1;
|
|
174
|
+
if (dcbOutputs !== undefined) {
|
|
175
|
+
let augmented = Object.assign({}, eventTopics);
|
|
176
|
+
augmented[name + "DcbEventLog"] = dcbOutputs.eventTopic;
|
|
177
|
+
eventTopics$1 = augmented;
|
|
178
|
+
} else {
|
|
179
|
+
eventTopics$1 = eventTopics;
|
|
180
|
+
}
|
|
167
181
|
let AdminEventCollector = EventCollector_Builder$ReventlessCore.Make(RuntimeEnvironment)(EventCollectorChannel);
|
|
168
182
|
let make = (name, eventTopics, opts) => {
|
|
169
183
|
let eventCollector = AdminEventCollector.make(name, eventTopics, opts);
|
|
@@ -186,7 +200,6 @@ function Make(RuntimeEnvironment) {
|
|
|
186
200
|
extensionProtocols: [],
|
|
187
201
|
apiSchemaFragment: undefined,
|
|
188
202
|
apiTarget: undefined,
|
|
189
|
-
uiFragments: undefined,
|
|
190
203
|
structure: undefined,
|
|
191
204
|
dcbEventLog: undefined,
|
|
192
205
|
kind: "Domain"
|
|
@@ -199,9 +212,9 @@ function Make(RuntimeEnvironment) {
|
|
|
199
212
|
AdminRuntimeBuilder.forPluginEventCollector(handler, eventTopics, resources, undefined, undefined, eventCollector);
|
|
200
213
|
});
|
|
201
214
|
};
|
|
202
|
-
let match$1 = make(name, eventTopics, opts);
|
|
215
|
+
let match$1 = make(name, eventTopics$1, opts);
|
|
203
216
|
let eventCollector = match$1[0];
|
|
204
|
-
Output$Pulumi.flatMap(Pulumi.all(match[1]), extensionPointsOutgoingJsonEventsHandlers => connect(eventCollector, eventTopics, extensionPointsOutputs, extensionPointsOutgoingJsonEventsHandlers));
|
|
217
|
+
Output$Pulumi.flatMap(Pulumi.all(match[1]), extensionPointsOutgoingJsonEventsHandlers => connect(eventCollector, eventTopics$1, extensionPointsOutputs, extensionPointsOutgoingJsonEventsHandlers));
|
|
205
218
|
Stdlib_Option.forEach(dcbResult.dcbRuntimeSetup, dcbRuntimeSetup => dcbRuntimeSetup());
|
|
206
219
|
return extensionPointsOutputs;
|
|
207
220
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// SDL type and JSON encoder for the Platform_ApiFragments GraphQL query — the
|
|
2
|
+
// deploy-facing status surface of the API-schema fragment registry. Shared
|
|
3
|
+
// between the in-memory adapter (which resolves from the in-process ApiFragments
|
|
4
|
+
// StateViewSlice QueryDb) and the AWS adapter (which scans the ApiFragments
|
|
5
|
+
// DynamoDB table), so the wire shape is byte-identical and one client query
|
|
6
|
+
// string works against either.
|
|
7
|
+
//
|
|
8
|
+
// The deploy waiter polls this to confirm its plugin's fragment reached ACTIVE
|
|
9
|
+
// (pushStatus "ok") or to surface a stitch error (pushStatus "error" +
|
|
10
|
+
// pushMessage). It exposes status only — NOT the encoded SDL, which the waiter
|
|
11
|
+
// does not need.
|
|
12
|
+
|
|
13
|
+
let sdlTypes: array<string> = [
|
|
14
|
+
`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}`,
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
let queryFieldName: string = "Platform_ApiFragments"
|
|
18
|
+
|
|
19
|
+
let sdlQueryField: string = ` ${queryFieldName}: [Platform_ApiFragmentEntry!]!`
|
|
20
|
+
|
|
21
|
+
let encodeApiFragmentEntry = (entry: ApiFragments.state): JSON.t =>
|
|
22
|
+
Dict.fromArray([
|
|
23
|
+
("pluginId", JSON.Encode.string(Plugin.name(entry.pluginId))),
|
|
24
|
+
// apiTarget is a payload-less variant — its schema serializes to the bare
|
|
25
|
+
// JSON string "Domain" / "Platform".
|
|
26
|
+
("apiTarget", entry.apiTarget->S.reverseConvertToJsonOrThrow(Reventless.Plugin.apiTargetSchema)),
|
|
27
|
+
("pushStatus", JSON.Encode.string(entry.pushStatus)),
|
|
28
|
+
("pushMessage", JSON.Encode.string(entry.pushMessage)),
|
|
29
|
+
("pushedAt", JSON.Encode.string(entry.pushedAt)),
|
|
30
|
+
("registeredAt", JSON.Encode.string(entry.registeredAt)),
|
|
31
|
+
("updatedAt", JSON.Encode.string(entry.updatedAt)),
|
|
32
|
+
])->JSON.Encode.object
|