@reventlessdev/reventless-core 3.0.0-alpha.239 → 3.0.0-alpha.240

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 (170) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/package.json +9 -9
  3. package/src/Message.res +11 -11
  4. package/src/Message.res.mjs +15 -14
  5. package/src/Projection.res +2 -2
  6. package/src/Projection.res.mjs +5 -4
  7. package/src/adapter/Monitoring/Monitoring.res +24 -2
  8. package/src/adapter/Monitoring/Monitoring.res.mjs +3 -3
  9. package/src/admin/Platform_AdminApi.res.mjs +3 -3
  10. package/src/admin/Platform_Admin_Structure.res +33 -113
  11. package/src/admin/Platform_Admin_Structure.res.mjs +8 -139
  12. package/src/admin/UiFragmentRegistry/StateChangeSlice/UiFragmentRegistry.res.mjs +12 -12
  13. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments.res.mjs +14 -14
  14. package/src/components/Aggregate/Aggregate_Callback.res +2 -2
  15. package/src/components/Aggregate/Aggregate_Callback.res.mjs +4 -4
  16. package/src/components/Api/ApiAllowedStatesHelpers.res.mjs +3 -3
  17. package/src/components/Api/ApiNoApiHelpers.res.mjs +5 -5
  18. package/src/components/Api/ApiTargetStateHelpers.res.mjs +3 -3
  19. package/src/components/Api/GraphQL_FragmentGenerator.res +18 -12
  20. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +7 -13
  21. package/src/components/Api/GraphQL_SchemaInspector.res +1 -1
  22. package/src/components/Api/GraphQL_SchemaInspector.res.mjs +1 -1
  23. package/src/components/Api/MCP_SchemaGenerator.res +1 -1
  24. package/src/components/Api/MCP_SchemaGenerator.res.mjs +1 -1
  25. package/src/components/Api/SchemaType.res +6 -10
  26. package/src/components/Api/SchemaType.res.mjs +4 -4
  27. package/src/components/Api/StorageRefFields.res +1 -1
  28. package/src/components/Api/StorageRefFields.res.mjs +1 -1
  29. package/src/components/Api/SuryToJsonSchema.res +11 -0
  30. package/src/components/Api/SuryToJsonSchema.res.mjs +2 -1
  31. package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -4
  32. package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +22 -23
  33. package/src/components/CommandTopic/CommandTopic_Callback.res +1 -2
  34. package/src/components/CommandTopic/CommandTopic_Callback.res.mjs +3 -4
  35. package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -1
  36. package/src/components/CommandTopic/CommandTopic_Helpers.res.mjs +3 -3
  37. package/src/components/Counter/Counter.res.mjs +2 -2
  38. package/src/components/Counter/Counter_Callback.res.mjs +4 -4
  39. package/src/components/Counter/Counter_Operations.res.mjs +4 -4
  40. package/src/components/Dcb/Dcb_Builder.res +1 -1
  41. package/src/components/Dcb/Dcb_Builder.res.mjs +8 -6
  42. package/src/components/DcbEventLog/DcbEventLog_Builder.res.mjs +4 -3
  43. package/src/components/EventCollector/EventCollector_Builder.res +1 -1
  44. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +1 -2
  45. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +11 -12
  46. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res +3 -4
  47. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res.mjs +24 -25
  48. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +1 -1
  49. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res.mjs +3 -3
  50. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +2 -2
  51. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +4 -4
  52. package/src/components/StateViewSlice/StateViewSlice_Builder.res +1 -1
  53. package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +3 -3
  54. package/src/plugin/api/PluginBaseFragment.res +12 -29
  55. package/src/plugin/api/PluginBaseFragment.res.mjs +3 -22
  56. package/src/plugin/component/Capability_Inference.res +118 -1
  57. package/src/plugin/component/Capability_Inference.res.mjs +128 -1
  58. package/src/plugin/component/Plugin_Builder.res +2 -2
  59. package/src/plugin/component/Plugin_Builder.res.mjs +2 -1
  60. package/src/plugin/component/Plugin_Helpers.res +11 -11
  61. package/src/plugin/component/Plugin_Helpers.res.mjs +11 -10
  62. package/src/plugin/component/Plugin_Structure.res +349 -274
  63. package/src/plugin/component/Plugin_Structure.res.mjs +212 -175
  64. package/src/plugin/component/Plugin_SubscriptionSchema.res +1 -1
  65. package/src/plugin/component/Plugin_SubscriptionSchema.res.mjs +1 -1
  66. package/src/plugin/component/SchemaWalker.res +11 -11
  67. package/src/plugin/component/SchemaWalker.res.mjs +8 -9
  68. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +15 -15
  69. package/src/plugin/lifecycle/PluginBehavior.res.mjs +55 -55
  70. package/src/plugin/lifecycle/PluginSpec.res +22 -7
  71. package/src/plugin/lifecycle/PluginSpec.res.mjs +91 -36
  72. package/src/plugin/lifecycle/PluginsReadModelSpec.res +36 -9
  73. package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +33 -22
  74. package/src/util/Interstack.res +3 -3
  75. package/src/util/LogFormat.res +1 -2
  76. package/src/util/LogFormat.res.mjs +2 -2
  77. package/tests/IdentityTest.res +6 -7
  78. package/tests/IdentityTest.res.mjs +7 -9
  79. package/tests/adapter/MonitoringTest.res +30 -3
  80. package/tests/adapter/MonitoringTest.res.mjs +31 -12
  81. package/tests/adapter/RuntimeExtensionHookReachTest.res +0 -1
  82. package/tests/adapter/RuntimeExtensionHookReachTest.res.mjs +5 -7
  83. package/tests/admin/Platform_Admin_StructureTest.res +254 -0
  84. package/tests/admin/Platform_Admin_StructureTest.res.mjs +219 -0
  85. package/tests/aggregate/AggregateCacheTest.res +0 -1
  86. package/tests/aggregate/AggregateCacheTest.res.mjs +13 -15
  87. package/tests/aggregate/AggregateConflictTest.res +0 -1
  88. package/tests/aggregate/AggregateConflictTest.res.mjs +7 -9
  89. package/tests/aggregate/AggregateFixtures.res +0 -1
  90. package/tests/aggregate/AggregateFixtures.res.mjs +19 -21
  91. package/tests/aggregate/AggregateSnapshotTest.res +1 -2
  92. package/tests/aggregate/AggregateSnapshotTest.res.mjs +15 -16
  93. package/tests/api/ApiNoApiTest.res.mjs +17 -9
  94. package/tests/api/GraphQL_FragmentGeneratorTest.res +62 -1
  95. package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +102 -58
  96. package/tests/api/PsInternalFieldsReadModel.res +21 -0
  97. package/tests/api/PsInternalFieldsReadModel.res.mjs +74 -0
  98. package/tests/api/StorageRefFieldsTest.res.mjs +13 -13
  99. package/tests/api/SuryToJsonSchemaTest.res +43 -0
  100. package/tests/api/SuryToJsonSchemaTest.res.mjs +352 -849
  101. package/tests/commandgenerator/CommandGeneratorFixtures.res +0 -1
  102. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +19 -21
  103. package/tests/commandgenerator/OwnerStampingTest.res +0 -1
  104. package/tests/commandgenerator/OwnerStampingTest.res.mjs +9 -11
  105. package/tests/commandtopic/CommandTopicCallbackFixtures.res +0 -1
  106. package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +7 -9
  107. package/tests/counter/CounterFixtures.res +0 -1
  108. package/tests/counter/CounterFixtures.res.mjs +1 -4
  109. package/tests/dcb/DcbDecodeTest.res.mjs +24 -24
  110. package/tests/dcb/DcbEventLogOperationsTest.res +2 -2
  111. package/tests/dcb/DcbEventLogOperationsTest.res.mjs +3 -3
  112. package/tests/dcb/DcbEventLogSchemaTest.res.mjs +7 -7
  113. package/tests/dcb/DcbFixtures.res +0 -1
  114. package/tests/dcb/DcbFixtures.res.mjs +87 -89
  115. package/tests/dcb/DcbTagTest.res.mjs +6 -6
  116. package/tests/dcb/DcbValidationTest.res.mjs +52 -52
  117. package/tests/eventlog/EventLogFixtures.res +0 -1
  118. package/tests/eventlog/EventLogFixtures.res.mjs +7 -9
  119. package/tests/eventmapper/EventMapperFixtures.res +0 -1
  120. package/tests/eventmapper/EventMapperFixtures.res.mjs +24 -26
  121. package/tests/eventtopic/EventTopicFixtures.res +0 -1
  122. package/tests/eventtopic/EventTopicFixtures.res.mjs +7 -9
  123. package/tests/extensionpoint/ExtensionPointFixtures.res +0 -1
  124. package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +10 -12
  125. package/tests/extensionpoint/ExtensionPointOperationsTest.res +1 -2
  126. package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +9 -10
  127. package/tests/logger/LogFormatTest.res +3 -2
  128. package/tests/logger/LogFormatTest.res.mjs +7 -9
  129. package/tests/message/MessageTest.res +2 -2
  130. package/tests/message/MessageTest.res.mjs +8 -7
  131. package/tests/message/MetaEnvelopeTest.res +5 -5
  132. package/tests/message/MetaEnvelopeTest.res.mjs +12 -11
  133. package/tests/plugin/ManifestVisibilityTest.res.mjs +7 -7
  134. package/tests/plugin/PluginDefinitionScalars.res +5 -3
  135. package/tests/plugin/PluginDefinitionScalars.res.mjs +2 -2
  136. package/tests/plugin/PluginLifecycleCorpusTest.res +4 -3
  137. package/tests/plugin/PluginLifecycleCorpusTest.res.mjs +3 -6
  138. package/tests/plugin/PluginSpecExperiment.res +0 -1
  139. package/tests/plugin/PluginSpecExperiment.res.mjs +12 -14
  140. package/tests/plugin/PluginStructureTest.res +188 -1
  141. package/tests/plugin/PluginStructureTest.res.mjs +293 -102
  142. package/tests/plugin/Plugin_EventQuerySchemaTest.res.mjs +3 -3
  143. package/tests/plugin/SchemaWalkerTest.res +0 -1
  144. package/tests/plugin/SchemaWalkerTest.res.mjs +17 -19
  145. package/tests/plugin/StateChangeSlice/PsAttachInvoice.res.mjs +5 -5
  146. package/tests/plugin/StateChangeSlice/PsChangePhoto.res.mjs +8 -8
  147. package/tests/plugin/StateChangeSlice/PsDispatchShipment.res.mjs +5 -5
  148. package/tests/plugin/StateChangeSlice/PsGatedCommands.res.mjs +9 -9
  149. package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +5 -5
  150. package/tests/plugin/StateChangeSlice/PsReserveStock.res.mjs +8 -8
  151. package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +15 -15
  152. package/tests/plugin/StateChangeSlice/PsTypoStore.res +31 -0
  153. package/tests/plugin/StateChangeSlice/PsTypoStore.res.mjs +62 -0
  154. package/tests/plugin/StateChangeSlice/PsUploadAvatar.res.mjs +8 -8
  155. package/tests/plugin/StateChangeSlice/PsUploadImages.res +40 -0
  156. package/tests/plugin/StateChangeSlice/PsUploadImages.res.mjs +66 -0
  157. package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +13 -12
  158. package/tests/plugin/StateViewSlice/PsAvailableProductsView.res.mjs +8 -8
  159. package/tests/plugin/StateViewSlice/PsCategoriesView.res.mjs +6 -6
  160. package/tests/plugin/StateViewSlice/PsCustomersView.res.mjs +10 -10
  161. package/tests/plugin/StateViewSlice/PsGatedView.res.mjs +6 -6
  162. package/tests/plugin/StateViewSlice/PsOrdersView.res.mjs +9 -9
  163. package/tests/plugin/StateViewSlice/PsShipmentsView.res.mjs +11 -10
  164. package/tests/plugin/pluginDefinitionRequiredScalars.txt +15 -0
  165. package/tests/querydb/QueryDbFixtures.res +0 -1
  166. package/tests/querydb/QueryDbFixtures.res.mjs +5 -7
  167. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +0 -1
  168. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +4 -6
  169. package/tests/util/CommandPublisherTest.res +0 -1
  170. package/tests/util/CommandPublisherTest.res.mjs +4 -6
@@ -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 =
@@ -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=` + Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(e), Stdlib_JsExn.message), "unknown");
207
+ return `err=` + Util_Sury$Reventless.exnMessage(e, undefined);
208
208
  }
209
209
 
210
210
  function commandJsonsToLogMessages(cmdJsons) {
@@ -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->S.reverseConvertToJsonOrThrow(Reventless.Identity.schema)
75
- let decoded = json->S.parseJsonOrThrow(Reventless.Identity.schema)
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->S.reverseConvertToJsonOrThrow(Reventless.Identity.schema)
87
- let decoded = json->S.parseJsonOrThrow(Reventless.Identity.schema)
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->S.reverseConvertToJsonOrThrow(Reventless.Identity.schema)
99
- let decoded = json->S.parseJsonOrThrow(Reventless.Identity.schema)
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 = S.reverseConvertToJsonOrThrow(identity, Identity$Reventless.schema);
90
- let decoded = S.parseJsonOrThrow(json, Identity$Reventless.schema);
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 = S.reverseConvertToJsonOrThrow(identity, Identity$Reventless.schema);
106
- let decoded = S.parseJsonOrThrow(json, Identity$Reventless.schema);
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 = S.reverseConvertToJsonOrThrow(identity, Identity$Reventless.schema);
118
- let decoded = S.parseJsonOrThrow(json, Identity$Reventless.schema);
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,5 @@
1
1
  open JestGlobals
2
2
 
3
- S.enableJson()
4
3
 
5
4
  // The point of the seam, end to end at the hook level: an extension that only
6
5
  // ever gets `onColdStart` can reach the framework's runtime callback hooks from
@@ -1,6 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as S from "sury/src/S.res.mjs";
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
- S.enableJson();
12
-
13
- let commandSchema = S.schema(s => ({
11
+ let commandSchema = Sury.$schema(s => ({
14
12
  TAG: "Add",
15
- productId: s.m(S.string),
16
- name: s.m(S.string)
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
- /* Not a pure module */
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
+ })