@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
|
@@ -30,33 +30,6 @@ let initialState = {
|
|
|
30
30
|
known: initialState_known
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
function uiRegisterEvents(pluginId, manifest) {
|
|
34
|
-
if (manifest !== undefined) {
|
|
35
|
-
return [{
|
|
36
|
-
TAG: "UIFragmentRegistered",
|
|
37
|
-
_0: {
|
|
38
|
-
pluginId: pluginId,
|
|
39
|
-
manifest: manifest
|
|
40
|
-
}
|
|
41
|
-
}];
|
|
42
|
-
} else {
|
|
43
|
-
return [];
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function uiDeregisterEvents(pluginId, manifest) {
|
|
48
|
-
if (manifest !== undefined) {
|
|
49
|
-
return [{
|
|
50
|
-
TAG: "UIFragmentDeregistered",
|
|
51
|
-
_0: {
|
|
52
|
-
pluginId: pluginId
|
|
53
|
-
}
|
|
54
|
-
}];
|
|
55
|
-
} else {
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
33
|
function highestConnected(known) {
|
|
61
34
|
return Stdlib_Array.reduce(Object.entries(known), undefined, (acc, param) => {
|
|
62
35
|
let v = param[0];
|
|
@@ -97,7 +70,7 @@ function connectEvents(state, v, def) {
|
|
|
97
70
|
let base = [{
|
|
98
71
|
TAG: "VersionConnected",
|
|
99
72
|
_0: def
|
|
100
|
-
}]
|
|
73
|
+
}];
|
|
101
74
|
let supersede;
|
|
102
75
|
if (currentAfterConnect(state.known, v) === v) {
|
|
103
76
|
let c = state.current;
|
|
@@ -229,26 +202,27 @@ function decide(state, command) {
|
|
|
229
202
|
case "Disconnect" :
|
|
230
203
|
let v$3 = command._0;
|
|
231
204
|
let match$3 = state.known[v$3];
|
|
232
|
-
if (match$3
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
205
|
+
if (match$3 !== undefined) {
|
|
206
|
+
if (match$3.status === "Connected") {
|
|
207
|
+
return {
|
|
208
|
+
TAG: "Ok",
|
|
209
|
+
_0: [{
|
|
210
|
+
TAG: "VersionDisconnected",
|
|
211
|
+
_0: match$3.definition
|
|
212
|
+
}].concat(promoteEvents(state, v$3))
|
|
213
|
+
};
|
|
214
|
+
} else {
|
|
215
|
+
return {
|
|
216
|
+
TAG: "Ok",
|
|
217
|
+
_0: []
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
239
221
|
return {
|
|
240
222
|
TAG: "Ok",
|
|
241
223
|
_0: []
|
|
242
224
|
};
|
|
243
225
|
}
|
|
244
|
-
let definition = match$3.definition;
|
|
245
|
-
return {
|
|
246
|
-
TAG: "Ok",
|
|
247
|
-
_0: [{
|
|
248
|
-
TAG: "VersionDisconnected",
|
|
249
|
-
_0: definition
|
|
250
|
-
}].concat(uiDeregisterEvents(definition.id, definition.uiFragments)).concat(promoteEvents(state, v$3))
|
|
251
|
-
};
|
|
252
226
|
case "Activate" :
|
|
253
227
|
let v$4 = command._0;
|
|
254
228
|
let match$4 = state.known[v$4];
|
|
@@ -258,7 +232,7 @@ function decide(state, command) {
|
|
|
258
232
|
_0: "UnknownVersion"
|
|
259
233
|
};
|
|
260
234
|
}
|
|
261
|
-
let definition
|
|
235
|
+
let definition = match$4.definition;
|
|
262
236
|
switch (match$4.status) {
|
|
263
237
|
case "Connected" :
|
|
264
238
|
return {
|
|
@@ -276,8 +250,8 @@ function decide(state, command) {
|
|
|
276
250
|
}
|
|
277
251
|
let base = [{
|
|
278
252
|
TAG: "VersionActivated",
|
|
279
|
-
_0: definition
|
|
280
|
-
}]
|
|
253
|
+
_0: definition
|
|
254
|
+
}];
|
|
281
255
|
let supersede;
|
|
282
256
|
if (currentAfterConnect(state.known, v$4) === v$4) {
|
|
283
257
|
let c = state.current;
|
|
@@ -290,7 +264,7 @@ function decide(state, command) {
|
|
|
290
264
|
supersededVersion: c,
|
|
291
265
|
supersededDefinition: match$5.definition,
|
|
292
266
|
newVersion: v$4,
|
|
293
|
-
newDefinition: definition
|
|
267
|
+
newDefinition: definition
|
|
294
268
|
}
|
|
295
269
|
}] : [];
|
|
296
270
|
} else {
|
|
@@ -316,7 +290,6 @@ function decide(state, command) {
|
|
|
316
290
|
_0: "UnknownVersion"
|
|
317
291
|
};
|
|
318
292
|
}
|
|
319
|
-
let definition$2 = match$6.definition;
|
|
320
293
|
switch (match$6.status) {
|
|
321
294
|
case "Connected" :
|
|
322
295
|
case "Disconnected" :
|
|
@@ -332,8 +305,8 @@ function decide(state, command) {
|
|
|
332
305
|
TAG: "Ok",
|
|
333
306
|
_0: [{
|
|
334
307
|
TAG: "VersionDeactivated",
|
|
335
|
-
_0: definition
|
|
336
|
-
}].concat(
|
|
308
|
+
_0: match$6.definition
|
|
309
|
+
}].concat(promoteEvents(state, v$5))
|
|
337
310
|
};
|
|
338
311
|
break;
|
|
339
312
|
case "ReportIncompatibility" :
|
|
@@ -347,27 +320,27 @@ function decide(state, command) {
|
|
|
347
320
|
case "Retire" :
|
|
348
321
|
let v$6 = command._0;
|
|
349
322
|
let match$7 = state.known[v$6];
|
|
350
|
-
if (match$7
|
|
323
|
+
if (match$7 !== undefined) {
|
|
324
|
+
if (match$7.status === "Retired") {
|
|
325
|
+
return {
|
|
326
|
+
TAG: "Ok",
|
|
327
|
+
_0: []
|
|
328
|
+
};
|
|
329
|
+
} else {
|
|
330
|
+
return {
|
|
331
|
+
TAG: "Ok",
|
|
332
|
+
_0: [{
|
|
333
|
+
TAG: "VersionRetired",
|
|
334
|
+
_0: match$7.definition
|
|
335
|
+
}].concat(promoteEvents(state, v$6))
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
351
339
|
return {
|
|
352
340
|
TAG: "Error",
|
|
353
341
|
_0: "UnknownVersion"
|
|
354
342
|
};
|
|
355
343
|
}
|
|
356
|
-
let definition$3 = match$7.definition;
|
|
357
|
-
if (match$7.status === "Retired") {
|
|
358
|
-
return {
|
|
359
|
-
TAG: "Ok",
|
|
360
|
-
_0: []
|
|
361
|
-
};
|
|
362
|
-
} else {
|
|
363
|
-
return {
|
|
364
|
-
TAG: "Ok",
|
|
365
|
-
_0: [{
|
|
366
|
-
TAG: "VersionRetired",
|
|
367
|
-
_0: definition$3
|
|
368
|
-
}].concat(uiDeregisterEvents(definition$3.id, definition$3.uiFragments)).concat(promoteEvents(state, v$6))
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
344
|
}
|
|
372
345
|
}
|
|
373
346
|
|
|
@@ -419,8 +392,6 @@ export {
|
|
|
419
392
|
initialState,
|
|
420
393
|
snapshot,
|
|
421
394
|
atomicCounter,
|
|
422
|
-
uiRegisterEvents,
|
|
423
|
-
uiDeregisterEvents,
|
|
424
395
|
highestConnected,
|
|
425
396
|
highestConnectedExcluding,
|
|
426
397
|
currentAfterConnect,
|
|
@@ -26,19 +26,6 @@ type command =
|
|
|
26
26
|
// automatic `Superseded` transition (which is decided, not commanded).
|
|
27
27
|
| Retire(version)
|
|
28
28
|
|
|
29
|
-
@schema
|
|
30
|
-
type uiFragmentRegisteredData = {pluginId: string, manifest: uiFragmentManifest}
|
|
31
|
-
|
|
32
|
-
@schema
|
|
33
|
-
type uiFragmentUpdatedData = {
|
|
34
|
-
pluginId: string,
|
|
35
|
-
previousManifest: uiFragmentManifest,
|
|
36
|
-
newManifest: uiFragmentManifest,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
@schema
|
|
40
|
-
type uiFragmentDeregisteredData = {pluginId: string}
|
|
41
|
-
|
|
42
29
|
// Carries both the superseded and the superseding version's full definition —
|
|
43
30
|
// a deterministic trigger for version-to-version schema/data migrations
|
|
44
31
|
// (analysis §6.2.4).
|
|
@@ -72,11 +59,6 @@ type event =
|
|
|
72
59
|
| VersionRetired(pluginDefinition)
|
|
73
60
|
// Protocol incompatibility recorded at connect; connection still proceeds.
|
|
74
61
|
| IncompatiblePluginDetected(pluginDefinition)
|
|
75
|
-
// UI fragment lifecycle events — emitted alongside connect/disconnect/(de)activate
|
|
76
|
-
// when the plugin's pluginDefinition.uiFragments is set.
|
|
77
|
-
| UIFragmentRegistered(uiFragmentRegisteredData)
|
|
78
|
-
| UIFragmentUpdated(uiFragmentUpdatedData)
|
|
79
|
-
| UIFragmentDeregistered(uiFragmentDeregisteredData)
|
|
80
62
|
|
|
81
63
|
@schema
|
|
82
64
|
type error =
|
|
@@ -39,21 +39,6 @@ let commandSchema = S.union([
|
|
|
39
39
|
}))
|
|
40
40
|
]);
|
|
41
41
|
|
|
42
|
-
let uiFragmentRegisteredDataSchema = S.schema(s => ({
|
|
43
|
-
pluginId: s.m(S.string),
|
|
44
|
-
manifest: s.m(Plugin$Reventless.uiFragmentManifestSchema)
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
let uiFragmentUpdatedDataSchema = S.schema(s => ({
|
|
48
|
-
pluginId: s.m(S.string),
|
|
49
|
-
previousManifest: s.m(Plugin$Reventless.uiFragmentManifestSchema),
|
|
50
|
-
newManifest: s.m(Plugin$Reventless.uiFragmentManifestSchema)
|
|
51
|
-
}));
|
|
52
|
-
|
|
53
|
-
let uiFragmentDeregisteredDataSchema = S.schema(s => ({
|
|
54
|
-
pluginId: s.m(S.string)
|
|
55
|
-
}));
|
|
56
|
-
|
|
57
42
|
let versionSupersededDataSchema = S.schema(s => ({
|
|
58
43
|
supersededVersion: s.m(Plugin$Reventless.versionSchema),
|
|
59
44
|
supersededDefinition: s.m(Plugin$Reventless.pluginDefinitionSchema),
|
|
@@ -97,18 +82,6 @@ let eventSchema = S.union([
|
|
|
97
82
|
S.schema(s => ({
|
|
98
83
|
TAG: "IncompatiblePluginDetected",
|
|
99
84
|
_0: s.m(Plugin$Reventless.pluginDefinitionSchema)
|
|
100
|
-
})),
|
|
101
|
-
S.schema(s => ({
|
|
102
|
-
TAG: "UIFragmentRegistered",
|
|
103
|
-
_0: s.m(uiFragmentRegisteredDataSchema)
|
|
104
|
-
})),
|
|
105
|
-
S.schema(s => ({
|
|
106
|
-
TAG: "UIFragmentUpdated",
|
|
107
|
-
_0: s.m(uiFragmentUpdatedDataSchema)
|
|
108
|
-
})),
|
|
109
|
-
S.schema(s => ({
|
|
110
|
-
TAG: "UIFragmentDeregistered",
|
|
111
|
-
_0: s.m(uiFragmentDeregisteredDataSchema)
|
|
112
85
|
}))
|
|
113
86
|
]);
|
|
114
87
|
|
|
@@ -140,9 +113,6 @@ let moduleUrl = "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginSpec.
|
|
|
140
113
|
export {
|
|
141
114
|
name,
|
|
142
115
|
Id,
|
|
143
|
-
uiFragmentRegisteredDataSchema,
|
|
144
|
-
uiFragmentUpdatedDataSchema,
|
|
145
|
-
uiFragmentDeregisteredDataSchema,
|
|
146
116
|
versionSupersededDataSchema,
|
|
147
117
|
eventSchema,
|
|
148
118
|
errorSchema,
|
|
@@ -55,7 +55,6 @@ let displayState = (
|
|
|
55
55
|
status,
|
|
56
56
|
statusChange,
|
|
57
57
|
apiSchemaFragment: def.apiSchemaFragment,
|
|
58
|
-
uiFragments: def.uiFragments,
|
|
59
58
|
structure: def.structure,
|
|
60
59
|
dcbEventLog: def.dcbEventLog,
|
|
61
60
|
// A freshly projected row always knows its kind (the definition carries it as a
|
|
@@ -78,14 +77,11 @@ module PluginMapping = Reventless.Projection.Mapping.Make(
|
|
|
78
77
|
let project = ({event, id, meta: {time, user: ?user}}) => {
|
|
79
78
|
let statusChange = {at: time, by: user->Option.getOr("")}
|
|
80
79
|
switch event {
|
|
81
|
-
// Handshake trigger, supersession record
|
|
80
|
+
// Handshake trigger, supersession record and incompatibility
|
|
82
81
|
// events do not change which version is current.
|
|
83
82
|
| PluginSpec.VersionDetected(_)
|
|
84
83
|
| VersionSuperseded(_)
|
|
85
|
-
| IncompatiblePluginDetected(_)
|
|
86
|
-
| UIFragmentRegistered(_)
|
|
87
|
-
| UIFragmentUpdated(_)
|
|
88
|
-
| UIFragmentDeregistered(_) =>
|
|
84
|
+
| IncompatiblePluginDetected(_) =>
|
|
89
85
|
Reventless.Projection.Ignore
|
|
90
86
|
|
|
91
87
|
// A version became live. It becomes current iff it is the highest
|
|
@@ -71,7 +71,6 @@ function displayState(def, status, statusChange, otherConnectedVersions) {
|
|
|
71
71
|
let base_extensionNames = def.extensions.map(extension => extension.extensionPointName);
|
|
72
72
|
let base_extensions = def.extensions;
|
|
73
73
|
let base_apiSchemaFragment = def.apiSchemaFragment;
|
|
74
|
-
let base_uiFragments = def.uiFragments;
|
|
75
74
|
let base_structure = def.structure;
|
|
76
75
|
let base_dcbEventLog = def.dcbEventLog;
|
|
77
76
|
let base_kind = def.kind;
|
|
@@ -87,7 +86,6 @@ function displayState(def, status, statusChange, otherConnectedVersions) {
|
|
|
87
86
|
status: status,
|
|
88
87
|
statusChange: statusChange,
|
|
89
88
|
apiSchemaFragment: base_apiSchemaFragment,
|
|
90
|
-
uiFragments: base_uiFragments,
|
|
91
89
|
structure: base_structure,
|
|
92
90
|
dcbEventLog: base_dcbEventLog,
|
|
93
91
|
kind: base_kind,
|
|
@@ -28,7 +28,6 @@ type state = {
|
|
|
28
28
|
// API target for split-API schema routing. Absent/None means "Domain" (backward compat).
|
|
29
29
|
// "Platform" → excluded from DomainApi runtime schema stitching in updateApiSchema.
|
|
30
30
|
apiTarget?: string,
|
|
31
|
-
uiFragments: @s.matches(Reventless.Plugin.uiFragmentManifestOptionSchema) option<Reventless.Plugin.uiFragmentManifest>,
|
|
32
31
|
// Plugin structure (component metadata) — surfaced via Platform_ComponentDefinitions.
|
|
33
32
|
// None for older plugins whose protocol version did not carry the field.
|
|
34
33
|
structure: @s.matches(Reventless.Plugin.pluginStructureOptionSchema) option<Reventless.Plugin.pluginStructure>,
|
|
@@ -75,7 +74,6 @@ type queryResult = {
|
|
|
75
74
|
status: status,
|
|
76
75
|
apiSchemaFragment: option<Reventless.Plugin.apiSchemaFragment>,
|
|
77
76
|
apiTarget?: string,
|
|
78
|
-
uiFragments: option<Reventless.Plugin.uiFragmentManifest>,
|
|
79
77
|
structure: option<Reventless.Plugin.pluginStructure>,
|
|
80
78
|
dcbEventLog: option<Reventless.Plugin.dcbEventLogDefinition>,
|
|
81
79
|
kind: option<Reventless.Plugin.pluginKind>,
|
|
@@ -25,7 +25,6 @@ let stateSchema = S.schema(s => ({
|
|
|
25
25
|
statusChange: s.m(Message$ReventlessCore.statusChangeSchema),
|
|
26
26
|
apiSchemaFragment: s.m(Plugin$Reventless.apiSchemaFragmentOptionSchema),
|
|
27
27
|
apiTarget: s.m(S.option(S.string)),
|
|
28
|
-
uiFragments: s.m(Plugin$Reventless.uiFragmentManifestOptionSchema),
|
|
29
28
|
structure: s.m(Plugin$Reventless.pluginStructureOptionSchema),
|
|
30
29
|
dcbEventLog: s.m(Plugin$Reventless.dcbEventLogOptionSchema),
|
|
31
30
|
kind: s.m(S.option(Plugin$Reventless.pluginKindSchema)),
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Asserts the canonical JSON shape produced by encodeApiFragmentEntry — the
|
|
2
|
+
// deploy-facing status surface of the API-schema fragment registry, shared by
|
|
3
|
+
// the in-memory and AWS adapters. Status only (no encoded SDL). If this test
|
|
4
|
+
// changes, any deploy waiter parsing Platform_ApiFragments is affected.
|
|
5
|
+
|
|
6
|
+
open JestGlobals
|
|
7
|
+
|
|
8
|
+
let state: ApiFragments.state = {
|
|
9
|
+
pluginId: "Catalog",
|
|
10
|
+
encoded: `{"types":[],"mutations":[],"queries":[],"subscriptions":[],"subscriptionSources":[]}`,
|
|
11
|
+
protocol: "graphql",
|
|
12
|
+
apiTarget: Reventless.Plugin.Domain,
|
|
13
|
+
registeredAt: "2026-05-14T00:00:00.000Z",
|
|
14
|
+
updatedAt: "2026-05-14T00:00:00.000Z",
|
|
15
|
+
pushStatus: "ok",
|
|
16
|
+
pushMessage: "",
|
|
17
|
+
pushedAt: "2026-05-14T00:00:01.000Z",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let encoded = Platform_ApiFragmentsApi.encodeApiFragmentEntry(state)
|
|
21
|
+
let json = encoded->JSON.stringify
|
|
22
|
+
|
|
23
|
+
describe("encodeApiFragmentEntry", () => {
|
|
24
|
+
testSync("produces a JSON object with the expected top-level keys (status only, no SDL)", () => {
|
|
25
|
+
let dict = encoded->JSON.Decode.object->Option.getOr(Dict.make())
|
|
26
|
+
let keys = dict->Dict.keysToArray->Array.toSorted(String.compare)
|
|
27
|
+
expect(keys)->toEqual([
|
|
28
|
+
"apiTarget",
|
|
29
|
+
"pluginId",
|
|
30
|
+
"pushMessage",
|
|
31
|
+
"pushStatus",
|
|
32
|
+
"pushedAt",
|
|
33
|
+
"registeredAt",
|
|
34
|
+
"updatedAt",
|
|
35
|
+
])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
testSync("encodes pluginId", () =>
|
|
39
|
+
expect(json->String.includes("\"pluginId\":\"Catalog\""))->toEqual(true)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
testSync("encodes the apiTarget variant as its bare string", () =>
|
|
43
|
+
expect(json->String.includes("\"apiTarget\":\"Domain\""))->toEqual(true)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
testSync("encodes pushStatus", () =>
|
|
47
|
+
expect(json->String.includes("\"pushStatus\":\"ok\""))->toEqual(true)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
testSync("does not leak the encoded SDL into the status shape", () =>
|
|
51
|
+
expect(json->String.includes("subscriptionSources"))->toEqual(false)
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe("Platform_ApiFragmentsApi.sdl", () => {
|
|
56
|
+
testSync("query field declares the list type", () =>
|
|
57
|
+
expect(Platform_ApiFragmentsApi.sdlQueryField->String.includes("[Platform_ApiFragmentEntry!]!"))
|
|
58
|
+
->toEqual(true)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
testSync("sdlTypes includes Platform_ApiFragmentEntry with pushStatus field", () => {
|
|
62
|
+
let allTypes = Platform_ApiFragmentsApi.sdlTypes->Array.join("\n")
|
|
63
|
+
expect(allTypes->String.includes("Platform_ApiFragmentEntry"))->toEqual(true)
|
|
64
|
+
expect(allTypes->String.includes("pushStatus: String!"))->toEqual(true)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
6
|
+
import * as Platform_ApiFragmentsApi$ReventlessCore from "../../src/admin/Platform_ApiFragmentsApi.res.mjs";
|
|
7
|
+
|
|
8
|
+
let state = {
|
|
9
|
+
pluginId: "Catalog",
|
|
10
|
+
encoded: `{"types":[],"mutations":[],"queries":[],"subscriptions":[],"subscriptionSources":[]}`,
|
|
11
|
+
protocol: "graphql",
|
|
12
|
+
apiTarget: "Domain",
|
|
13
|
+
registeredAt: "2026-05-14T00:00:00.000Z",
|
|
14
|
+
updatedAt: "2026-05-14T00:00:00.000Z",
|
|
15
|
+
pushStatus: "ok",
|
|
16
|
+
pushMessage: "",
|
|
17
|
+
pushedAt: "2026-05-14T00:00:01.000Z"
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let encoded = Platform_ApiFragmentsApi$ReventlessCore.encodeApiFragmentEntry(state);
|
|
21
|
+
|
|
22
|
+
let json = JSON.stringify(encoded);
|
|
23
|
+
|
|
24
|
+
globalThis.describe("encodeApiFragmentEntry", () => {
|
|
25
|
+
globalThis.test("produces a JSON object with the expected top-level keys (status only, no SDL)", () => {
|
|
26
|
+
let dict = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(encoded), {});
|
|
27
|
+
let keys = Object.keys(dict).toSorted(Primitive_string.compare);
|
|
28
|
+
globalThis.expect(keys).toEqual([
|
|
29
|
+
"apiTarget",
|
|
30
|
+
"pluginId",
|
|
31
|
+
"pushMessage",
|
|
32
|
+
"pushStatus",
|
|
33
|
+
"pushedAt",
|
|
34
|
+
"registeredAt",
|
|
35
|
+
"updatedAt"
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
globalThis.test("encodes pluginId", () => {
|
|
39
|
+
globalThis.expect(json.includes("\"pluginId\":\"Catalog\"")).toEqual(true);
|
|
40
|
+
});
|
|
41
|
+
globalThis.test("encodes the apiTarget variant as its bare string", () => {
|
|
42
|
+
globalThis.expect(json.includes("\"apiTarget\":\"Domain\"")).toEqual(true);
|
|
43
|
+
});
|
|
44
|
+
globalThis.test("encodes pushStatus", () => {
|
|
45
|
+
globalThis.expect(json.includes("\"pushStatus\":\"ok\"")).toEqual(true);
|
|
46
|
+
});
|
|
47
|
+
globalThis.test("does not leak the encoded SDL into the status shape", () => {
|
|
48
|
+
globalThis.expect(json.includes("subscriptionSources")).toEqual(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
globalThis.describe("Platform_ApiFragmentsApi.sdl", () => {
|
|
53
|
+
globalThis.test("query field declares the list type", () => {
|
|
54
|
+
globalThis.expect(Platform_ApiFragmentsApi$ReventlessCore.sdlQueryField.includes("[Platform_ApiFragmentEntry!]!")).toEqual(true);
|
|
55
|
+
});
|
|
56
|
+
globalThis.test("sdlTypes includes Platform_ApiFragmentEntry with pushStatus field", () => {
|
|
57
|
+
let allTypes = Platform_ApiFragmentsApi$ReventlessCore.sdlTypes.join("\n");
|
|
58
|
+
globalThis.expect(allTypes.includes("Platform_ApiFragmentEntry")).toEqual(true);
|
|
59
|
+
globalThis.expect(allTypes.includes("pushStatus: String!")).toEqual(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
state,
|
|
65
|
+
encoded,
|
|
66
|
+
json,
|
|
67
|
+
}
|
|
68
|
+
/* encoded Not a pure module */
|
|
@@ -25,7 +25,7 @@ let page: pageManifestEntry = {
|
|
|
25
25
|
requiredAccess: Some("admin"),
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
let state:
|
|
28
|
+
let state: UiFragments.state = {
|
|
29
29
|
pluginId: "Catalog",
|
|
30
30
|
remoteEntryUrl: "https://cdn.example.com/catalog@1.0/remoteEntry.js",
|
|
31
31
|
panels: [panel],
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
open GraphQL_PushPlanner
|
|
3
|
+
|
|
4
|
+
// Minimal neutral fragments. `stitch` always adds Relay base types + `node`, so we assert on the
|
|
5
|
+
// distinctive field names to know which base/fragments landed in each plan.
|
|
6
|
+
let mkFragment = (~query: string): Reventless.Plugin.apiSchemaFragment =>
|
|
7
|
+
GraphQL_Stitcher.encode({
|
|
8
|
+
types: [],
|
|
9
|
+
queries: [query],
|
|
10
|
+
mutations: [],
|
|
11
|
+
subscriptions: [],
|
|
12
|
+
subscriptionSources: [],
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
let adminBase = mkFragment(~query=" Admin_Q: String")
|
|
16
|
+
let domainFrag = mkFragment(~query=" Domain_Q: String")
|
|
17
|
+
let platformFrag = mkFragment(~query=" Platform_Q: String")
|
|
18
|
+
|
|
19
|
+
let fragments = [
|
|
20
|
+
{fragment: domainFrag, target: Domain},
|
|
21
|
+
{fragment: platformFrag, target: Platform},
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
let planFor = (plans: array<pushPlan>, api: planTarget): option<pushPlan> =>
|
|
25
|
+
plans->Array.find(p => p.api == api)
|
|
26
|
+
|
|
27
|
+
describe("GraphQL_PushPlanner.planPushes", () => {
|
|
28
|
+
testSync("split mode: admin base + Platform fragments on the Platform API only", () => {
|
|
29
|
+
let plans = planPushes(~adminBase, ~fragments, ~splitApi=true)
|
|
30
|
+
expect(plans->Array.length)->toBe(2)
|
|
31
|
+
let platform = planFor(plans, PlatformApi)->Belt.Option.getExn
|
|
32
|
+
expect(platform.sdl->String.includes("Admin_Q"))->toBe(true)
|
|
33
|
+
expect(platform.sdl->String.includes("Platform_Q"))->toBe(true)
|
|
34
|
+
expect(platform.sdl->String.includes("Domain_Q"))->toBe(false)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
testSync("split mode: empty base + Domain fragments on the Domain API only (no admin)", () => {
|
|
38
|
+
let plans = planPushes(~adminBase, ~fragments, ~splitApi=true)
|
|
39
|
+
let domain = planFor(plans, DomainApi)->Belt.Option.getExn
|
|
40
|
+
expect(domain.sdl->String.includes("Domain_Q"))->toBe(true)
|
|
41
|
+
expect(domain.sdl->String.includes("Admin_Q"))->toBe(false)
|
|
42
|
+
expect(domain.sdl->String.includes("Platform_Q"))->toBe(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
testSync("unified mode: one Domain API push with admin base + ALL fragments", () => {
|
|
46
|
+
let plans = planPushes(~adminBase, ~fragments, ~splitApi=false)
|
|
47
|
+
expect(plans->Array.length)->toBe(1)
|
|
48
|
+
let only = plans->Array.getUnsafe(0)
|
|
49
|
+
expect(only.api == DomainApi)->toBe(true)
|
|
50
|
+
expect(only.sdl->String.includes("Admin_Q"))->toBe(true)
|
|
51
|
+
expect(only.sdl->String.includes("Domain_Q"))->toBe(true)
|
|
52
|
+
expect(only.sdl->String.includes("Platform_Q"))->toBe(true)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
testSync("empty registry still pushes the admin base (split → Platform API)", () => {
|
|
56
|
+
let plans = planPushes(~adminBase, ~fragments=[], ~splitApi=true)
|
|
57
|
+
let platform = planFor(plans, PlatformApi)->Belt.Option.getExn
|
|
58
|
+
expect(platform.sdl->String.includes("Admin_Q"))->toBe(true)
|
|
59
|
+
let domain = planFor(plans, DomainApi)->Belt.Option.getExn
|
|
60
|
+
expect(domain.sdl->String.includes("Admin_Q"))->toBe(false)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Belt_Option from "@rescript/runtime/lib/es6/Belt_Option.js";
|
|
4
|
+
import * as GraphQL_Stitcher$ReventlessCore from "../../src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
5
|
+
import * as GraphQL_PushPlanner$ReventlessCore from "../../src/components/Api/GraphQL_PushPlanner.res.mjs";
|
|
6
|
+
|
|
7
|
+
function mkFragment(query) {
|
|
8
|
+
return GraphQL_Stitcher$ReventlessCore.encode({
|
|
9
|
+
types: [],
|
|
10
|
+
mutations: [],
|
|
11
|
+
queries: [query],
|
|
12
|
+
subscriptions: [],
|
|
13
|
+
subscriptionSources: []
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let adminBase = mkFragment(" Admin_Q: String");
|
|
18
|
+
|
|
19
|
+
let domainFrag = mkFragment(" Domain_Q: String");
|
|
20
|
+
|
|
21
|
+
let platformFrag = mkFragment(" Platform_Q: String");
|
|
22
|
+
|
|
23
|
+
let fragments = [
|
|
24
|
+
{
|
|
25
|
+
fragment: domainFrag,
|
|
26
|
+
target: "Domain"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
fragment: platformFrag,
|
|
30
|
+
target: "Platform"
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
function planFor(plans, api) {
|
|
35
|
+
return plans.find(p => p.api === api);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
globalThis.describe("GraphQL_PushPlanner.planPushes", () => {
|
|
39
|
+
globalThis.test("split mode: admin base + Platform fragments on the Platform API only", () => {
|
|
40
|
+
let plans = GraphQL_PushPlanner$ReventlessCore.planPushes(adminBase, fragments, true);
|
|
41
|
+
globalThis.expect(plans.length).toBe(2);
|
|
42
|
+
let platform = Belt_Option.getExn(planFor(plans, "PlatformApi"));
|
|
43
|
+
globalThis.expect(platform.sdl.includes("Admin_Q")).toBe(true);
|
|
44
|
+
globalThis.expect(platform.sdl.includes("Platform_Q")).toBe(true);
|
|
45
|
+
globalThis.expect(platform.sdl.includes("Domain_Q")).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
globalThis.test("split mode: empty base + Domain fragments on the Domain API only (no admin)", () => {
|
|
48
|
+
let plans = GraphQL_PushPlanner$ReventlessCore.planPushes(adminBase, fragments, true);
|
|
49
|
+
let domain = Belt_Option.getExn(planFor(plans, "DomainApi"));
|
|
50
|
+
globalThis.expect(domain.sdl.includes("Domain_Q")).toBe(true);
|
|
51
|
+
globalThis.expect(domain.sdl.includes("Admin_Q")).toBe(false);
|
|
52
|
+
globalThis.expect(domain.sdl.includes("Platform_Q")).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
globalThis.test("unified mode: one Domain API push with admin base + ALL fragments", () => {
|
|
55
|
+
let plans = GraphQL_PushPlanner$ReventlessCore.planPushes(adminBase, fragments, false);
|
|
56
|
+
globalThis.expect(plans.length).toBe(1);
|
|
57
|
+
let only = plans[0];
|
|
58
|
+
globalThis.expect(only.api === "DomainApi").toBe(true);
|
|
59
|
+
globalThis.expect(only.sdl.includes("Admin_Q")).toBe(true);
|
|
60
|
+
globalThis.expect(only.sdl.includes("Domain_Q")).toBe(true);
|
|
61
|
+
globalThis.expect(only.sdl.includes("Platform_Q")).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
globalThis.test("empty registry still pushes the admin base (split → Platform API)", () => {
|
|
64
|
+
let plans = GraphQL_PushPlanner$ReventlessCore.planPushes(adminBase, [], true);
|
|
65
|
+
let platform = Belt_Option.getExn(planFor(plans, "PlatformApi"));
|
|
66
|
+
globalThis.expect(platform.sdl.includes("Admin_Q")).toBe(true);
|
|
67
|
+
let domain = Belt_Option.getExn(planFor(plans, "DomainApi"));
|
|
68
|
+
globalThis.expect(domain.sdl.includes("Admin_Q")).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
mkFragment,
|
|
74
|
+
adminBase,
|
|
75
|
+
domainFrag,
|
|
76
|
+
platformFrag,
|
|
77
|
+
fragments,
|
|
78
|
+
planFor,
|
|
79
|
+
}
|
|
80
|
+
/* adminBase Not a pure module */
|