@reventlessdev/reventless-core 3.0.0-alpha.179 → 3.0.0-alpha.181

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 (118) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/package.json +8 -8
  3. package/src/Message.res +2 -1
  4. package/src/Message.res.mjs +5 -1
  5. package/src/adapter/Monitoring/Monitoring.res +14 -2
  6. package/src/adapter/Monitoring/Monitoring.res.mjs +4 -2
  7. package/src/admin/Platform_Admin_Structure.res +2 -0
  8. package/src/admin/Platform_Admin_Structure.res.mjs +2 -0
  9. package/src/admin/Platform_ComponentDefinitionsApi.res +2 -1
  10. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +5 -1
  11. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res +1 -1
  12. package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res.mjs +2 -1
  13. package/src/components/Api/ApiTargetStateHelpers.res +20 -0
  14. package/src/components/Api/ApiTargetStateHelpers.res.mjs +16 -0
  15. package/src/components/Api/GraphQL_FragmentGenerator.res +1 -0
  16. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +1 -0
  17. package/src/components/Api/SchemaType.res +3 -0
  18. package/src/components/Api/SchemaType.res.mjs +10 -2
  19. package/src/components/Api/SuryToJsonSchema.res +16 -0
  20. package/src/components/Api/SuryToJsonSchema.res.mjs +28 -0
  21. package/src/components/Dcb/Dcb_Builder.res +4 -5
  22. package/src/components/Dcb/Dcb_Builder.res.mjs +3 -5
  23. package/src/components/DcbEventLog/DcbEventLog_Operations.res +6 -1
  24. package/src/components/DcbEventLog/DcbEventLog_Operations.res.mjs +2 -1
  25. package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +25 -0
  26. package/src/components/InboundTranslationSlice/InboundTranslationSlice.res.mjs +31 -0
  27. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +8 -8
  28. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +26 -6
  29. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +44 -37
  30. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res.mjs +17 -25
  31. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +27 -2
  32. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +4 -0
  33. package/src/components/StateViewSlice/StateViewSlice_Builder.res +20 -6
  34. package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +23 -5
  35. package/src/components/StateViewSlice/StateViewSlice_Callback.res +1 -1
  36. package/src/components/StateViewSlice/StateViewSlice_Callback.res.mjs +5 -1
  37. package/src/plugin/component/Plugin_Helpers.res +4 -1
  38. package/src/plugin/component/Plugin_Structure.res +15 -1
  39. package/src/plugin/component/Plugin_Structure.res.mjs +4 -1
  40. package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +2 -0
  41. package/tests/IdentityTest.res.mjs +174 -0
  42. package/tests/ResourceAttributionTest.res.mjs +75 -0
  43. package/tests/adapter/MonitoringTest.res +32 -2
  44. package/tests/adapter/MonitoringTest.res.mjs +116 -0
  45. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +10 -0
  46. package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +365 -0
  47. package/tests/admin/Platform_UIFragmentsApiTest.res.mjs +101 -0
  48. package/tests/aggregate/AggregateCacheTest.res.mjs +433 -0
  49. package/tests/aggregate/AggregateCallbackTest.res.mjs +143 -0
  50. package/tests/aggregate/AggregateCausationTest.res.mjs +47 -0
  51. package/tests/aggregate/AggregateConflictTest.res.mjs +386 -0
  52. package/tests/aggregate/AggregateFixtures.res.mjs +308 -0
  53. package/tests/aggregate/AggregateRejectionTest.res.mjs +166 -0
  54. package/tests/aggregate/AggregateSnapshotTest.res.mjs +645 -0
  55. package/tests/api/ApiNoApiTest.res.mjs +90 -0
  56. package/tests/api/GraphQL_StitcherTest.res.mjs +112 -0
  57. package/tests/api/SimpleTest.res.mjs +10 -0
  58. package/tests/api/SuryToJsonSchemaTest.res +68 -0
  59. package/tests/api/SuryToJsonSchemaTest.res.mjs +843 -0
  60. package/tests/commandgenerator/CommandGeneratorCallbackTest.res.mjs +160 -0
  61. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +197 -0
  62. package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +152 -0
  63. package/tests/commandtopic/CommandTopicCallbackTest.res.mjs +106 -0
  64. package/tests/commandtopic/CommandTopicHelpersRejectionTest.res.mjs +109 -0
  65. package/tests/counter/CounterCallbackTest.res.mjs +140 -0
  66. package/tests/counter/CounterFixtures.res.mjs +67 -0
  67. package/tests/dcb/DcbDecodeTest.res.mjs +194 -0
  68. package/tests/dcb/DcbEventLogOperationsTest.res.mjs +268 -0
  69. package/tests/dcb/DcbFixtures.res.mjs +534 -0
  70. package/tests/dcb/DcbScopeInferenceTest.res.mjs +480 -0
  71. package/tests/dcb/DcbStateChangeSliceTest.res.mjs +540 -0
  72. package/tests/dcb/DcbTagTest.res.mjs +867 -0
  73. package/tests/dcb/DcbValidationTest.res.mjs +476 -0
  74. package/tests/eventlog/EventLogFixtures.res.mjs +196 -0
  75. package/tests/eventlog/EventLogOperationsTest.res.mjs +192 -0
  76. package/tests/eventlog/EventLogRetryTest.res.mjs +195 -0
  77. package/tests/eventmapper/EventMapperCallbackTest.res.mjs +141 -0
  78. package/tests/eventmapper/EventMapperFixtures.res.mjs +440 -0
  79. package/tests/eventmapper/EventMapperTest.res.mjs +83 -0
  80. package/tests/eventtopic/EventTopicFixtures.res.mjs +65 -0
  81. package/tests/eventtopic/EventTopicOperationsTest.res.mjs +71 -0
  82. package/tests/extensionpoint/ExtensionPointCallbackTest.res.mjs +94 -0
  83. package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +205 -0
  84. package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +304 -0
  85. package/tests/extensionpoint/PluginDisconnectScheduleTest.res.mjs +92 -0
  86. package/tests/ftp/FTPTest.res.mjs +88 -0
  87. package/tests/logger/LogFormatTest.res.mjs +329 -0
  88. package/tests/logger/LoggerLazyTest.res.mjs +40 -0
  89. package/tests/message/MessageTest.res.mjs +218 -0
  90. package/tests/message/MetaEnvelopeTest.res.mjs +188 -0
  91. package/tests/plugin/HeartbeatDisconnectGraceTest.res.mjs +91 -0
  92. package/tests/plugin/ManifestVisibilityTest.res.mjs +215 -0
  93. package/tests/plugin/PluginSpecExperiment.res.mjs +31 -0
  94. package/tests/plugin/PluginStructureTest.res +55 -0
  95. package/tests/plugin/PluginStructureTest.res.mjs +467 -0
  96. package/tests/plugin/PluginVersionTest.res.mjs +30 -0
  97. package/tests/plugin/SchemaWalkerTest.res.mjs +160 -0
  98. package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +62 -0
  99. package/tests/plugin/StateChangeSlice/PsShipOrder.res +4 -1
  100. package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +77 -0
  101. package/tests/plugin/StateViewSlice/PsAnnotatedView.res +5 -4
  102. package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +110 -0
  103. package/tests/plugin/StateViewSlice/PsAvailableProductsView.res +1 -1
  104. package/tests/plugin/StateViewSlice/PsAvailableProductsView.res.mjs +70 -0
  105. package/tests/plugin/StateViewSlice/PsCustomersView.res +1 -1
  106. package/tests/plugin/StateViewSlice/PsCustomersView.res.mjs +70 -0
  107. package/tests/plugin/StateViewSlice/PsOrdersView.res +1 -1
  108. package/tests/plugin/StateViewSlice/PsOrdersView.res.mjs +82 -0
  109. package/tests/poc/StructuralSubtypingPoc.res.mjs +61 -0
  110. package/tests/projection/ProjectionOptimizeTest.res.mjs +75 -0
  111. package/tests/querydb/QueryDbFixtures.res.mjs +135 -0
  112. package/tests/querydb/QueryDbOperationsTest.res.mjs +168 -0
  113. package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res.mjs +77 -0
  114. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +139 -0
  115. package/tests/util/CommandPublisherTest.res.mjs +78 -0
  116. package/tests/util/CsvStreamTest.res.mjs +51 -0
  117. package/tests/util/LruTest.res.mjs +105 -0
  118. package/tests/util/MetricsTest.res.mjs +53 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,27 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.181 (2026-07-26)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **auto-ui:** don't hand a [@no](https://github.com/no)Api command variant a sibling's mutation field ([2640465](https://github.com/ReventlessDev/reventless-core/commit/264046509c5f5fd7f00f5d04ab977bbc2008098c)), closes [#2](https://github.com/ReventlessDev/reventless-core/issues/2)
11
+ * **core:** decode the event envelope in OutboundTranslationSlice ([66008a4](https://github.com/ReventlessDev/reventless-core/commit/66008a4dff411c85470f60aac5424e7b3eda6f01))
12
+ * **graphql:** give InboundTranslationSlice mutations a resolvable result type ([381b545](https://github.com/ReventlessDev/reventless-core/commit/381b5458c16797404cb8ed95fa853fb1e1ca4199))
13
+ ### Features
14
+
15
+ * **auto-ui:** declare command target state via [@target](https://github.com/target)State ([5fc0374](https://github.com/ReventlessDev/reventless-core/commit/5fc03741a8816c57085b86a4ad7d595e3b690193)), closes [#5](https://github.com/ReventlessDev/reventless-core/issues/5)
16
+ * **auto-ui:** declare field semantics + dashboard metrics ([@semantic](https://github.com/semantic), [@metric](https://github.com/metric)) ([d74ff77](https://github.com/ReventlessDev/reventless-core/commit/d74ff7721e18e8638a82931a370a549b304dac94)), closes [#4](https://github.com/ReventlessDev/reventless-core/issues/4)
17
+ * **dcb:** emit decision-model cache hit/miss + delta metrics ([967fb7f](https://github.com/ReventlessDev/reventless-core/commit/967fb7fb42f00d22b9dd6f6261e2f5308a6e9516))
18
+
19
+
20
+ # 3.0.0-alpha.180 (2026-07-24)
21
+
22
+ ### Features
23
+
24
+ * **core:** Monitoring seam announces owning plugin/platform to backends ([79dd49a](https://github.com/ReventlessDev/reventless-core/commit/79dd49ae29feb95ed88c836cd8827c8e68978889))
25
+
26
+
6
27
  # 3.0.0-alpha.179 (2026-07-23)
7
28
 
8
29
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-core",
3
- "version": "3.0.0-alpha.179",
3
+ "version": "3.0.0-alpha.181",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -28,22 +28,22 @@
28
28
  "dependencies": {
29
29
  "sury": "11.0.0-alpha.4",
30
30
  "uuid": "^13.0.0",
31
- "@reventlessdev/rescript-effect": "0.1.0-alpha.30",
32
- "@reventlessdev/rescript-jest": "1.0.0-alpha.9",
33
31
  "@reventlessdev/rescript-fast-csv": "1.2.0-alpha.14",
34
32
  "@reventlessdev/rescript-hash-object": "1.2.0-alpha.13",
33
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.9",
35
34
  "@reventlessdev/rescript-node-streams": "1.1.0-alpha.13",
36
- "@reventlessdev/rescript-ssh2": "1.1.0-alpha.14",
37
- "@reventlessdev/reventless-interop": "3.0.0-alpha.29",
35
+ "@reventlessdev/rescript-effect": "0.1.0-alpha.31",
38
36
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
37
+ "@reventlessdev/rescript-ssh2": "1.1.0-alpha.14",
39
38
  "@reventlessdev/rescript-uuid": "1.1.0-alpha.17",
40
- "@reventlessdev/reventless-infra": "3.0.0-alpha.104",
41
- "@reventlessdev/reventless-spec": "3.0.0-alpha.80"
39
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.105",
40
+ "@reventlessdev/reventless-interop": "3.0.0-alpha.29",
41
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.81"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "12.3.0",
45
45
  "sury-ppx": "11.0.0-alpha.2",
46
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.54"
46
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.56"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "rescript": "12.3.0"
package/src/Message.res CHANGED
@@ -257,10 +257,11 @@ let decomposeMeta = meta =>
257
257
  ->Option.getOrThrow
258
258
  ->Dict.toArray
259
259
 
260
- let composeEventJson' = (id, meta, eventJson) =>
260
+ let composeEventJson' = (id, meta, ~recordedAt, eventJson) =>
261
261
  [
262
262
  ("id", id->JSON.Encode.string),
263
263
  ("meta", meta->S.reverseConvertToJsonOrThrow(metaSchema)),
264
+ ("recordedAt", recordedAt->JSON.Encode.string),
264
265
  ("event", eventJson),
265
266
  ]
266
267
  ->Dict.fromArray
@@ -203,7 +203,7 @@ function decomposeMeta(meta) {
203
203
  return Object.entries(Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.object(S.reverseConvertToJsonOrThrow(meta, Message$Reventless.metaSchema)), undefined));
204
204
  }
205
205
 
206
- function composeEventJson$p(id, meta, eventJson) {
206
+ function composeEventJson$p(id, meta, recordedAt, eventJson) {
207
207
  return Object.fromEntries([
208
208
  [
209
209
  "id",
@@ -213,6 +213,10 @@ function composeEventJson$p(id, meta, eventJson) {
213
213
  "meta",
214
214
  S.reverseConvertToJsonOrThrow(meta, Message$Reventless.metaSchema)
215
215
  ],
216
+ [
217
+ "recordedAt",
218
+ recordedAt
219
+ ],
216
220
  [
217
221
  "event",
218
222
  eventJson
@@ -58,18 +58,26 @@ FunctionName or a PrometheusRule deployment selector, but unusable as a logical
58
58
  resource name). Because `onProvisioned` fires during resource creation inside the
59
59
  deploy program, a Pulumi-based backend can create monitoring resources in the
60
60
  same stack, parented naturally.
61
+
62
+ `~plugin` / `~platform` are the OWNING plugin and platform of the unit, taken from the ambient
63
+ `ResourceAttribution` context the plugin builder publishes during construction (the same source
64
+ `AWS_Tags` uses). Both are `None` for substrate provisioned outside any plugin construct — the
65
+ correct answer for platform-scope resources. A backend that names or routes its monitoring resources
66
+ per plugin needs this: `~name` is only the component, and two plugins can own like-named components.
61
67
  */
62
68
  module type Backend = {
63
69
  let onProvisioned: (
64
70
  ~kind: unitKind,
65
71
  ~name: string,
66
72
  ~component: ReventlessInfra.Adapter.resource,
73
+ ~plugin: option<string>,
74
+ ~platform: option<string>,
67
75
  ) => unit
68
76
  }
69
77
 
70
78
  /** Default backend: does nothing. Active unless an extension calls `use`. */
71
79
  module Noop: Backend = {
72
- let onProvisioned = (~kind as _, ~name as _, ~component as _) => ()
80
+ let onProvisioned = (~kind as _, ~name as _, ~component as _, ~plugin as _, ~platform as _) => ()
73
81
  }
74
82
 
75
83
  let backend: ref<module(Backend)> = ref(module(Noop: Backend))
@@ -92,5 +100,9 @@ let notify = (
92
100
  ~component: ReventlessInfra.Adapter.resource,
93
101
  ) => {
94
102
  module B = unpack(backend.contents)
95
- B.onProvisioned(~kind, ~name, ~component)
103
+ // The owning plugin/platform come from the ambient context the plugin builder publishes
104
+ // (ResourceAttribution.enter) — populated exactly when this fires, so the call sites need not
105
+ // thread the names. `None` outside a plugin construct (platform substrate).
106
+ let {ResourceAttribution.plugin: plugin, platform} = ResourceAttribution.current.contents
107
+ B.onProvisioned(~kind, ~name, ~component, ~plugin, ~platform)
96
108
  }
@@ -1,7 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as ResourceAttribution$ReventlessCore from "../../ResourceAttribution.res.mjs";
3
4
 
4
- function onProvisioned(param, param$1, param$2) {
5
+ function onProvisioned(param, param$1, param$2, param$3, param$4) {
5
6
 
6
7
  }
7
8
 
@@ -19,7 +20,8 @@ function use(b) {
19
20
 
20
21
  function notify(kind, name, component) {
21
22
  let B = backend.contents;
22
- B.onProvisioned(kind, name, component);
23
+ let match = ResourceAttribution$ReventlessCore.current.contents;
24
+ B.onProvisioned(kind, name, component, match.plugin, match.platform);
23
25
  }
24
26
 
25
27
  export {
@@ -74,6 +74,7 @@ let activateCommand: commandDef = {
74
74
  mutationField: Api_Naming.adminField(~name="Plugin_Activate"),
75
75
  references: [],
76
76
  allowedStates: Some(["Inactive"]),
77
+ targetState: None,
77
78
  apiExposed: Some(true),
78
79
  }
79
80
 
@@ -85,6 +86,7 @@ let deactivateCommand: commandDef = {
85
86
  mutationField: Api_Naming.adminField(~name="Plugin_Deactivate"),
86
87
  references: [],
87
88
  allowedStates: Some(["Connected", "Disconnected"]),
89
+ targetState: None,
88
90
  apiExposed: Some(true),
89
91
  }
90
92
 
@@ -61,6 +61,7 @@ let activateCommand = {
61
61
  mutationField: activateCommand_mutationField,
62
62
  references: activateCommand_references,
63
63
  allowedStates: activateCommand_allowedStates,
64
+ targetState: undefined,
64
65
  apiExposed: activateCommand_apiExposed
65
66
  };
66
67
 
@@ -87,6 +88,7 @@ let deactivateCommand = {
87
88
  mutationField: deactivateCommand_mutationField,
88
89
  references: deactivateCommand_references,
89
90
  allowedStates: deactivateCommand_allowedStates,
91
+ targetState: undefined,
90
92
  apiExposed: deactivateCommand_apiExposed
91
93
  };
92
94
 
@@ -8,7 +8,7 @@ open Reventless.Plugin
8
8
 
9
9
  let sdlTypes: array<string> = [
10
10
  `type Platform_FieldReference {\n fieldName: String!\n entity: String!\n plugin: String\n}`,
11
- `type Platform_CommandDef {\n name: String!\n schema: String!\n level: String!\n aggregateIdField: String\n mutationField: String!\n references: [Platform_FieldReference!]!\n allowedStates: [String!]\n apiExposed: Boolean\n}`,
11
+ `type Platform_CommandDef {\n name: String!\n schema: String!\n level: String!\n aggregateIdField: String\n mutationField: String!\n references: [Platform_FieldReference!]!\n allowedStates: [String!]\n targetState: String\n apiExposed: Boolean\n}`,
12
12
  `type Platform_EventDef {\n name: String!\n schema: String!\n references: [Platform_FieldReference!]!\n}`,
13
13
  `type Platform_WriteSideDef {\n name: String!\n commands: [Platform_CommandDef!]!\n linkedViews: [String!]!\n consistencyRead: String\n producedEventTypes: [String!]!\n consumedEventTypes: [String!]!\n events: [Platform_EventDef!]!\n}`,
14
14
  `type Platform_ReadSideDef {\n name: String!\n queryField: String!\n schema: String!\n consumedEventTypes: [String!]!\n linkedWriteSide: [String!]!\n labelField: String!\n searchableFields: [String!]!\n statusField: String\n}`,
@@ -54,6 +54,7 @@ let encodeCommandDef = (c: commandDef): JSON.t =>
54
54
  "allowedStates",
55
55
  c.allowedStates->Option.mapOr(JSON.Encode.null, encodeStrings),
56
56
  ),
57
+ ("targetState", c.targetState->Option.mapOr(JSON.Encode.null, JSON.Encode.string)),
57
58
  ("apiExposed", c.apiExposed->Option.mapOr(JSON.Encode.null, JSON.Encode.bool)),
58
59
  ])->JSON.Encode.object
59
60
 
@@ -6,7 +6,7 @@ import * as Plugin$ReventlessCore from "../plugin/component/Plugin.res.mjs";
6
6
 
7
7
  let sdlTypes = [
8
8
  `type Platform_FieldReference {\n fieldName: String!\n entity: String!\n plugin: String\n}`,
9
- `type Platform_CommandDef {\n name: String!\n schema: String!\n level: String!\n aggregateIdField: String\n mutationField: String!\n references: [Platform_FieldReference!]!\n allowedStates: [String!]\n apiExposed: Boolean\n}`,
9
+ `type Platform_CommandDef {\n name: String!\n schema: String!\n level: String!\n aggregateIdField: String\n mutationField: String!\n references: [Platform_FieldReference!]!\n allowedStates: [String!]\n targetState: String\n apiExposed: Boolean\n}`,
10
10
  `type Platform_EventDef {\n name: String!\n schema: String!\n references: [Platform_FieldReference!]!\n}`,
11
11
  `type Platform_WriteSideDef {\n name: String!\n commands: [Platform_CommandDef!]!\n linkedViews: [String!]!\n consistencyRead: String\n producedEventTypes: [String!]!\n consumedEventTypes: [String!]!\n events: [Platform_EventDef!]!\n}`,
12
12
  `type Platform_ReadSideDef {\n name: String!\n queryField: String!\n schema: String!\n consumedEventTypes: [String!]!\n linkedWriteSide: [String!]!\n labelField: String!\n searchableFields: [String!]!\n statusField: String\n}`,
@@ -71,6 +71,10 @@ function encodeCommandDef(c) {
71
71
  "allowedStates",
72
72
  Stdlib_Option.mapOr(c.allowedStates, null, encodeStrings)
73
73
  ],
74
+ [
75
+ "targetState",
76
+ Stdlib_Option.mapOr(c.targetState, null, prim => prim)
77
+ ],
74
78
  [
75
79
  "apiExposed",
76
80
  Stdlib_Option.mapOr(c.apiExposed, null, prim => prim)
@@ -3,7 +3,7 @@
3
3
  // No `open Reventless.Plugin`: it would shadow the state labels (panels/pages/...) brought in
4
4
  // by the projection's spec open. `manifest.<field>` resolves type-directed from the event's
5
5
  // uiFragmentManifest payload.
6
- let project = event =>
6
+ let project = ({event}) =>
7
7
  switch event {
8
8
  | UiFragmentRegistered({pluginId, manifest, at}) => [
9
9
  Set(
@@ -1,7 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
 
4
- function project(event) {
4
+ function project(param) {
5
+ let event = param.event;
5
6
  switch (event.TAG) {
6
7
  case "UiFragmentRegistered" :
7
8
  let at = event.at;
@@ -0,0 +1,20 @@
1
+ // Read per-variant `targetState` metadata that the reventless-ppx
2
+ // `@targetState("…")` attribute attaches to a command schema. Consumed by
3
+ // `Plugin_Structure.toCommandDef` when building the `commandDef` records that
4
+ // land in `Platform_ComponentDefinitions`, so AutoUI's board drag resolver can
5
+ // move a row by a *declared* transition instead of a name-stem guess. Mirrors
6
+ // `ApiAllowedStatesHelpers.getAllowedStates`.
7
+
8
+ open ReventlessInfra.Api
9
+
10
+ /** Returns the declared target state for a given command-variant name, or None
11
+ when the variant has no `@targetState` annotation (back-compat default —
12
+ the resolver falls back to its name-stem heuristic). */
13
+ let getTargetState = (
14
+ commandSchema: S.t<unknown>,
15
+ ~variantName: string,
16
+ ): option<string> =>
17
+ switch commandSchema->S.Metadata.get(~id=targetStateId) {
18
+ | None => None
19
+ | Some(entries) => entries->Dict.get(variantName)
20
+ }
@@ -0,0 +1,16 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Api$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/Api.res.mjs";
5
+
6
+ function getTargetState(commandSchema, variantName) {
7
+ let entries = S.Metadata.get(commandSchema, Api$ReventlessInfra.targetStateId);
8
+ if (entries !== undefined) {
9
+ return entries[variantName];
10
+ }
11
+ }
12
+
13
+ export {
14
+ getTargetState,
15
+ }
16
+ /* S Not a pure module */
@@ -43,6 +43,7 @@ let rec fromSchemaType = (
43
43
  | ScalarBoolean => `Boolean${bang}`
44
44
  | ScalarBigInt => `String${bang}`
45
45
  | EntityId => `ID${bang}`
46
+ | DateTime => `String${bang}`
46
47
  | Nullable(inner) => fromSchemaType(~required=false, ~asInput, inner, collectedTypes, seenTypes)
47
48
  | ArrayOf(item) =>
48
49
  let itemType = fromSchemaType(~required=true, ~asInput, item, collectedTypes, seenTypes)
@@ -41,6 +41,7 @@ function fromSchemaType(_required, _asInputOpt, _st, collectedTypes, seenTypes)
41
41
  return `ID` + bang;
42
42
  case "ScalarString" :
43
43
  case "ScalarBigInt" :
44
+ case "DateTime" :
44
45
  case "Unknown" :
45
46
  return `String` + bang;
46
47
  }
@@ -4,6 +4,7 @@ type rec schemaType =
4
4
  | ScalarBoolean
5
5
  | ScalarBigInt
6
6
  | EntityId
7
+ | DateTime
7
8
  | Nullable(schemaType)
8
9
  | ArrayOf(schemaType)
9
10
  | ObjectRef(string, dict<schemaType>)
@@ -11,6 +12,7 @@ type rec schemaType =
11
12
  | Unknown
12
13
 
13
14
  let isTagged = Reventless.DcbTag.isTagged
15
+ let isDateTime = Reventless.DateTime.isDateTime
14
16
 
15
17
  let isIdFieldName = (name: string): bool => {
16
18
  let lower = String.toLowerCase(name)
@@ -30,6 +32,7 @@ let rec fromSury = (~parentName: string, ~fieldName: string, schema: S.t<unknown
30
32
  } else {
31
33
  switch schema {
32
34
  | String({const: ?Some(_)}) => ScalarString
35
+ | String(_) if isDateTime(schema) => DateTime
33
36
  | String(_) => isIdFieldName(fieldName) ? EntityId : ScalarString
34
37
  | Number(_) => ScalarNumber
35
38
  | Boolean(_) => ScalarBoolean
@@ -3,6 +3,7 @@
3
3
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
5
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
6
+ import * as DateTime$Reventless from "@reventlessdev/reventless-spec/src/types/DateTime.res.mjs";
6
7
  import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
7
8
 
8
9
  function isIdFieldName(name) {
@@ -31,10 +32,14 @@ function fromSury(parentName, fieldName, schema) {
31
32
  }
32
33
  switch (schema.type) {
33
34
  case "string" :
34
- if (schema.const !== undefined || !isIdFieldName(fieldName)) {
35
+ if (schema.const !== undefined) {
35
36
  return "ScalarString";
36
- } else {
37
+ } else if (DateTime$Reventless.isDateTime(schema)) {
38
+ return "DateTime";
39
+ } else if (isIdFieldName(fieldName)) {
37
40
  return "EntityId";
41
+ } else {
42
+ return "ScalarString";
38
43
  }
39
44
  case "number" :
40
45
  return "ScalarNumber";
@@ -133,8 +138,11 @@ function fromSuryObject(typeName, schema) {
133
138
 
134
139
  let isTagged = DcbTag$Reventless.isTagged;
135
140
 
141
+ let isDateTime = DateTime$Reventless.isDateTime;
142
+
136
143
  export {
137
144
  isTagged,
145
+ isDateTime,
138
146
  isIdFieldName,
139
147
  isIdsFieldName,
140
148
  fromSury,
@@ -68,6 +68,21 @@ let mergeAnnotations = (
68
68
  if spec.scanSort->Array.includes(fieldName) {
69
69
  obj->Dict.set("x-reventless-scanSort", JSON.Encode.bool(true))
70
70
  }
71
+ switch spec.semantic->Array.find(((field, _)) => field === fieldName) {
72
+ | Some((_, semanticId)) =>
73
+ obj->Dict.set("x-reventless-semantic", JSON.Encode.string(semanticId))
74
+ | None => ()
75
+ }
76
+ switch spec.metric->Array.find(((field, _)) => field === fieldName) {
77
+ | Some((_, m)) =>
78
+ // `x-reventless-metric: {aggregate, label}`. Omit an empty label so the
79
+ // UI derives one from the field name.
80
+ let entries = [("aggregate", JSON.Encode.string(m.aggregate))]
81
+ let entries =
82
+ m.label === "" ? entries : Array.concat(entries, [("label", JSON.Encode.string(m.label))])
83
+ obj->Dict.set("x-reventless-metric", JSON.Encode.object(Dict.fromArray(entries)))
84
+ | None => ()
85
+ }
71
86
  JSON.Encode.object(obj)
72
87
  }
73
88
 
@@ -80,6 +95,7 @@ let rec fromSchemaType = (st: SchemaType.schemaType): JSON.t =>
80
95
  | ScalarBoolean => jsonObject([("type", str("boolean"))])
81
96
  | ScalarBigInt => jsonObject([("type", str("integer"))])
82
97
  | EntityId => jsonObject([("type", str("string")), ("format", str("uuid"))])
98
+ | DateTime => jsonObject([("type", str("string")), ("format", str("date-time"))])
83
99
  | Nullable(inner) =>
84
100
  let innerSchema = fromSchemaType(inner)
85
101
  jsonObject([("oneOf", JSON.Encode.array([innerSchema, jsonObject([("type", str("null"))])]))])
@@ -62,6 +62,23 @@ function mergeAnnotations(fieldSchema, fieldName, spec) {
62
62
  if (spec.scanSort.includes(fieldName)) {
63
63
  obj["x-reventless-scanSort"] = true;
64
64
  }
65
+ let match$3 = spec.semantic.find(param => param[0] === fieldName);
66
+ if (match$3 !== undefined) {
67
+ obj["x-reventless-semantic"] = match$3[1];
68
+ }
69
+ let match$4 = spec.metric.find(param => param[0] === fieldName);
70
+ if (match$4 !== undefined) {
71
+ let m = match$4[1];
72
+ let entries = [[
73
+ "aggregate",
74
+ m.aggregate
75
+ ]];
76
+ let entries$1 = m.label === "" ? entries : entries.concat([[
77
+ "label",
78
+ m.label
79
+ ]]);
80
+ obj["x-reventless-metric"] = Object.fromEntries(entries$1);
81
+ }
65
82
  return obj;
66
83
  }
67
84
 
@@ -94,6 +111,17 @@ function fromSchemaType(st) {
94
111
  "uuid"
95
112
  ]
96
113
  ]);
114
+ case "DateTime" :
115
+ return Object.fromEntries([
116
+ [
117
+ "type",
118
+ "string"
119
+ ],
120
+ [
121
+ "format",
122
+ "date-time"
123
+ ]
124
+ ]);
97
125
  case "ScalarString" :
98
126
  case "Unknown" :
99
127
  return Object.fromEntries([[
@@ -718,11 +718,10 @@ module Make = (
718
718
  | Some(receiveFn) =>
719
719
  Effect.promise(async () => {
720
720
  let result = await receiveFn(args)
721
- let response = switch result {
722
- | Ok(targetIds) =>
723
- targetIds->Array.map(JSON.Encode.string)->JSON.Encode.array
724
- | Error(msg) => msg->JSON.Encode.string
725
- }
721
+ let response =
722
+ result
723
+ ->InboundTranslationSlice.receiveResultToOutcome
724
+ ->CommandTopic.commandOutcomeToJson
726
725
  response->Obj.magic
727
726
  })
728
727
  | None => baseHandler(event, ctx)
@@ -15,6 +15,7 @@ import * as Logger$ReventlessCore from "../../util/Logger.res.mjs";
15
15
  import * as Component$ReventlessCore from "../Component.res.mjs";
16
16
  import * as DcbValidation$Reventless from "@reventlessdev/reventless-spec/src/components/DcbValidation.res.mjs";
17
17
  import * as Api_Naming$ReventlessCore from "../Api/Api_Naming.res.mjs";
18
+ import * as CommandTopic$ReventlessCore from "../CommandTopic/CommandTopic.res.mjs";
18
19
  import * as DcbScopeInference$Reventless from "@reventlessdev/reventless-spec/src/components/DcbScopeInference.res.mjs";
19
20
  import * as RuntimeHints$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/RuntimeHints.res.mjs";
20
21
  import * as Plugin_Helpers$ReventlessCore from "../../plugin/component/Plugin_Helpers.res.mjs";
@@ -22,6 +23,7 @@ import * as ApiNoApiHelpers$ReventlessCore from "../Api/ApiNoApiHelpers.res.mjs"
22
23
  import * as Plugin_Structure$ReventlessCore from "../../plugin/component/Plugin_Structure.res.mjs";
23
24
  import * as DcbEventLog_Builder$ReventlessCore from "../DcbEventLog/DcbEventLog_Builder.res.mjs";
24
25
  import * as CommandTopic_Builder$ReventlessCore from "../CommandTopic/CommandTopic_Builder.res.mjs";
26
+ import * as InboundTranslationSlice$ReventlessCore from "../InboundTranslationSlice/InboundTranslationSlice.res.mjs";
25
27
  import * as AutomationSlice_Callback$ReventlessCore from "../AutomationSlice/AutomationSlice_Callback.res.mjs";
26
28
  import * as CommandGenerator_Callback$ReventlessCore from "../CommandGenerator/CommandGenerator_Callback.res.mjs";
27
29
  import * as InboundTranslationSlice_Callback$ReventlessCore from "../InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs";
@@ -382,11 +384,7 @@ function Make(DcbEventLogStorage) {
382
384
  if (receiveFn !== undefined) {
383
385
  return Effect.promise(async () => {
384
386
  let result = await receiveFn(args);
385
- if (result.TAG === "Ok") {
386
- return result._0.map(prim => prim);
387
- } else {
388
- return result._0;
389
- }
387
+ return CommandTopic$ReventlessCore.commandOutcomeToJson(InboundTranslationSlice$ReventlessCore.receiveResultToOutcome(result));
390
388
  });
391
389
  } else {
392
390
  return baseHandler(event, ctx);
@@ -68,11 +68,16 @@ module Make = (Ops: Ops): T => {
68
68
  }
69
69
  }
70
70
 
71
+ // Storage stamped its own `recordedAt` at append but does not return it, so
72
+ // the topic envelope carries a publish-time stamp instead (a few ms later).
73
+ // StateViewSlice projections read this as `consumed.recordedAt`; the stored
74
+ // column remains authoritative for reads. Producer time is `meta.time`.
75
+ let recordedAt = Message.nowAsISOString()
71
76
  let _ = await Array.zip(rawEvents, finalRawEventsJson)
72
77
  ->Array.map(async ((rawEvent, eventJson)) => {
73
78
  let entityId =
74
79
  rawEvent.tags->Array.get(0)->Option.map(t => t.value)->Option.getOr(name)
75
- let eventJson' = Message.composeEventJson'(entityId, rawEvent.meta, eventJson)
80
+ let eventJson' = Message.composeEventJson'(entityId, rawEvent.meta, ~recordedAt, eventJson)
76
81
  try await Ops.publishJson(serviceName, rawEvent.meta, eventJson') catch {
77
82
  | JsExn(err) =>
78
83
  let errMsg = err->JsExn.message->Option.getOr("unknown")
@@ -41,10 +41,11 @@ function Make(Ops) {
41
41
  } else {
42
42
  finalRawEventsJson = rawEventsJson;
43
43
  }
44
+ let recordedAt = Message$ReventlessCore.nowAsISOString();
44
45
  await Promise.all(Stdlib_Array.zip(rawEvents, finalRawEventsJson).map(async param => {
45
46
  let rawEvent = param[0];
46
47
  let entityId = Stdlib_Option.getOr(Stdlib_Option.map(rawEvent.tags[0], t => t.value), name);
47
- let eventJson$p = Message$ReventlessCore.composeEventJson$p(entityId, rawEvent.meta, param[1]);
48
+ let eventJson$p = Message$ReventlessCore.composeEventJson$p(entityId, rawEvent.meta, recordedAt, param[1]);
48
49
  try {
49
50
  return await Ops.publishJson(serviceName, rawEvent.meta, eventJson$p);
50
51
  } catch (raw_err) {
@@ -3,8 +3,33 @@ let componentType = ComponentType.InboundTranslationSlice
3
3
  type t = ReventlessInfra.InboundTranslationSlice.t
4
4
  type outputs = ReventlessInfra.InboundTranslationSlice.outputs
5
5
  type operations = ReventlessInfra.InboundTranslationSlice.operations
6
+ type acceptedResult = ReventlessInfra.InboundTranslationSlice.acceptedResult
7
+ type rejectedResult = ReventlessInfra.InboundTranslationSlice.rejectedResult
8
+ type receiveResult = ReventlessInfra.InboundTranslationSlice.receiveResult
6
9
  type component = Component.t<t, outputs, operations>
7
10
 
11
+ /**
12
+ Map a `receive` outcome onto the `CommandResult` union the slice's mutation field
13
+ declares, so both surfaces feed `CommandTopic.commandOutcomeToJson` rather than
14
+ hand-rolling a second `__typename` writer.
15
+
16
+ A translation can fan out across several targets; the mutation response reports
17
+ the first target as `entityId` and the fan-out count as `eventCount`. The
18
+ per-target detail stays queryable through the slice's audit read model. When the
19
+ translation legitimately produced no command, `entityId` is omitted (encoding as
20
+ `null`) rather than reporting a target that does not exist.
21
+ */
22
+ let receiveResultToOutcome = (result: receiveResult): CommandTopic.commandOutcome =>
23
+ switch result {
24
+ | Ok({requestId, targetIds, commandCount}) =>
25
+ switch targetIds->Array.get(0) {
26
+ | Some(entityId) => Accepted({msgId: requestId, entityId, eventCount: commandCount})
27
+ | None => Accepted({msgId: requestId, eventCount: commandCount})
28
+ }
29
+ | Error({requestId, error}) =>
30
+ Rejected({msgId: requestId, errorCode: "TranslationFailed", errorDetail: Some(error)})
31
+ }
32
+
8
33
  let toResolvedOutputs = (
9
34
  outputs: outputs,
10
35
  ): Pulumi.Output.t<ReventlessInterop.InboundTranslationSlice.resolvedOutputs> =>
@@ -3,6 +3,36 @@
3
3
  import * as Pulumi from "@pulumi/pulumi";
4
4
  import * as Adapter$ReventlessCore from "../../adapter/Adapter.res.mjs";
5
5
 
6
+ function receiveResultToOutcome(result) {
7
+ if (result.TAG === "Ok") {
8
+ let match = result._0;
9
+ let commandCount = match.commandCount;
10
+ let requestId = match.requestId;
11
+ let entityId = match.targetIds[0];
12
+ if (entityId !== undefined) {
13
+ return {
14
+ TAG: "Accepted",
15
+ msgId: requestId,
16
+ entityId: entityId,
17
+ eventCount: commandCount
18
+ };
19
+ } else {
20
+ return {
21
+ TAG: "Accepted",
22
+ msgId: requestId,
23
+ eventCount: commandCount
24
+ };
25
+ }
26
+ }
27
+ let match$1 = result._0;
28
+ return {
29
+ TAG: "Rejected",
30
+ msgId: match$1.requestId,
31
+ errorCode: "TranslationFailed",
32
+ errorDetail: match$1.error
33
+ };
34
+ }
35
+
6
36
  function toResolvedOutputs(outputs) {
7
37
  return Pulumi.all([
8
38
  Adapter$ReventlessCore.resourcesToInterop(outputs.resources),
@@ -19,6 +49,7 @@ let componentType = "InboundTranslationSlice";
19
49
 
20
50
  export {
21
51
  componentType,
52
+ receiveResultToOutcome,
22
53
  toResolvedOutputs,
23
54
  }
24
55
  /* @pulumi/pulumi Not a pure module */