@reventlessdev/reventless-core 3.0.0-alpha.156 → 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 +15 -0
- package/package.json +6 -6
- 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,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 */
|
|
@@ -4,13 +4,33 @@
|
|
|
4
4
|
|
|
5
5
|
let log = Logger.fromEnv()
|
|
6
6
|
|
|
7
|
+
// Neutral subscription→mutation source mapping. Core emits provider-free
|
|
8
|
+
// subscription SDL; this record carries which mutation(s) feed a subscription
|
|
9
|
+
// field so each provider can wire its own dialect (AppSync appends
|
|
10
|
+
// `@aws_subscribe(mutations: [...])` at push time; the local platform routes
|
|
11
|
+
// its PubSub bridge from the same mapping). Supports many-mutations-to-one
|
|
12
|
+
// fan-in (e.g. onUIFragmentChange ← three Platform_UIFragment* mutations).
|
|
13
|
+
type subscriptionSource = {
|
|
14
|
+
field: string,
|
|
15
|
+
mutations: array<string>,
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
type fragmentParts = {
|
|
8
19
|
types: array<string>,
|
|
9
20
|
mutations: array<string>,
|
|
10
21
|
queries: array<string>,
|
|
11
22
|
subscriptions: array<string>,
|
|
23
|
+
subscriptionSources: array<subscriptionSource>,
|
|
12
24
|
}
|
|
13
25
|
|
|
26
|
+
let encodeSubscriptionSource = (source: subscriptionSource): JSON.t =>
|
|
27
|
+
JSON.Encode.object(
|
|
28
|
+
Dict.fromArray([
|
|
29
|
+
("field", JSON.Encode.string(source.field)),
|
|
30
|
+
("mutations", JSON.Encode.array(source.mutations->Array.map(JSON.Encode.string))),
|
|
31
|
+
]),
|
|
32
|
+
)
|
|
33
|
+
|
|
14
34
|
let encode = (parts: fragmentParts): Reventless.Plugin.apiSchemaFragment => {
|
|
15
35
|
let encoded =
|
|
16
36
|
JSON.Encode.object(
|
|
@@ -19,6 +39,10 @@ let encode = (parts: fragmentParts): Reventless.Plugin.apiSchemaFragment => {
|
|
|
19
39
|
("mutations", JSON.Encode.array(parts.mutations->Array.map(JSON.Encode.string))),
|
|
20
40
|
("queries", JSON.Encode.array(parts.queries->Array.map(JSON.Encode.string))),
|
|
21
41
|
("subscriptions", JSON.Encode.array(parts.subscriptions->Array.map(JSON.Encode.string))),
|
|
42
|
+
(
|
|
43
|
+
"subscriptionSources",
|
|
44
|
+
JSON.Encode.array(parts.subscriptionSources->Array.map(encodeSubscriptionSource)),
|
|
45
|
+
),
|
|
22
46
|
]),
|
|
23
47
|
)->JSON.stringify
|
|
24
48
|
{Reventless.Plugin.encoded, protocol: "graphql"}
|
|
@@ -26,7 +50,7 @@ let encode = (parts: fragmentParts): Reventless.Plugin.apiSchemaFragment => {
|
|
|
26
50
|
|
|
27
51
|
let decode = (fragment: Reventless.Plugin.apiSchemaFragment): fragmentParts => {
|
|
28
52
|
switch fragment.encoded->JSON.parseOrThrow->JSON.Decode.object {
|
|
29
|
-
| None => {types: [], mutations: [], queries: [], subscriptions: []}
|
|
53
|
+
| None => {types: [], mutations: [], queries: [], subscriptions: [], subscriptionSources: []}
|
|
30
54
|
| Some(dict) =>
|
|
31
55
|
let getString = (key): array<string> =>
|
|
32
56
|
switch dict->Dict.get(key) {
|
|
@@ -39,15 +63,62 @@ let decode = (fragment: Reventless.Plugin.apiSchemaFragment): fragmentParts => {
|
|
|
39
63
|
)
|
|
40
64
|
| _ => []
|
|
41
65
|
}
|
|
66
|
+
// Tolerant of fragments encoded before the field existed — default [].
|
|
67
|
+
let subscriptionSources = switch dict->Dict.get("subscriptionSources") {
|
|
68
|
+
| Some(JSON.Array(arr)) =>
|
|
69
|
+
arr->Array.filterMap(j =>
|
|
70
|
+
j
|
|
71
|
+
->JSON.Decode.object
|
|
72
|
+
->Option.flatMap(obj => {
|
|
73
|
+
let field = obj->Dict.get("field")->Option.flatMap(JSON.Decode.string)
|
|
74
|
+
let mutations = switch obj->Dict.get("mutations") {
|
|
75
|
+
| Some(JSON.Array(ms)) =>
|
|
76
|
+
ms->Array.filterMap(m =>
|
|
77
|
+
switch m {
|
|
78
|
+
| JSON.String(s) => Some(s)
|
|
79
|
+
| _ => None
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
| _ => []
|
|
83
|
+
}
|
|
84
|
+
field->Option.map(field => {field, mutations})
|
|
85
|
+
})
|
|
86
|
+
)
|
|
87
|
+
| _ => []
|
|
88
|
+
}
|
|
42
89
|
{
|
|
43
90
|
types: getString("types"),
|
|
44
91
|
mutations: getString("mutations"),
|
|
45
92
|
queries: getString("queries"),
|
|
46
93
|
subscriptions: getString("subscriptions"),
|
|
94
|
+
subscriptionSources,
|
|
47
95
|
}
|
|
48
96
|
}
|
|
49
97
|
}
|
|
50
98
|
|
|
99
|
+
/**
|
|
100
|
+
Union of the subscription→mutation source mappings across a base fragment and
|
|
101
|
+
all plugin fragments, deduped by subscription field name (first wins — matching
|
|
102
|
+
`stitch`'s first-wins field dedupe). Providers use this to decorate or wire the
|
|
103
|
+
STITCHED schema's subscription fields.
|
|
104
|
+
*/
|
|
105
|
+
let collectSubscriptionSources = (
|
|
106
|
+
~baseFragment: Reventless.Plugin.apiSchemaFragment,
|
|
107
|
+
~pluginFragments: array<Reventless.Plugin.apiSchemaFragment>,
|
|
108
|
+
): array<subscriptionSource> => {
|
|
109
|
+
let seen: Set.t<string> = Set.make()
|
|
110
|
+
Array.concat([baseFragment], pluginFragments)
|
|
111
|
+
->Array.flatMap(fragment => decode(fragment).subscriptionSources)
|
|
112
|
+
->Array.filter(source =>
|
|
113
|
+
if seen->Set.has(source.field) {
|
|
114
|
+
false
|
|
115
|
+
} else {
|
|
116
|
+
seen->Set.add(source.field)
|
|
117
|
+
true
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
51
122
|
// Extract the first identifier from a SDL field/type string.
|
|
52
123
|
// e.g. " fieldName(args): Type" → "fieldName"
|
|
53
124
|
// "type TypeName {" → "TypeName"
|
|
@@ -8,6 +8,19 @@ import * as Logger$ReventlessCore from "../../util/Logger.res.mjs";
|
|
|
8
8
|
|
|
9
9
|
let log = Logger$ReventlessCore.fromEnv();
|
|
10
10
|
|
|
11
|
+
function encodeSubscriptionSource(source) {
|
|
12
|
+
return Object.fromEntries([
|
|
13
|
+
[
|
|
14
|
+
"field",
|
|
15
|
+
source.field
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
"mutations",
|
|
19
|
+
source.mutations.map(prim => prim)
|
|
20
|
+
]
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
function encode(parts) {
|
|
12
25
|
let encoded = JSON.stringify(Object.fromEntries([
|
|
13
26
|
[
|
|
@@ -25,6 +38,10 @@ function encode(parts) {
|
|
|
25
38
|
[
|
|
26
39
|
"subscriptions",
|
|
27
40
|
parts.subscriptions.map(prim => prim)
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
"subscriptionSources",
|
|
44
|
+
parts.subscriptionSources.map(encodeSubscriptionSource)
|
|
28
45
|
]
|
|
29
46
|
]));
|
|
30
47
|
return {
|
|
@@ -40,7 +57,8 @@ function decode(fragment) {
|
|
|
40
57
|
types: [],
|
|
41
58
|
mutations: [],
|
|
42
59
|
queries: [],
|
|
43
|
-
subscriptions: []
|
|
60
|
+
subscriptions: [],
|
|
61
|
+
subscriptionSources: []
|
|
44
62
|
};
|
|
45
63
|
}
|
|
46
64
|
let getString = key => {
|
|
@@ -59,14 +77,45 @@ function decode(fragment) {
|
|
|
59
77
|
return [];
|
|
60
78
|
}
|
|
61
79
|
};
|
|
80
|
+
let match = dict["subscriptionSources"];
|
|
81
|
+
let subscriptionSources = match !== undefined ? (
|
|
82
|
+
Array.isArray(match) ? Stdlib_Array.filterMap(match, j => Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(j), obj => {
|
|
83
|
+
let field = Stdlib_Option.flatMap(obj["field"], Stdlib_JSON.Decode.string);
|
|
84
|
+
let match = obj["mutations"];
|
|
85
|
+
let mutations = match !== undefined ? (
|
|
86
|
+
Array.isArray(match) ? Stdlib_Array.filterMap(match, m => {
|
|
87
|
+
if (typeof m === "string") {
|
|
88
|
+
return m;
|
|
89
|
+
}
|
|
90
|
+
}) : []
|
|
91
|
+
) : [];
|
|
92
|
+
return Stdlib_Option.map(field, field => ({
|
|
93
|
+
field: field,
|
|
94
|
+
mutations: mutations
|
|
95
|
+
}));
|
|
96
|
+
})) : []
|
|
97
|
+
) : [];
|
|
62
98
|
return {
|
|
63
99
|
types: getString("types"),
|
|
64
100
|
mutations: getString("mutations"),
|
|
65
101
|
queries: getString("queries"),
|
|
66
|
-
subscriptions: getString("subscriptions")
|
|
102
|
+
subscriptions: getString("subscriptions"),
|
|
103
|
+
subscriptionSources: subscriptionSources
|
|
67
104
|
};
|
|
68
105
|
}
|
|
69
106
|
|
|
107
|
+
function collectSubscriptionSources(baseFragment, pluginFragments) {
|
|
108
|
+
let seen = new Set();
|
|
109
|
+
return [baseFragment].concat(pluginFragments).flatMap(fragment => decode(fragment).subscriptionSources).filter(source => {
|
|
110
|
+
if (seen.has(source.field)) {
|
|
111
|
+
return false;
|
|
112
|
+
} else {
|
|
113
|
+
seen.add(source.field);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
70
119
|
function extractLeadingName(str) {
|
|
71
120
|
let trimmed = str.trim();
|
|
72
121
|
let afterType = trimmed.startsWith("type ") || trimmed.startsWith("enum ") ? trimmed.slice(5, trimmed.length).trim() : (
|
|
@@ -252,8 +301,10 @@ function isCatastrophicSchemaShrink(currentSdl, newSdl, threshold) {
|
|
|
252
301
|
|
|
253
302
|
export {
|
|
254
303
|
log,
|
|
304
|
+
encodeSubscriptionSource,
|
|
255
305
|
encode,
|
|
256
306
|
decode,
|
|
307
|
+
collectSubscriptionSources,
|
|
257
308
|
extractLeadingName,
|
|
258
309
|
relayBaseTypes,
|
|
259
310
|
relayBaseQueries,
|