@reventlessdev/reventless-core 3.0.0-alpha.239 → 3.0.0-alpha.241
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 +35 -0
- package/package.json +11 -11
- package/src/Message.res +11 -11
- package/src/Message.res.mjs +15 -14
- package/src/Projection.res +2 -2
- package/src/Projection.res.mjs +5 -4
- package/src/adapter/Monitoring/Monitoring.res +24 -2
- package/src/adapter/Monitoring/Monitoring.res.mjs +3 -3
- package/src/admin/Platform_Admin.res +1 -16
- package/src/admin/Platform_Admin.res.mjs +2 -30
- package/src/admin/Platform_AdminApi.res.mjs +3 -3
- package/src/admin/Platform_Admin_Structure.res +58 -113
- package/src/admin/Platform_Admin_Structure.res.mjs +24 -139
- package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +12 -12
- package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments.res.mjs +14 -14
- package/src/components/Aggregate/Aggregate_Callback.res +2 -2
- package/src/components/Aggregate/Aggregate_Callback.res.mjs +4 -4
- package/src/components/Api/ApiAllowedStatesHelpers.res.mjs +3 -3
- package/src/components/Api/ApiNoApiHelpers.res.mjs +5 -5
- package/src/components/Api/ApiTargetStateHelpers.res.mjs +3 -3
- package/src/components/Api/GraphQL_FragmentGenerator.res +18 -12
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +7 -13
- package/src/components/Api/GraphQL_SchemaInspector.res +1 -1
- package/src/components/Api/GraphQL_SchemaInspector.res.mjs +1 -1
- package/src/components/Api/MCP_SchemaGenerator.res +1 -1
- package/src/components/Api/MCP_SchemaGenerator.res.mjs +1 -1
- package/src/components/Api/SchemaType.res +6 -10
- package/src/components/Api/SchemaType.res.mjs +4 -4
- package/src/components/Api/StorageRefFields.res +1 -1
- package/src/components/Api/StorageRefFields.res.mjs +1 -1
- package/src/components/Api/SuryToJsonSchema.res +11 -0
- package/src/components/Api/SuryToJsonSchema.res.mjs +2 -1
- package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -4
- package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +22 -23
- package/src/components/CommandTopic/CommandTopic_Callback.res +1 -2
- package/src/components/CommandTopic/CommandTopic_Callback.res.mjs +3 -4
- package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -1
- package/src/components/CommandTopic/CommandTopic_Helpers.res.mjs +3 -3
- package/src/components/Counter/Counter.res.mjs +2 -2
- package/src/components/Counter/Counter_Callback.res.mjs +4 -4
- package/src/components/Counter/Counter_Operations.res.mjs +4 -4
- package/src/components/Dcb/Dcb_Builder.res +1 -1
- package/src/components/Dcb/Dcb_Builder.res.mjs +8 -6
- package/src/components/DcbEventLog/DcbEventLog_Builder.res.mjs +4 -3
- package/src/components/EventCollector/EventCollector_Builder.res +1 -1
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +1 -2
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +11 -12
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res +3 -4
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res.mjs +24 -25
- package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +1 -1
- package/src/components/SideEffectHandler/SideEffectHandler_Callback.res.mjs +3 -3
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +2 -2
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +4 -4
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +1 -1
- package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +3 -3
- package/src/plugin/api/PluginBaseFragment.res +12 -29
- package/src/plugin/api/PluginBaseFragment.res.mjs +3 -22
- package/src/plugin/component/Capability_Inference.res +118 -1
- package/src/plugin/component/Capability_Inference.res.mjs +128 -1
- package/src/plugin/component/Plugin_Builder.res +2 -2
- package/src/plugin/component/Plugin_Builder.res.mjs +2 -1
- package/src/plugin/component/Plugin_Helpers.res +11 -11
- package/src/plugin/component/Plugin_Helpers.res.mjs +11 -10
- package/src/plugin/component/Plugin_Structure.res +349 -274
- package/src/plugin/component/Plugin_Structure.res.mjs +212 -175
- package/src/plugin/component/Plugin_SubscriptionSchema.res +1 -1
- package/src/plugin/component/Plugin_SubscriptionSchema.res.mjs +1 -1
- package/src/plugin/component/SchemaWalker.res +11 -11
- package/src/plugin/component/SchemaWalker.res.mjs +8 -9
- package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +15 -15
- package/src/plugin/lifecycle/PluginBehavior.res.mjs +55 -55
- package/src/plugin/lifecycle/PluginSpec.res +22 -7
- package/src/plugin/lifecycle/PluginSpec.res.mjs +91 -36
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +36 -9
- package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +33 -22
- package/src/util/Interstack.res +3 -3
- package/src/util/LogFormat.res +1 -2
- package/src/util/LogFormat.res.mjs +2 -2
- package/tests/IdentityTest.res +6 -7
- package/tests/IdentityTest.res.mjs +7 -9
- package/tests/adapter/MonitoringTest.res +30 -3
- package/tests/adapter/MonitoringTest.res.mjs +31 -12
- package/tests/adapter/RuntimeExtensionHookReachTest.res +0 -1
- package/tests/adapter/RuntimeExtensionHookReachTest.res.mjs +5 -7
- package/tests/admin/Platform_Admin_StructureTest.res +254 -0
- package/tests/admin/Platform_Admin_StructureTest.res.mjs +219 -0
- package/tests/aggregate/AggregateCacheTest.res +0 -1
- package/tests/aggregate/AggregateCacheTest.res.mjs +13 -15
- package/tests/aggregate/AggregateConflictTest.res +0 -1
- package/tests/aggregate/AggregateConflictTest.res.mjs +7 -9
- package/tests/aggregate/AggregateFixtures.res +0 -1
- package/tests/aggregate/AggregateFixtures.res.mjs +19 -21
- package/tests/aggregate/AggregateSnapshotTest.res +1 -2
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +15 -16
- package/tests/api/ApiNoApiTest.res.mjs +17 -9
- package/tests/api/GraphQL_FragmentGeneratorTest.res +62 -1
- package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +102 -58
- package/tests/api/PsInternalFieldsReadModel.res +21 -0
- package/tests/api/PsInternalFieldsReadModel.res.mjs +74 -0
- package/tests/api/StorageRefFieldsTest.res.mjs +13 -13
- package/tests/api/SuryToJsonSchemaTest.res +43 -0
- package/tests/api/SuryToJsonSchemaTest.res.mjs +352 -849
- package/tests/commandgenerator/CommandGeneratorFixtures.res +0 -1
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +19 -21
- package/tests/commandgenerator/OwnerStampingTest.res +0 -1
- package/tests/commandgenerator/OwnerStampingTest.res.mjs +9 -11
- package/tests/commandtopic/CommandTopicCallbackFixtures.res +0 -1
- package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +7 -9
- package/tests/counter/CounterFixtures.res +0 -1
- package/tests/counter/CounterFixtures.res.mjs +1 -4
- package/tests/dcb/DcbDecodeTest.res.mjs +24 -24
- package/tests/dcb/DcbEventLogOperationsTest.res +2 -2
- package/tests/dcb/DcbEventLogOperationsTest.res.mjs +3 -3
- package/tests/dcb/DcbEventLogSchemaTest.res.mjs +7 -7
- package/tests/dcb/DcbFixtures.res +0 -1
- package/tests/dcb/DcbFixtures.res.mjs +87 -89
- package/tests/dcb/DcbTagTest.res.mjs +6 -6
- package/tests/dcb/DcbValidationTest.res.mjs +52 -52
- package/tests/eventlog/EventLogFixtures.res +0 -1
- package/tests/eventlog/EventLogFixtures.res.mjs +7 -9
- package/tests/eventmapper/EventMapperFixtures.res +0 -1
- package/tests/eventmapper/EventMapperFixtures.res.mjs +24 -26
- package/tests/eventtopic/EventTopicFixtures.res +0 -1
- package/tests/eventtopic/EventTopicFixtures.res.mjs +7 -9
- package/tests/extensionpoint/ExtensionPointFixtures.res +0 -1
- package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +10 -12
- package/tests/extensionpoint/ExtensionPointOperationsTest.res +1 -2
- package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +9 -10
- package/tests/logger/LogFormatTest.res +3 -2
- package/tests/logger/LogFormatTest.res.mjs +7 -9
- package/tests/message/MessageTest.res +2 -2
- package/tests/message/MessageTest.res.mjs +8 -7
- package/tests/message/MetaEnvelopeTest.res +5 -5
- package/tests/message/MetaEnvelopeTest.res.mjs +12 -11
- package/tests/plugin/ManifestVisibilityTest.res.mjs +7 -7
- package/tests/plugin/PluginDefinitionScalars.res +5 -3
- package/tests/plugin/PluginDefinitionScalars.res.mjs +2 -2
- package/tests/plugin/PluginLifecycleCorpusTest.res +4 -3
- package/tests/plugin/PluginLifecycleCorpusTest.res.mjs +3 -6
- package/tests/plugin/PluginSpecExperiment.res +0 -1
- package/tests/plugin/PluginSpecExperiment.res.mjs +12 -14
- package/tests/plugin/PluginStructureTest.res +188 -1
- package/tests/plugin/PluginStructureTest.res.mjs +293 -102
- package/tests/plugin/Plugin_EventQuerySchemaTest.res.mjs +3 -3
- package/tests/plugin/SchemaWalkerTest.res +0 -1
- package/tests/plugin/SchemaWalkerTest.res.mjs +17 -19
- package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +5 -5
- package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +8 -8
- package/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs +5 -5
- package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +9 -9
- package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +5 -5
- package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +8 -8
- package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +15 -15
- package/tests/plugin/StateChangeSlice/PsTypoStore.res +31 -0
- package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +62 -0
- package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +8 -8
- package/tests/plugin/StateChangeSlice/PsUploadImages.res +40 -0
- package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +66 -0
- package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +13 -12
- package/tests/plugin/StateViewSlice/PsAvailableProductsView.res.mjs +8 -8
- package/tests/plugin/StateViewSlice/PsCategoriesView.res.mjs +6 -6
- package/tests/plugin/StateViewSlice/PsCustomersView.res.mjs +10 -10
- package/tests/plugin/StateViewSlice/PsGatedView.res.mjs +6 -6
- package/tests/plugin/StateViewSlice/PsOrdersView.res.mjs +9 -9
- package/tests/plugin/StateViewSlice/PsShipmentsView.res.mjs +11 -10
- package/tests/plugin/pluginDefinitionRequiredScalars.txt +15 -0
- package/tests/querydb/QueryDbFixtures.res +0 -1
- package/tests/querydb/QueryDbFixtures.res.mjs +5 -7
- package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +0 -1
- package/tests/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +4 -6
- package/tests/util/CommandPublisherTest.res +0 -1
- package/tests/util/CommandPublisherTest.res.mjs +4 -6
package/src/util/Interstack.res
CHANGED
|
@@ -21,7 +21,7 @@ module DefaultTaskQuery = ReventlessInterop.Query.Task.Make({
|
|
|
21
21
|
let requiredFields = ["name"]
|
|
22
22
|
let optionalFields = ["bucketNames", "sideEffectSources"]
|
|
23
23
|
let fromJson = (json: JSON.t) =>
|
|
24
|
-
try Ok(json->S.parseOrThrow(ReventlessInterop.Task.resolvedOutputsSchema))
|
|
24
|
+
try Ok(json->S.parseOrThrow(~to=ReventlessInterop.Task.resolvedOutputsSchema))
|
|
25
25
|
catch {
|
|
26
26
|
| exn =>
|
|
27
27
|
let msg =
|
|
@@ -35,7 +35,7 @@ module DefaultEventMapperQuery = ReventlessInterop.Query.EventMapper.Make({
|
|
|
35
35
|
let requiredFields = ["name", "eventCollector"]
|
|
36
36
|
let optionalFields = ["counter"]
|
|
37
37
|
let fromJson = (json: JSON.t) =>
|
|
38
|
-
try Ok(json->S.parseOrThrow(ReventlessInterop.EventMapper.resolvedOutputsSchema))
|
|
38
|
+
try Ok(json->S.parseOrThrow(~to=ReventlessInterop.EventMapper.resolvedOutputsSchema))
|
|
39
39
|
catch {
|
|
40
40
|
| exn =>
|
|
41
41
|
let msg =
|
|
@@ -54,7 +54,7 @@ module DefaultExtensionPointQuery = ReventlessInterop.Query.ExtensionPoint.Make(
|
|
|
54
54
|
let requiredFields = ["name", "commandTopic", "eventTopic"]
|
|
55
55
|
let optionalFields = []
|
|
56
56
|
let fromJson = (json: JSON.t) =>
|
|
57
|
-
try Ok(json->S.parseOrThrow(ReventlessInterop.ExtensionPoint.resolvedOutputsSchema))
|
|
57
|
+
try Ok(json->S.parseOrThrow(~to=ReventlessInterop.ExtensionPoint.resolvedOutputsSchema))
|
|
58
58
|
catch {
|
|
59
59
|
| exn =>
|
|
60
60
|
let msg =
|
package/src/util/LogFormat.res
CHANGED
|
@@ -248,8 +248,7 @@ let fmtState = (~name: string, ~id: string, ~seq: int): string =>
|
|
|
248
248
|
`state=${name}(${id}) seq=${seq->Int.toString}`
|
|
249
249
|
|
|
250
250
|
// "err=<message>"
|
|
251
|
-
let fmtExn = (e: exn): string =>
|
|
252
|
-
`err=${e->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown")}`
|
|
251
|
+
let fmtExn = (e: exn): string => `err=${e->Reventless.Util_Sury.exnMessage}`
|
|
253
252
|
|
|
254
253
|
// ─── Legacy aliases (kept for existing callers) ──────────────────────────────
|
|
255
254
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
4
4
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
5
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
6
|
-
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
7
6
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
7
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
9
8
|
import * as AnsiStyle$Reventless from "@reventlessdev/reventless-spec/src/AnsiStyle.res.mjs";
|
|
9
|
+
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
10
10
|
import * as Message$ReventlessCore from "../Message.res.mjs";
|
|
11
11
|
|
|
12
12
|
let maxValueChars = Stdlib_Option.getOr(Stdlib_Option.flatMap(process.env["REVENTLESS_LOG_MAX_VALUE"], v => Stdlib_Int.fromString(v, undefined)), 512);
|
|
@@ -204,7 +204,7 @@ function fmtState(name, id, seq) {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
function fmtExn(e) {
|
|
207
|
-
return `err=` +
|
|
207
|
+
return `err=` + Util_Sury$Reventless.exnMessage(e, undefined);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
function commandJsonsToLogMessages(cmdJsons) {
|
package/tests/IdentityTest.res
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
open JestGlobals
|
|
2
2
|
|
|
3
|
-
S.enableJson()
|
|
4
3
|
|
|
5
4
|
describe("Identity", () => {
|
|
6
5
|
describe("anonymous", () => {
|
|
@@ -71,8 +70,8 @@ describe("Identity", () => {
|
|
|
71
70
|
claims: Dict.fromArray([("tenant", "acme")]),
|
|
72
71
|
provider: Cognito,
|
|
73
72
|
}
|
|
74
|
-
let json = identity->
|
|
75
|
-
let decoded = json->
|
|
73
|
+
let json = identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema)
|
|
74
|
+
let decoded = json->Reventless.Util_Sury.fromJson(Reventless.Identity.schema)
|
|
76
75
|
expect(decoded)->toEqual(identity)
|
|
77
76
|
})
|
|
78
77
|
|
|
@@ -83,8 +82,8 @@ describe("Identity", () => {
|
|
|
83
82
|
groups: [],
|
|
84
83
|
provider: Custom("oauth2"),
|
|
85
84
|
}
|
|
86
|
-
let json = identity->
|
|
87
|
-
let decoded = json->
|
|
85
|
+
let json = identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema)
|
|
86
|
+
let decoded = json->Reventless.Util_Sury.fromJson(Reventless.Identity.schema)
|
|
88
87
|
expect(decoded)->toEqual(identity)
|
|
89
88
|
})
|
|
90
89
|
|
|
@@ -95,8 +94,8 @@ describe("Identity", () => {
|
|
|
95
94
|
groups: [],
|
|
96
95
|
provider: InMemory,
|
|
97
96
|
}
|
|
98
|
-
let json = identity->
|
|
99
|
-
let decoded = json->
|
|
97
|
+
let json = identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema)
|
|
98
|
+
let decoded = json->Reventless.Util_Sury.fromJson(Reventless.Identity.schema)
|
|
100
99
|
expect(decoded.userId)->toBe("user-3")->ignore
|
|
101
100
|
expect(decoded.provider)->toEqual(Reventless.Identity.InMemory)
|
|
102
101
|
})
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as S from "sury/src/S.res.mjs";
|
|
4
3
|
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
4
|
+
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
5
5
|
import * as RequestContext$ReventlessCore from "../src/RequestContext.res.mjs";
|
|
6
6
|
|
|
7
|
-
S.enableJson();
|
|
8
|
-
|
|
9
7
|
globalThis.describe("Identity", () => {
|
|
10
8
|
globalThis.describe("anonymous", () => {
|
|
11
9
|
globalThis.test("has userId 'anonymous'", () => {
|
|
@@ -86,8 +84,8 @@ globalThis.describe("Identity", () => {
|
|
|
86
84
|
claims: identity_claims,
|
|
87
85
|
provider: "Cognito"
|
|
88
86
|
};
|
|
89
|
-
let json =
|
|
90
|
-
let decoded =
|
|
87
|
+
let json = Util_Sury$Reventless.toJson(identity, Identity$Reventless.schema);
|
|
88
|
+
let decoded = Util_Sury$Reventless.fromJson(json, Identity$Reventless.schema);
|
|
91
89
|
globalThis.expect(decoded).toEqual(identity);
|
|
92
90
|
});
|
|
93
91
|
globalThis.test("roundtrip with Custom provider", () => {
|
|
@@ -102,8 +100,8 @@ globalThis.describe("Identity", () => {
|
|
|
102
100
|
groups: identity_groups,
|
|
103
101
|
provider: identity_provider
|
|
104
102
|
};
|
|
105
|
-
let json =
|
|
106
|
-
let decoded =
|
|
103
|
+
let json = Util_Sury$Reventless.toJson(identity, Identity$Reventless.schema);
|
|
104
|
+
let decoded = Util_Sury$Reventless.fromJson(json, Identity$Reventless.schema);
|
|
107
105
|
globalThis.expect(decoded).toEqual(identity);
|
|
108
106
|
});
|
|
109
107
|
globalThis.test("roundtrip without optional claims", () => {
|
|
@@ -114,8 +112,8 @@ globalThis.describe("Identity", () => {
|
|
|
114
112
|
groups: identity_groups,
|
|
115
113
|
provider: "InMemory"
|
|
116
114
|
};
|
|
117
|
-
let json =
|
|
118
|
-
let decoded =
|
|
115
|
+
let json = Util_Sury$Reventless.toJson(identity, Identity$Reventless.schema);
|
|
116
|
+
let decoded = Util_Sury$Reventless.fromJson(json, Identity$Reventless.schema);
|
|
119
117
|
globalThis.expect(decoded.userId).toBe("user-3");
|
|
120
118
|
globalThis.expect(decoded.provider).toEqual("InMemory");
|
|
121
119
|
});
|
|
@@ -15,7 +15,7 @@ describe("Monitoring", () => {
|
|
|
15
15
|
testSync("use registers a backend that receives every notify, role and name", () => {
|
|
16
16
|
let recorded: array<(Monitoring.unitKind, string)> = []
|
|
17
17
|
module Recorder: Monitoring.Backend = {
|
|
18
|
-
let onProvisioned = (~kind, ~name, ~component as _, ~plugin as _, ~platform as _) =>
|
|
18
|
+
let onProvisioned = (~kind, ~name, ~component as _, ~plugin as _, ~platform as _, ~logLocator as _) =>
|
|
19
19
|
recorded->Array.push((kind, name))
|
|
20
20
|
}
|
|
21
21
|
Monitoring.use(module(Recorder: Monitoring.Backend))
|
|
@@ -39,7 +39,7 @@ describe("Monitoring", () => {
|
|
|
39
39
|
testSync("notify forwards the static name and component resource to the backend", () => {
|
|
40
40
|
let seen: ref<option<(string, ReventlessInfra.Adapter.resource)>> = ref(None)
|
|
41
41
|
module Capture: Monitoring.Backend = {
|
|
42
|
-
let onProvisioned = (~kind as _, ~name, ~component, ~plugin as _, ~platform as _) =>
|
|
42
|
+
let onProvisioned = (~kind as _, ~name, ~component, ~plugin as _, ~platform as _, ~logLocator as _) =>
|
|
43
43
|
seen := Some((name, component))
|
|
44
44
|
}
|
|
45
45
|
Monitoring.use(module(Capture: Monitoring.Backend))
|
|
@@ -56,7 +56,7 @@ describe("Monitoring", () => {
|
|
|
56
56
|
testSync("notify delivers the ambient plugin/platform inside a construct scope, None outside", () => {
|
|
57
57
|
let seen: array<(option<string>, option<string>)> = []
|
|
58
58
|
module OwnerCapture: Monitoring.Backend = {
|
|
59
|
-
let onProvisioned = (~kind as _, ~name as _, ~component as _, ~plugin, ~platform) =>
|
|
59
|
+
let onProvisioned = (~kind as _, ~name as _, ~component as _, ~plugin, ~platform, ~logLocator as _) =>
|
|
60
60
|
seen->Array.push((plugin, platform))
|
|
61
61
|
}
|
|
62
62
|
Monitoring.use(module(OwnerCapture: Monitoring.Backend))
|
|
@@ -80,4 +80,31 @@ describe("Monitoring", () => {
|
|
|
80
80
|
|
|
81
81
|
Monitoring.use(module(Monitoring.Noop))
|
|
82
82
|
})
|
|
83
|
+
|
|
84
|
+
// Unlike the owner, the locator is not ambient — the seam only forwards what the provisioning
|
|
85
|
+
// site passed. A site with nothing to say omits it, and the backend must see that as "this unit
|
|
86
|
+
// has no logs of its own" rather than receiving a fabricated address.
|
|
87
|
+
testSync("notify forwards the log locator when given one, None when omitted", () => {
|
|
88
|
+
// The registry only forwards the locator; it never resolves it. A structural stand-in keeps
|
|
89
|
+
// @pulumi/pulumi out of the Jest run, exactly as `stubResource` does.
|
|
90
|
+
let stubLocator: Pulumi.Output.t<string> = %raw(`{}`)
|
|
91
|
+
let seen: array<bool> = []
|
|
92
|
+
module LocatorCapture: Monitoring.Backend = {
|
|
93
|
+
let onProvisioned = (~kind as _, ~name as _, ~component as _, ~plugin as _, ~platform as _, ~logLocator) =>
|
|
94
|
+
seen->Array.push(logLocator->Option.isSome)
|
|
95
|
+
}
|
|
96
|
+
Monitoring.use(module(LocatorCapture: Monitoring.Backend))
|
|
97
|
+
|
|
98
|
+
Monitoring.notify(
|
|
99
|
+
~kind=CommandHandler,
|
|
100
|
+
~name="AllAggregatesCmdHandler",
|
|
101
|
+
~component=stubResource,
|
|
102
|
+
~logLocator=stubLocator,
|
|
103
|
+
)
|
|
104
|
+
Monitoring.notify(~kind=DeadLetterSink, ~name="DeadLetterQueue", ~component=stubResource)
|
|
105
|
+
|
|
106
|
+
expect(seen)->toEqual([true, false])
|
|
107
|
+
|
|
108
|
+
Monitoring.use(module(Monitoring.Noop))
|
|
109
|
+
})
|
|
83
110
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
3
4
|
import * as Monitoring$ReventlessCore from "../../src/adapter/Monitoring/Monitoring.res.mjs";
|
|
4
5
|
import * as ResourceAttribution$ReventlessCore from "../../src/ResourceAttribution.res.mjs";
|
|
5
6
|
|
|
@@ -7,12 +8,12 @@ let stubResource = {};
|
|
|
7
8
|
|
|
8
9
|
globalThis.describe("Monitoring", () => {
|
|
9
10
|
globalThis.test("Noop default: notify is silent and does not throw", () => {
|
|
10
|
-
Monitoring$ReventlessCore.notify("CommandHandler", "before", stubResource);
|
|
11
|
+
Monitoring$ReventlessCore.notify("CommandHandler", "before", stubResource, undefined);
|
|
11
12
|
globalThis.expect(true).toBe(true);
|
|
12
13
|
});
|
|
13
14
|
globalThis.test("use registers a backend that receives every notify, role and name", () => {
|
|
14
15
|
let recorded = [];
|
|
15
|
-
let onProvisioned = (kind, name, param, param$1, param$2) => {
|
|
16
|
+
let onProvisioned = (kind, name, param, param$1, param$2, param$3) => {
|
|
16
17
|
recorded.push([
|
|
17
18
|
kind,
|
|
18
19
|
name
|
|
@@ -22,13 +23,13 @@ globalThis.describe("Monitoring", () => {
|
|
|
22
23
|
onProvisioned: onProvisioned
|
|
23
24
|
};
|
|
24
25
|
Monitoring$ReventlessCore.use(Recorder);
|
|
25
|
-
Monitoring$ReventlessCore.notify("CommandHandler", "AllAggregatesCmdHandler", stubResource);
|
|
26
|
-
Monitoring$ReventlessCore.notify("Projection", "AllStateViewSlices", stubResource);
|
|
27
|
-
Monitoring$ReventlessCore.notify("DeadLetterSink", "DeadLetterQueue", stubResource);
|
|
26
|
+
Monitoring$ReventlessCore.notify("CommandHandler", "AllAggregatesCmdHandler", stubResource, undefined);
|
|
27
|
+
Monitoring$ReventlessCore.notify("Projection", "AllStateViewSlices", stubResource, undefined);
|
|
28
|
+
Monitoring$ReventlessCore.notify("DeadLetterSink", "DeadLetterQueue", stubResource, undefined);
|
|
28
29
|
Monitoring$ReventlessCore.notify({
|
|
29
30
|
TAG: "Other",
|
|
30
31
|
_0: "Counter"
|
|
31
|
-
}, "ProductCounter", stubResource);
|
|
32
|
+
}, "ProductCounter", stubResource, undefined);
|
|
32
33
|
globalThis.expect(recorded).toEqual([
|
|
33
34
|
[
|
|
34
35
|
"CommandHandler",
|
|
@@ -56,7 +57,7 @@ globalThis.describe("Monitoring", () => {
|
|
|
56
57
|
let seen = {
|
|
57
58
|
contents: undefined
|
|
58
59
|
};
|
|
59
|
-
let onProvisioned = (param, name, component, param$1, param$2) => {
|
|
60
|
+
let onProvisioned = (param, name, component, param$1, param$2, param$3) => {
|
|
60
61
|
seen.contents = [
|
|
61
62
|
name,
|
|
62
63
|
component
|
|
@@ -66,7 +67,7 @@ globalThis.describe("Monitoring", () => {
|
|
|
66
67
|
onProvisioned: onProvisioned
|
|
67
68
|
};
|
|
68
69
|
Monitoring$ReventlessCore.use(Capture);
|
|
69
|
-
Monitoring$ReventlessCore.notify("Scheduler", "HeartbeatUnit", stubResource);
|
|
70
|
+
Monitoring$ReventlessCore.notify("Scheduler", "HeartbeatUnit", stubResource, undefined);
|
|
70
71
|
let match = seen.contents;
|
|
71
72
|
if (match !== undefined) {
|
|
72
73
|
globalThis.expect(match[0]).toBe("HeartbeatUnit");
|
|
@@ -77,7 +78,7 @@ globalThis.describe("Monitoring", () => {
|
|
|
77
78
|
});
|
|
78
79
|
globalThis.test("notify delivers the ambient plugin/platform inside a construct scope, None outside", () => {
|
|
79
80
|
let seen = [];
|
|
80
|
-
let onProvisioned = (param, param$1, param$2, plugin, platform) => {
|
|
81
|
+
let onProvisioned = (param, param$1, param$2, plugin, platform, param$3) => {
|
|
81
82
|
seen.push([
|
|
82
83
|
plugin,
|
|
83
84
|
platform
|
|
@@ -87,11 +88,11 @@ globalThis.describe("Monitoring", () => {
|
|
|
87
88
|
onProvisioned: onProvisioned
|
|
88
89
|
};
|
|
89
90
|
Monitoring$ReventlessCore.use(OwnerCapture);
|
|
90
|
-
Monitoring$ReventlessCore.notify("CommandHandler", "ApiRouter", stubResource);
|
|
91
|
+
Monitoring$ReventlessCore.notify("CommandHandler", "ApiRouter", stubResource, undefined);
|
|
91
92
|
let prev = ResourceAttribution$ReventlessCore.enter("online-shop", "Ordering");
|
|
92
|
-
Monitoring$ReventlessCore.notify("CommandHandler", "AllAggregatesCmdHandler", stubResource);
|
|
93
|
+
Monitoring$ReventlessCore.notify("CommandHandler", "AllAggregatesCmdHandler", stubResource, undefined);
|
|
93
94
|
ResourceAttribution$ReventlessCore.restore(prev);
|
|
94
|
-
Monitoring$ReventlessCore.notify("Projection", "AllStateViewSlices", stubResource);
|
|
95
|
+
Monitoring$ReventlessCore.notify("Projection", "AllStateViewSlices", stubResource, undefined);
|
|
95
96
|
globalThis.expect(seen).toEqual([
|
|
96
97
|
[
|
|
97
98
|
undefined,
|
|
@@ -108,6 +109,24 @@ globalThis.describe("Monitoring", () => {
|
|
|
108
109
|
]);
|
|
109
110
|
Monitoring$ReventlessCore.use(Monitoring$ReventlessCore.Noop);
|
|
110
111
|
});
|
|
112
|
+
globalThis.test("notify forwards the log locator when given one, None when omitted", () => {
|
|
113
|
+
let stubLocator = {};
|
|
114
|
+
let seen = [];
|
|
115
|
+
let onProvisioned = (param, param$1, param$2, param$3, param$4, logLocator) => {
|
|
116
|
+
seen.push(Stdlib_Option.isSome(logLocator));
|
|
117
|
+
};
|
|
118
|
+
let LocatorCapture = {
|
|
119
|
+
onProvisioned: onProvisioned
|
|
120
|
+
};
|
|
121
|
+
Monitoring$ReventlessCore.use(LocatorCapture);
|
|
122
|
+
Monitoring$ReventlessCore.notify("CommandHandler", "AllAggregatesCmdHandler", stubResource, stubLocator);
|
|
123
|
+
Monitoring$ReventlessCore.notify("DeadLetterSink", "DeadLetterQueue", stubResource, undefined);
|
|
124
|
+
globalThis.expect(seen).toEqual([
|
|
125
|
+
true,
|
|
126
|
+
false
|
|
127
|
+
]);
|
|
128
|
+
Monitoring$ReventlessCore.use(Monitoring$ReventlessCore.Noop);
|
|
129
|
+
});
|
|
111
130
|
});
|
|
112
131
|
|
|
113
132
|
export {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as
|
|
3
|
+
import * as Sury from "sury";
|
|
4
4
|
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
import * as Effect from "effect/Effect";
|
|
@@ -8,12 +8,10 @@ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_excep
|
|
|
8
8
|
import * as RuntimeExtension$ReventlessCore from "../../src/adapter/RuntimeExtension/RuntimeExtension.res.mjs";
|
|
9
9
|
import * as CommandGenerator_Callback$ReventlessCore from "../../src/components/CommandGenerator/CommandGenerator_Callback.res.mjs";
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
let commandSchema = S.schema(s => ({
|
|
11
|
+
let commandSchema = Sury.$schema(s => ({
|
|
14
12
|
TAG: "Add",
|
|
15
|
-
productId: s.m(
|
|
16
|
-
name: s.m(
|
|
13
|
+
productId: s.m(Sury.string),
|
|
14
|
+
name: s.m(Sury.string)
|
|
17
15
|
}));
|
|
18
16
|
|
|
19
17
|
let published = {
|
|
@@ -94,4 +92,4 @@ export {
|
|
|
94
92
|
addChair,
|
|
95
93
|
runAdd,
|
|
96
94
|
}
|
|
97
|
-
/*
|
|
95
|
+
/* commandSchema Not a pure module */
|
|
@@ -15,6 +15,17 @@
|
|
|
15
15
|
open JestGlobals
|
|
16
16
|
open Reventless.Plugin
|
|
17
17
|
|
|
18
|
+
// The commands are derived from `PluginSpec.command` rather than hand-written, so
|
|
19
|
+
// a test names one by looking it up. Raises rather than defaulting: a missing
|
|
20
|
+
// variant is the failure these tests exist to catch, and a placeholder would let
|
|
21
|
+
// the assertions below pass against nothing.
|
|
22
|
+
let commandNamed = (name: string): commandDef =>
|
|
23
|
+
Platform_Admin_Structure.pluginCommands
|
|
24
|
+
->Array.find(c => c.name == name)
|
|
25
|
+
->Option.getOrThrow(~message=`Platform_Admin_Structure publishes no "${name}" command`)
|
|
26
|
+
|
|
27
|
+
let activate = () => commandNamed("Activate")
|
|
28
|
+
|
|
18
29
|
describe("the Plugins read model declares its lifecycle", () => {
|
|
19
30
|
let rm: queryableDef = Platform_Admin_Structure.pluginReadModel
|
|
20
31
|
|
|
@@ -47,3 +58,246 @@ describe("the Plugins read model declares its lifecycle", () => {
|
|
|
47
58
|
)->toEqual(false)
|
|
48
59
|
)
|
|
49
60
|
})
|
|
61
|
+
|
|
62
|
+
// `Plugin_Structure.make` never sees the platform's own components, so the checks it
|
|
63
|
+
// runs over every ordinary plugin do not run here. A test rather than a startup call:
|
|
64
|
+
// both sides are compile-time constants in this package, so nothing varies at runtime.
|
|
65
|
+
describe("the admin structure's declared transitions are checked", () => {
|
|
66
|
+
let states = Plugin_Structure.lifecycleStatesFromStateSchema(
|
|
67
|
+
~entityName=PluginsReadModelSpec.name,
|
|
68
|
+
PluginsReadModelSpec.stateSchema->S.castToUnknown,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
let statesByView = () => {
|
|
72
|
+
let d = Dict.make()
|
|
73
|
+
states->Option.forEach(s => d->Dict.set(PluginsReadModelSpec.name, s))
|
|
74
|
+
d
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let raises = writables =>
|
|
78
|
+
switch Plugin_Structure.checkDeclaredTransitions(
|
|
79
|
+
~pluginName="Platform",
|
|
80
|
+
~writables,
|
|
81
|
+
~lifecycleStatesByView=statesByView(),
|
|
82
|
+
) {
|
|
83
|
+
| () => false
|
|
84
|
+
| exception _ => true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The precondition the rest of the block is worthless without: with no states the
|
|
88
|
+
// check counts the commands as unvalidated and passes.
|
|
89
|
+
testSync("the linked view yields its lifecycle states", () =>
|
|
90
|
+
expect(states)->toEqual(Some(["Connected", "Disconnected", "Inactive", "Retired"]))
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
testSync("every state the Plugin commands name is one the view declares", () =>
|
|
94
|
+
expect(raises([Platform_Admin_Structure.pluginAggregate]))->toEqual(false)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
// A check that cannot fail is indistinguishable from one that is not running.
|
|
98
|
+
testSync("and a state the view does not declare is caught", () => {
|
|
99
|
+
let bogus: Reventless.Plugin.writableDef = {
|
|
100
|
+
...Platform_Admin_Structure.pluginAggregate,
|
|
101
|
+
commands: [
|
|
102
|
+
{
|
|
103
|
+
...activate(),
|
|
104
|
+
targetState: Some("Conected"),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
}
|
|
108
|
+
expect(raises([bogus]))->toEqual(true)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// The drift that made deriving these defs worth doing rather than tidy. The
|
|
113
|
+
// hand-written arg schema described only `id`, while the SDL generated off the
|
|
114
|
+
// same `commandSchema` declares `Platform_Plugin_Activate(_0: String!, id: ID!)`.
|
|
115
|
+
// AutoUI builds its mutation document from the published schema, so it sent a
|
|
116
|
+
// document missing a required argument and every admin row action was rejected at
|
|
117
|
+
// GraphQL validation, before the aggregate saw it.
|
|
118
|
+
describe("the Plugin command defs carry every argument the SDL requires", () => {
|
|
119
|
+
testSync("the version argument is published", () =>
|
|
120
|
+
expect(activate().schema->String.includes(`"_0"`))->toEqual(true)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
testSync("and so is the aggregate id", () => {
|
|
124
|
+
// `id` is not in the command schema — it is the aggregate instance, injected
|
|
125
|
+
// by the auto-resolver flow. What the def has to get right is naming it, so
|
|
126
|
+
// the consumer declares the variable rather than guessing.
|
|
127
|
+
expect(activate().aggregateIdField)->toEqual(None)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
testSync("the exposed commands name the field PluginBaseFragment generates", () =>
|
|
131
|
+
expect(
|
|
132
|
+
Platform_Admin_Structure.pluginCommands
|
|
133
|
+
->Array.filter(c => c.apiExposed != Some(false))
|
|
134
|
+
->Array.map(c => c.mutationField),
|
|
135
|
+
)->toEqual([
|
|
136
|
+
"Platform_Plugin_Activate",
|
|
137
|
+
"Platform_Plugin_Deactivate",
|
|
138
|
+
"Platform_Plugin_Retire",
|
|
139
|
+
])
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
// The `@noApi` protocol variants come back from the same walk. They belong on
|
|
143
|
+
// the event graph — `Disconnect` is the only edge into `Disconnected` — but
|
|
144
|
+
// `Connect` carries a whole plugin definition, so publishing metadata about
|
|
145
|
+
// them must not publish a way to call them.
|
|
146
|
+
testSync("the internal commands are published as uncallable", () => {
|
|
147
|
+
let internal =
|
|
148
|
+
Platform_Admin_Structure.pluginCommands->Array.filter(c => c.apiExposed == Some(false))
|
|
149
|
+
expect(internal->Array.map(c => c.name))->toEqual([
|
|
150
|
+
"Connect",
|
|
151
|
+
"ReportIncompatibility",
|
|
152
|
+
"Heartbeat",
|
|
153
|
+
"Redetect",
|
|
154
|
+
"Disconnect",
|
|
155
|
+
])
|
|
156
|
+
expect(internal->Array.every(c => c.mutationField == ""))->toEqual(true)
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// A heartbeat timing out is the only way a row reaches `Disconnected`, and the
|
|
161
|
+
// command that does it carries `@noApi`. Until it declared its edge the topology
|
|
162
|
+
// check reported the state unreachable and was right to: nothing in the
|
|
163
|
+
// declarations said how a row got there.
|
|
164
|
+
describe("the internal commands declare the edges they move rows along", () => {
|
|
165
|
+
let states = Plugin_Structure.lifecycleStatesFromStateSchema(
|
|
166
|
+
~entityName=PluginsReadModelSpec.name,
|
|
167
|
+
PluginsReadModelSpec.stateSchema->S.castToUnknown,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
let statesByView = () => {
|
|
171
|
+
let d = Dict.make()
|
|
172
|
+
states->Option.forEach(s => d->Dict.set(PluginsReadModelSpec.name, s))
|
|
173
|
+
d
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
testSync("the only edge into Disconnected is declared", () =>
|
|
177
|
+
expect((commandNamed("Disconnect").allowedStates, commandNamed("Disconnect").targetState))
|
|
178
|
+
->toEqual((Some(["Connected"]), Some("Disconnected")))
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
// The creating form's claim: a target and no from-set. `Connect` brings the
|
|
182
|
+
// version's row into being, so there is no state it runs from — and an empty
|
|
183
|
+
// `allowedStates` would say the opposite, that it is legal in none.
|
|
184
|
+
testSync("the handshake declares a target and no from-set", () =>
|
|
185
|
+
expect((commandNamed("Connect").allowedStates, commandNamed("Connect").targetState))
|
|
186
|
+
->toEqual((None, Some("Connected")))
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
testSync("and no state is left unreachable", () =>
|
|
190
|
+
expect(
|
|
191
|
+
Plugin_Structure.lifecycleTopologyFindings(
|
|
192
|
+
~writables=[Platform_Admin_Structure.pluginAggregate],
|
|
193
|
+
~lifecycleStatesByView=statesByView(),
|
|
194
|
+
),
|
|
195
|
+
)->toEqual([])
|
|
196
|
+
)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// One rule, read on both sides of the wire. `PluginBaseFragment` hand-wrote an
|
|
200
|
+
// `Admin` Cognito gate while the spec defaulted to `AllowAuthenticated`, and the
|
|
201
|
+
// published `requiredAccess` said `None` — so a shell was told the Plugins view
|
|
202
|
+
// was open to everyone and the server refused anyone outside `Admin`. The
|
|
203
|
+
// mismatch is invisible until a non-admin clicks the page.
|
|
204
|
+
describe("the Plugins view publishes the rule the server enforces", () => {
|
|
205
|
+
testSync("the spec declares the group", () =>
|
|
206
|
+
expect(PluginsReadModelSpec.authorization)->toEqual(
|
|
207
|
+
Reventless.Authorization.AllowGroups(["Admin"]),
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
testSync("and the published access keys are derived from it", () =>
|
|
212
|
+
expect(Platform_Admin_Structure.pluginReadModel.requiredAccess)->toEqual(Some(["Admin"]))
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
// The API entry reads the same binding rather than restating it, so the two
|
|
216
|
+
// cannot be edited apart.
|
|
217
|
+
testSync("the query entry carries the spec's rule and no hand-written pair", () => {
|
|
218
|
+
let entry = PluginBaseFragment.queryEntries->Array.getUnsafe(0)
|
|
219
|
+
expect((entry.permission, entry.authorization))->toEqual((
|
|
220
|
+
Some(PluginsReadModelSpec.authorization),
|
|
221
|
+
None,
|
|
222
|
+
))
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// The `queryableDef` is derived from the spec, not hand-written. The analysis
|
|
227
|
+
// that preceded this expected the admin's naming to be bespoke and it is not:
|
|
228
|
+
// `Api_Naming.queryFieldNamesForReadModel` returns byte-identical names, because
|
|
229
|
+
// `singularize` already handles the plural-spec-name / singular-type shape that
|
|
230
|
+
// looked special. Asserted rather than assumed — the whole case for deriving
|
|
231
|
+
// rests on the generic helpers reaching the same answers.
|
|
232
|
+
describe("the Plugins queryableDef is what the generic helpers produce", () => {
|
|
233
|
+
let rm: queryableDef = Platform_Admin_Structure.pluginReadModel
|
|
234
|
+
|
|
235
|
+
testSync("the field names are the generic ones", () =>
|
|
236
|
+
expect((rm.queryField, rm.singleQueryField))->toEqual((
|
|
237
|
+
"Platform_Plugins",
|
|
238
|
+
Some("Platform_Plugin"),
|
|
239
|
+
))
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
testSync("the label ladder reaches the same field, by convention", () =>
|
|
243
|
+
expect((rm.labelField, rm.labelFieldSource, rm.searchableFields))->toEqual((
|
|
244
|
+
"name",
|
|
245
|
+
Some("convention"),
|
|
246
|
+
["name"],
|
|
247
|
+
))
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
// The row id is `name@version` and the state carries the two halves
|
|
251
|
+
// separately, never the composed key — so `None` is the honest answer, and it
|
|
252
|
+
// is the one the resolver ladder reaches on its own.
|
|
253
|
+
testSync("the key ladder finds no id field, as the hand-written def claimed", () =>
|
|
254
|
+
expect((rm.idField, rm.idFieldSource))->toEqual((None, None))
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
// `apiSchemaFragment` and `structure` are on the API — the shell fetches them
|
|
258
|
+
// through dedicated resolver paths — but each is a multi-kilobyte JSON blob
|
|
259
|
+
// that a generated list view must not name. They used to be cut out of the
|
|
260
|
+
// published schema by a hand-written list; they now carry `@hidden`, so they
|
|
261
|
+
// are described and the consumer decides.
|
|
262
|
+
//
|
|
263
|
+
// This is the difference between the two markers, asserted side by side: a
|
|
264
|
+
// `@hidden` field is present and flagged, an `@internal` one is not there at
|
|
265
|
+
// all. Cutting a hidden field out here would leave a consumer unable to tell
|
|
266
|
+
// "not for you" from "does not exist".
|
|
267
|
+
testSync("the blob fields are published, and marked hidden", () => {
|
|
268
|
+
// Read as property NAMES, not as substrings of the JSON: the schema carries
|
|
269
|
+
// nested object types whose own fields would match a naive `includes`.
|
|
270
|
+
let props =
|
|
271
|
+
rm.schema
|
|
272
|
+
->JSON.parseExn
|
|
273
|
+
->JSON.Decode.object
|
|
274
|
+
->Option.flatMap(o => o->Dict.get("properties"))
|
|
275
|
+
->Option.flatMap(JSON.Decode.object)
|
|
276
|
+
->Option.getOr(Dict.make())
|
|
277
|
+
let isHidden = key =>
|
|
278
|
+
props
|
|
279
|
+
->Dict.get(key)
|
|
280
|
+
->Option.flatMap(JSON.Decode.object)
|
|
281
|
+
->Option.flatMap(o => o->Dict.get("x-reventless-hidden"))
|
|
282
|
+
->Option.flatMap(JSON.Decode.bool)
|
|
283
|
+
expect((isHidden("apiSchemaFragment"), isHidden("structure")))->toEqual((
|
|
284
|
+
Some(true),
|
|
285
|
+
Some(true),
|
|
286
|
+
))
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
testSync("while the internal fields are not published at all", () => {
|
|
290
|
+
let props =
|
|
291
|
+
rm.schema
|
|
292
|
+
->JSON.parseExn
|
|
293
|
+
->JSON.Decode.object
|
|
294
|
+
->Option.flatMap(o => o->Dict.get("properties"))
|
|
295
|
+
->Option.flatMap(JSON.Decode.object)
|
|
296
|
+
->Option.mapOr([], Dict.keysToArray)
|
|
297
|
+
expect((
|
|
298
|
+
props->Array.includes("eventCollector"),
|
|
299
|
+
props->Array.includes("extensionNames"),
|
|
300
|
+
props->Array.includes("statusChange"),
|
|
301
|
+
))->toEqual((false, false, true))
|
|
302
|
+
})
|
|
303
|
+
})
|