@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.
Files changed (75) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +49 -150
  3. package/package.json +13 -13
  4. package/src/admin/AdminApi.res +80 -10
  5. package/src/admin/AdminApi.res.mjs +45 -7
  6. package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry.res +65 -0
  7. package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry.res.mjs +87 -0
  8. package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry_Behavior.res +58 -0
  9. package/src/admin/ApiFragmentRegistry/StateChangeSlice/ApiFragmentRegistry_Behavior.res.mjs +135 -0
  10. package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments.res +44 -0
  11. package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments.res.mjs +74 -0
  12. package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments_Projection.res +50 -0
  13. package/src/admin/ApiFragmentRegistry/StateViewSlice/ApiFragments_Projection.res.mjs +80 -0
  14. package/src/admin/Platform_Admin.res +37 -1
  15. package/src/admin/Platform_Admin.res.mjs +18 -5
  16. package/src/admin/Platform_ApiFragmentsApi.res +32 -0
  17. package/src/admin/Platform_ApiFragmentsApi.res.mjs +52 -0
  18. package/src/admin/Platform_UIFragmentsApi.res +1 -1
  19. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res +50 -0
  20. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +70 -0
  21. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry_Behavior.res +33 -0
  22. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry_Behavior.res.mjs +80 -0
  23. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments.res +33 -0
  24. package/src/admin/{UIFragmentRegistryReadModelSpec.res.mjs → UiFragmentRegistry/StateViewSlice/UiFragments.res.mjs} +24 -2
  25. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res +34 -0
  26. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res.mjs +54 -0
  27. package/src/components/Api/Api_Naming.res +1 -1
  28. package/src/components/Api/GraphQL_FragmentGenerator.res +7 -11
  29. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +8 -22
  30. package/src/components/Api/GraphQL_PushPlanner.res +65 -0
  31. package/src/components/Api/GraphQL_PushPlanner.res.mjs +46 -0
  32. package/src/components/Api/GraphQL_Stitcher.res +72 -1
  33. package/src/components/Api/GraphQL_Stitcher.res.mjs +53 -2
  34. package/src/components/Dcb/Dcb_Builder.res +16 -6
  35. package/src/components/Dcb/Dcb_Builder.res.mjs +10 -7
  36. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +6 -1
  37. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +1 -1
  38. package/src/plugin/api/PluginBaseFragment.res +5 -6
  39. package/src/plugin/api/PluginBaseFragment.res.mjs +0 -1
  40. package/src/plugin/component/Plugin_Builder.res +4 -1
  41. package/src/plugin/component/Plugin_Builder.res.mjs +7 -6
  42. package/src/plugin/component/Plugin_Helpers.res +30 -0
  43. package/src/plugin/component/Plugin_Helpers.res.mjs +13 -3
  44. package/src/plugin/component/Plugin_SubscriptionSchema.res +19 -9
  45. package/src/plugin/component/Plugin_SubscriptionSchema.res.mjs +23 -14
  46. package/src/plugin/connect/PluginConnectExtension_Mapping.res +28 -6
  47. package/src/plugin/connect/PluginConnectExtension_Mapping.res.mjs +34 -14
  48. package/src/plugin/connect/PluginExtensionPoint_Builder.res +7 -1
  49. package/src/plugin/connect/PluginExtensionPoint_Builder.res.mjs +5 -1
  50. package/src/plugin/connect/PluginExtensionPoint_Plugin.res +3 -1
  51. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +4 -2
  52. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res +52 -0
  53. package/src/plugin/connect/PluginExtensionPoint_UiFragment.res.mjs +74 -0
  54. package/src/plugin/lifecycle/PluginBehavior.res +9 -51
  55. package/src/plugin/lifecycle/PluginBehavior.res.mjs +39 -68
  56. package/src/plugin/lifecycle/PluginSpec.res +0 -18
  57. package/src/plugin/lifecycle/PluginSpec.res.mjs +0 -30
  58. package/src/plugin/lifecycle/PluginsProjection.res +2 -6
  59. package/src/plugin/lifecycle/PluginsProjection.res.mjs +0 -2
  60. package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
  61. package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +0 -1
  62. package/tests/admin/Platform_ApiFragmentsApiTest.res +66 -0
  63. package/tests/admin/Platform_ApiFragmentsApiTest.res.mjs +68 -0
  64. package/tests/admin/Platform_UIFragmentsApiTest.res +1 -1
  65. package/tests/api/GraphQL_PushPlannerTest.res +62 -0
  66. package/tests/api/GraphQL_PushPlannerTest.res.mjs +80 -0
  67. package/tests/api/GraphQL_StitcherTest.res +63 -0
  68. package/tests/api/GraphQL_StitcherTest.res.mjs +75 -0
  69. package/tests/logger/LogFormatTest.res +1 -2
  70. package/tests/logger/LogFormatTest.res.mjs +1 -2
  71. package/tests/message/MessageTest.res +0 -3
  72. package/tests/message/MessageTest.res.mjs +0 -3
  73. package/src/admin/UIFragmentRegistryProjection.res +0 -43
  74. package/src/admin/UIFragmentRegistryProjection.res.mjs +0 -81
  75. 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, undefined, undefined, opts);
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