@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.
- package/CHANGELOG.md +21 -0
- package/package.json +8 -8
- package/src/Message.res +2 -1
- package/src/Message.res.mjs +5 -1
- package/src/adapter/Monitoring/Monitoring.res +14 -2
- package/src/adapter/Monitoring/Monitoring.res.mjs +4 -2
- package/src/admin/Platform_Admin_Structure.res +2 -0
- package/src/admin/Platform_Admin_Structure.res.mjs +2 -0
- package/src/admin/Platform_ComponentDefinitionsApi.res +2 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +5 -1
- package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res +1 -1
- package/src/admin/UiFragmentRegistry/StateViewSlice/UiFragments_Projection.res.mjs +2 -1
- package/src/components/Api/ApiTargetStateHelpers.res +20 -0
- package/src/components/Api/ApiTargetStateHelpers.res.mjs +16 -0
- package/src/components/Api/GraphQL_FragmentGenerator.res +1 -0
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +1 -0
- package/src/components/Api/SchemaType.res +3 -0
- package/src/components/Api/SchemaType.res.mjs +10 -2
- package/src/components/Api/SuryToJsonSchema.res +16 -0
- package/src/components/Api/SuryToJsonSchema.res.mjs +28 -0
- package/src/components/Dcb/Dcb_Builder.res +4 -5
- package/src/components/Dcb/Dcb_Builder.res.mjs +3 -5
- package/src/components/DcbEventLog/DcbEventLog_Operations.res +6 -1
- package/src/components/DcbEventLog/DcbEventLog_Operations.res.mjs +2 -1
- package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +25 -0
- package/src/components/InboundTranslationSlice/InboundTranslationSlice.res.mjs +31 -0
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +8 -8
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +26 -6
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +44 -37
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res.mjs +17 -25
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +27 -2
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +4 -0
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +20 -6
- package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +23 -5
- package/src/components/StateViewSlice/StateViewSlice_Callback.res +1 -1
- package/src/components/StateViewSlice/StateViewSlice_Callback.res.mjs +5 -1
- package/src/plugin/component/Plugin_Helpers.res +4 -1
- package/src/plugin/component/Plugin_Structure.res +15 -1
- package/src/plugin/component/Plugin_Structure.res.mjs +4 -1
- package/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs +2 -0
- package/tests/IdentityTest.res.mjs +174 -0
- package/tests/ResourceAttributionTest.res.mjs +75 -0
- package/tests/adapter/MonitoringTest.res +32 -2
- package/tests/adapter/MonitoringTest.res.mjs +116 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +10 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +365 -0
- package/tests/admin/Platform_UIFragmentsApiTest.res.mjs +101 -0
- package/tests/aggregate/AggregateCacheTest.res.mjs +433 -0
- package/tests/aggregate/AggregateCallbackTest.res.mjs +143 -0
- package/tests/aggregate/AggregateCausationTest.res.mjs +47 -0
- package/tests/aggregate/AggregateConflictTest.res.mjs +386 -0
- package/tests/aggregate/AggregateFixtures.res.mjs +308 -0
- package/tests/aggregate/AggregateRejectionTest.res.mjs +166 -0
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +645 -0
- package/tests/api/ApiNoApiTest.res.mjs +90 -0
- package/tests/api/GraphQL_StitcherTest.res.mjs +112 -0
- package/tests/api/SimpleTest.res.mjs +10 -0
- package/tests/api/SuryToJsonSchemaTest.res +68 -0
- package/tests/api/SuryToJsonSchemaTest.res.mjs +843 -0
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res.mjs +160 -0
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +197 -0
- package/tests/commandtopic/CommandTopicCallbackFixtures.res.mjs +152 -0
- package/tests/commandtopic/CommandTopicCallbackTest.res.mjs +106 -0
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res.mjs +109 -0
- package/tests/counter/CounterCallbackTest.res.mjs +140 -0
- package/tests/counter/CounterFixtures.res.mjs +67 -0
- package/tests/dcb/DcbDecodeTest.res.mjs +194 -0
- package/tests/dcb/DcbEventLogOperationsTest.res.mjs +268 -0
- package/tests/dcb/DcbFixtures.res.mjs +534 -0
- package/tests/dcb/DcbScopeInferenceTest.res.mjs +480 -0
- package/tests/dcb/DcbStateChangeSliceTest.res.mjs +540 -0
- package/tests/dcb/DcbTagTest.res.mjs +867 -0
- package/tests/dcb/DcbValidationTest.res.mjs +476 -0
- package/tests/eventlog/EventLogFixtures.res.mjs +196 -0
- package/tests/eventlog/EventLogOperationsTest.res.mjs +192 -0
- package/tests/eventlog/EventLogRetryTest.res.mjs +195 -0
- package/tests/eventmapper/EventMapperCallbackTest.res.mjs +141 -0
- package/tests/eventmapper/EventMapperFixtures.res.mjs +440 -0
- package/tests/eventmapper/EventMapperTest.res.mjs +83 -0
- package/tests/eventtopic/EventTopicFixtures.res.mjs +65 -0
- package/tests/eventtopic/EventTopicOperationsTest.res.mjs +71 -0
- package/tests/extensionpoint/ExtensionPointCallbackTest.res.mjs +94 -0
- package/tests/extensionpoint/ExtensionPointFixtures.res.mjs +205 -0
- package/tests/extensionpoint/ExtensionPointOperationsTest.res.mjs +304 -0
- package/tests/extensionpoint/PluginDisconnectScheduleTest.res.mjs +92 -0
- package/tests/ftp/FTPTest.res.mjs +88 -0
- package/tests/logger/LogFormatTest.res.mjs +329 -0
- package/tests/logger/LoggerLazyTest.res.mjs +40 -0
- package/tests/message/MessageTest.res.mjs +218 -0
- package/tests/message/MetaEnvelopeTest.res.mjs +188 -0
- package/tests/plugin/HeartbeatDisconnectGraceTest.res.mjs +91 -0
- package/tests/plugin/ManifestVisibilityTest.res.mjs +215 -0
- package/tests/plugin/PluginSpecExperiment.res.mjs +31 -0
- package/tests/plugin/PluginStructureTest.res +55 -0
- package/tests/plugin/PluginStructureTest.res.mjs +467 -0
- package/tests/plugin/PluginVersionTest.res.mjs +30 -0
- package/tests/plugin/SchemaWalkerTest.res.mjs +160 -0
- package/tests/plugin/StateChangeSlice/PsPlaceOrder.res.mjs +62 -0
- package/tests/plugin/StateChangeSlice/PsShipOrder.res +4 -1
- package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +77 -0
- package/tests/plugin/StateViewSlice/PsAnnotatedView.res +5 -4
- package/tests/plugin/StateViewSlice/PsAnnotatedView.res.mjs +110 -0
- package/tests/plugin/StateViewSlice/PsAvailableProductsView.res +1 -1
- package/tests/plugin/StateViewSlice/PsAvailableProductsView.res.mjs +70 -0
- package/tests/plugin/StateViewSlice/PsCustomersView.res +1 -1
- package/tests/plugin/StateViewSlice/PsCustomersView.res.mjs +70 -0
- package/tests/plugin/StateViewSlice/PsOrdersView.res +1 -1
- package/tests/plugin/StateViewSlice/PsOrdersView.res.mjs +82 -0
- package/tests/poc/StructuralSubtypingPoc.res.mjs +61 -0
- package/tests/projection/ProjectionOptimizeTest.res.mjs +75 -0
- package/tests/querydb/QueryDbFixtures.res.mjs +135 -0
- package/tests/querydb/QueryDbOperationsTest.res.mjs +168 -0
- package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res.mjs +77 -0
- package/tests/sideeffecthandler/SideEffectHandlerFixtures.res.mjs +139 -0
- package/tests/util/CommandPublisherTest.res.mjs +78 -0
- package/tests/util/CsvStreamTest.res.mjs +51 -0
- package/tests/util/LruTest.res.mjs +105 -0
- package/tests/util/MetricsTest.res.mjs +53 -0
|
@@ -29,7 +29,7 @@ module type T = {
|
|
|
29
29
|
let receive: (
|
|
30
30
|
ReventlessInfra.CommandTopic.publishJsons,
|
|
31
31
|
JSON.t,
|
|
32
|
-
) => promise<
|
|
32
|
+
) => promise<ReventlessInfra.InboundTranslationSlice.receiveResult>
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
module Make = (
|
|
@@ -53,7 +53,7 @@ module Make = (
|
|
|
53
53
|
let receive = async (
|
|
54
54
|
publishJsons: ReventlessInfra.CommandTopic.publishJsons,
|
|
55
55
|
inputJson: JSON.t,
|
|
56
|
-
) => {
|
|
56
|
+
): ReventlessInfra.InboundTranslationSlice.receiveResult => {
|
|
57
57
|
let requestId = Uuid.v4()
|
|
58
58
|
|
|
59
59
|
// Parse the external input
|
|
@@ -78,7 +78,7 @@ module Make = (
|
|
|
78
78
|
receivedAt: now(),
|
|
79
79
|
},
|
|
80
80
|
)
|
|
81
|
-
Error(msg)
|
|
81
|
+
Error({requestId, error: msg})
|
|
82
82
|
|
|
83
83
|
| Ok(input) =>
|
|
84
84
|
switch Translation.translate(input) {
|
|
@@ -94,7 +94,7 @@ module Make = (
|
|
|
94
94
|
receivedAt: now(),
|
|
95
95
|
},
|
|
96
96
|
)
|
|
97
|
-
Ok([])
|
|
97
|
+
Ok({requestId, targetIds: [], commandCount: 0})
|
|
98
98
|
} else {
|
|
99
99
|
// Encode all commands; abort on first encoding failure
|
|
100
100
|
let msgs = ref([])
|
|
@@ -134,7 +134,7 @@ module Make = (
|
|
|
134
134
|
receivedAt: now(),
|
|
135
135
|
},
|
|
136
136
|
)
|
|
137
|
-
Error(msg)
|
|
137
|
+
Error({requestId, error: msg})
|
|
138
138
|
| None =>
|
|
139
139
|
try {
|
|
140
140
|
await publishJsons(msgs.contents)
|
|
@@ -152,7 +152,7 @@ module Make = (
|
|
|
152
152
|
receivedAt: now(),
|
|
153
153
|
},
|
|
154
154
|
)
|
|
155
|
-
Ok(targetIds)
|
|
155
|
+
Ok({requestId, targetIds, commandCount: pairs->Array.length})
|
|
156
156
|
} catch {
|
|
157
157
|
| exn =>
|
|
158
158
|
let msg =
|
|
@@ -169,7 +169,7 @@ module Make = (
|
|
|
169
169
|
receivedAt: now(),
|
|
170
170
|
},
|
|
171
171
|
)
|
|
172
|
-
Error(msg)
|
|
172
|
+
Error({requestId, error: msg})
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
}
|
|
@@ -184,7 +184,7 @@ module Make = (
|
|
|
184
184
|
receivedAt: now(),
|
|
185
185
|
},
|
|
186
186
|
)
|
|
187
|
-
Error(msg)
|
|
187
|
+
Error({requestId, error: msg})
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -59,7 +59,11 @@ function Make(Spec) {
|
|
|
59
59
|
};
|
|
60
60
|
return {
|
|
61
61
|
TAG: "Ok",
|
|
62
|
-
_0:
|
|
62
|
+
_0: {
|
|
63
|
+
requestId: requestId,
|
|
64
|
+
targetIds: [],
|
|
65
|
+
commandCount: 0
|
|
66
|
+
}
|
|
63
67
|
};
|
|
64
68
|
}
|
|
65
69
|
let msgs = {
|
|
@@ -101,7 +105,10 @@ function Make(Spec) {
|
|
|
101
105
|
};
|
|
102
106
|
return {
|
|
103
107
|
TAG: "Error",
|
|
104
|
-
_0:
|
|
108
|
+
_0: {
|
|
109
|
+
requestId: requestId,
|
|
110
|
+
error: msg$1
|
|
111
|
+
}
|
|
105
112
|
};
|
|
106
113
|
}
|
|
107
114
|
try {
|
|
@@ -116,7 +123,11 @@ function Make(Spec) {
|
|
|
116
123
|
};
|
|
117
124
|
return {
|
|
118
125
|
TAG: "Ok",
|
|
119
|
-
_0:
|
|
126
|
+
_0: {
|
|
127
|
+
requestId: requestId,
|
|
128
|
+
targetIds: targetIds,
|
|
129
|
+
commandCount: pairs$1.length
|
|
130
|
+
}
|
|
120
131
|
};
|
|
121
132
|
} catch (raw_exn$1) {
|
|
122
133
|
let exn$1 = Primitive_exceptions.internalToException(raw_exn$1);
|
|
@@ -129,7 +140,10 @@ function Make(Spec) {
|
|
|
129
140
|
};
|
|
130
141
|
return {
|
|
131
142
|
TAG: "Error",
|
|
132
|
-
_0:
|
|
143
|
+
_0: {
|
|
144
|
+
requestId: requestId,
|
|
145
|
+
error: msg$2
|
|
146
|
+
}
|
|
133
147
|
};
|
|
134
148
|
}
|
|
135
149
|
} else {
|
|
@@ -142,7 +156,10 @@ function Make(Spec) {
|
|
|
142
156
|
};
|
|
143
157
|
return {
|
|
144
158
|
TAG: "Error",
|
|
145
|
-
_0:
|
|
159
|
+
_0: {
|
|
160
|
+
requestId: requestId,
|
|
161
|
+
error: msg$3
|
|
162
|
+
}
|
|
146
163
|
};
|
|
147
164
|
}
|
|
148
165
|
} else {
|
|
@@ -155,7 +172,10 @@ function Make(Spec) {
|
|
|
155
172
|
};
|
|
156
173
|
return {
|
|
157
174
|
TAG: "Error",
|
|
158
|
-
_0:
|
|
175
|
+
_0: {
|
|
176
|
+
requestId: requestId,
|
|
177
|
+
error: msg$4
|
|
178
|
+
}
|
|
159
179
|
};
|
|
160
180
|
}
|
|
161
181
|
};
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Follows the AutomationSlice_Builder pattern for adapter injection.
|
|
5
5
|
|
|
6
|
-
let log = Logger.fromEnv()
|
|
7
|
-
|
|
8
6
|
module Make = (
|
|
9
7
|
RuntimeEnvironment: Runtime.Environment,
|
|
10
8
|
QueryDbStorage: QueryDb_Adapter.Storage,
|
|
@@ -80,18 +78,25 @@ module Make = (
|
|
|
80
78
|
)
|
|
81
79
|
|
|
82
80
|
let dcbEventTopicOutputs: EventTopic.outputs = (dcbEventLog->Component.outputs).eventTopic
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
// Key the topic by the source-name convention AutomationSlice and
|
|
82
|
+
// Dcb_Builder use (`<pluginName>DcbEventLog`) rather than by this slice's
|
|
83
|
+
// own name. No backend routes on the key — local subscribes on
|
|
84
|
+
// `resource.name`, AWS reads `Dict.valuesToArray` — but channel specs are
|
|
85
|
+
// merged with `Dict.assign`, so slice-specific keys make one topic appear
|
|
86
|
+
// once per slice instead of collapsing to a single entry.
|
|
87
|
+
let dcbEventLogName =
|
|
88
|
+
(dcbEventLog->Component.toPulumiResource).name->Option.getOr("") ++ "DcbEventLog"
|
|
89
|
+
let allEventTopics = Dict.fromArray([(dcbEventLogName, dcbEventTopicOutputs)])
|
|
90
|
+
|
|
91
|
+
// Build the EventCollector inside an Output.all2 so both `queryDbOps` and
|
|
92
|
+
// `publishJsonsFn` are captured in the same closure. The jsonEventsHandler
|
|
93
|
+
// then has direct access to `publishJsonsFn` — avoids racing a
|
|
94
|
+
// side-effect-only `publishJsons.apply` against the first event arrival
|
|
95
|
+
// (which silently dropped Phase 2 in the AutomationSlice's previous shape).
|
|
91
96
|
let eventCollector =
|
|
92
|
-
queryDb
|
|
93
|
-
->
|
|
94
|
-
->Pulumi.Output.apply(queryDbOps => {
|
|
97
|
+
(queryDb->Component.operations, publishJsons)
|
|
98
|
+
->Pulumi.Output.all2
|
|
99
|
+
->Pulumi.Output.apply(((queryDbOps, publishJsonsFn)) => {
|
|
95
100
|
let ec = SpecificEventCollector.make(
|
|
96
101
|
~name=Spec.name,
|
|
97
102
|
~eventTopics=allEventTopics,
|
|
@@ -104,7 +109,17 @@ module Make = (
|
|
|
104
109
|
->Stream.mapEffect(json =>
|
|
105
110
|
Effect.sync(
|
|
106
111
|
() => {
|
|
107
|
-
|
|
112
|
+
// Events arrive as `{id, meta, event}` envelopes (see
|
|
113
|
+
// Message.composeEventJson'). The decoder works on the inner
|
|
114
|
+
// `event` payload — TAG + fields at the top level — not the
|
|
115
|
+
// wrapper. Splitting the wrapper yields eventType "Unknown",
|
|
116
|
+
// which DcbDecode drops without warning.
|
|
117
|
+
let rawEvent =
|
|
118
|
+
json
|
|
119
|
+
->JSON.Decode.object
|
|
120
|
+
->Option.flatMap(d => d->Dict.get("event"))
|
|
121
|
+
->Option.getOr(json)
|
|
122
|
+
let (eventType, dataDict) = rawEvent->Message.splitMessage
|
|
108
123
|
switch decoder.decode(~eventType, ~data=dataDict) {
|
|
109
124
|
| Some(event) => [event]
|
|
110
125
|
| None => []
|
|
@@ -126,29 +141,21 @@ module Make = (
|
|
|
126
141
|
// an inbound command whose downstream events fan back to this
|
|
127
142
|
// same topic, awaiting would self-deadlock the bus — same
|
|
128
143
|
// shape as AutomationSlice. Detach it; errors are logged.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Promise.resolve()
|
|
145
|
-
})
|
|
146
|
-
| None =>
|
|
147
|
-
log.error(
|
|
148
|
-
~comp=`OutboundTranslationSlice(${Spec.name})`,
|
|
149
|
-
`publishJsons not yet resolved`,
|
|
150
|
-
)
|
|
151
|
-
}
|
|
144
|
+
let _ =
|
|
145
|
+
Callback.phase2(publishJsonsFn)
|
|
146
|
+
->Promise.then(() => syncToQueryDb(queryDbOps))
|
|
147
|
+
->Promise.catch(exn => {
|
|
148
|
+
let errMsg =
|
|
149
|
+
exn
|
|
150
|
+
->JsExn.fromException
|
|
151
|
+
->Option.flatMap(JsExn.message)
|
|
152
|
+
->Option.getOr("unknown")
|
|
153
|
+
EffectLogger.logError(
|
|
154
|
+
~comp=`OutboundTranslationSlice(${Spec.name})`,
|
|
155
|
+
`detached phase 2 error: ${errMsg}`,
|
|
156
|
+
)->Effect.runSync
|
|
157
|
+
Promise.resolve()
|
|
158
|
+
})
|
|
152
159
|
},
|
|
153
160
|
)
|
|
154
161
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
|
|
4
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
5
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
6
|
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
6
7
|
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
@@ -13,7 +14,6 @@ import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
|
13
14
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
14
15
|
import * as DcbDecode$Reventless from "@reventlessdev/reventless-spec/src/components/DcbDecode.res.mjs";
|
|
15
16
|
import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
|
|
16
|
-
import * as Logger$ReventlessCore from "../../util/Logger.res.mjs";
|
|
17
17
|
import * as Message$ReventlessCore from "../../Message.res.mjs";
|
|
18
18
|
import * as Component$ReventlessCore from "../Component.res.mjs";
|
|
19
19
|
import * as EffectLogger$ReventlessCore from "../../util/EffectLogger.res.mjs";
|
|
@@ -24,8 +24,6 @@ import * as EventCollector_Builder$ReventlessCore from "../EventCollector/EventC
|
|
|
24
24
|
import * as OutboundTranslationSlice$ReventlessCore from "./OutboundTranslationSlice.res.mjs";
|
|
25
25
|
import * as OutboundTranslationSlice_Callback$ReventlessCore from "./OutboundTranslationSlice_Callback.res.mjs";
|
|
26
26
|
|
|
27
|
-
let log = Logger$ReventlessCore.fromEnv();
|
|
28
|
-
|
|
29
27
|
function Make(RuntimeEnvironment) {
|
|
30
28
|
return QueryDbStorage => (QueryDbResolvers => (EventCollectorChannel => (EventCollectorRuntimeBuilder => (Api => {
|
|
31
29
|
let Make = Spec => (Translation => {
|
|
@@ -64,23 +62,24 @@ function Make(RuntimeEnvironment) {
|
|
|
64
62
|
name: Spec.name
|
|
65
63
|
}, opts);
|
|
66
64
|
let dcbEventTopicOutputs = Component$ReventlessCore.outputs(dcbEventLog).eventTopic;
|
|
65
|
+
let dcbEventLogName = Stdlib_Option.getOr(Component$ReventlessCore.toPulumiResource(dcbEventLog).__name, "") + "DcbEventLog";
|
|
67
66
|
let allEventTopics = Object.fromEntries([[
|
|
68
|
-
|
|
67
|
+
dcbEventLogName,
|
|
69
68
|
dcbEventTopicOutputs
|
|
70
69
|
]]);
|
|
71
|
-
let
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
let eventCollector = Component$ReventlessCore.operations(queryDb).apply(queryDbOps => {
|
|
70
|
+
let eventCollector = Pulumi.all([
|
|
71
|
+
Component$ReventlessCore.operations(queryDb),
|
|
72
|
+
publishJsons
|
|
73
|
+
]).apply(param => {
|
|
74
|
+
let publishJsonsFn = param[1];
|
|
75
|
+
let queryDbOps = param[0];
|
|
78
76
|
let ec = SpecificEventCollector.make(Spec.name, allEventTopics, {
|
|
79
77
|
kind: "OutboundTranslationSlice",
|
|
80
78
|
name: Spec.name
|
|
81
79
|
}, opts);
|
|
82
80
|
let jsonEventsHandler = stream => Effect.flatMap(Stream.runCollect(Stream$1.flatMap(Stream$1.mapEffect(stream, json => Effect.sync(() => {
|
|
83
|
-
let
|
|
81
|
+
let rawEvent = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), d => d["event"]), json);
|
|
82
|
+
let match = Message$ReventlessCore.splitMessage(rawEvent);
|
|
84
83
|
let event = decoder.decode(match[0], match[1]);
|
|
85
84
|
if (event !== undefined) {
|
|
86
85
|
return [Primitive_option.valFromOption(event)];
|
|
@@ -90,17 +89,11 @@ function Make(RuntimeEnvironment) {
|
|
|
90
89
|
})), events => Stream$1.fromIterable(events))), eventsArr => Effect.promise(async () => {
|
|
91
90
|
Callback.phase1(eventsArr);
|
|
92
91
|
await syncToQueryDb(queryDbOps);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return Promise.resolve();
|
|
99
|
-
});
|
|
100
|
-
return;
|
|
101
|
-
} else {
|
|
102
|
-
return log.error(`OutboundTranslationSlice(` + Spec.name + `)`, undefined, `publishJsons not yet resolved`);
|
|
103
|
-
}
|
|
92
|
+
Stdlib_Promise.$$catch(Callback.phase2(publishJsonsFn).then(() => syncToQueryDb(queryDbOps)), exn => {
|
|
93
|
+
let errMsg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown");
|
|
94
|
+
Effect.runSync(EffectLogger$ReventlessCore.logError(`OutboundTranslationSlice(` + Spec.name + `)`, undefined, `detached phase 2 error: ` + errMsg));
|
|
95
|
+
return Promise.resolve();
|
|
96
|
+
});
|
|
104
97
|
}));
|
|
105
98
|
let handler = SpecificEventCollector.makeHandler(ec, jsonEventsHandler);
|
|
106
99
|
let resources = Component$ReventlessCore.outputs(queryDb).resources;
|
|
@@ -140,7 +133,6 @@ function Make(RuntimeEnvironment) {
|
|
|
140
133
|
}
|
|
141
134
|
|
|
142
135
|
export {
|
|
143
|
-
log,
|
|
144
136
|
Make,
|
|
145
137
|
}
|
|
146
|
-
/*
|
|
138
|
+
/* Stream Not a pure module */
|
|
@@ -227,6 +227,18 @@ module Make = (
|
|
|
227
227
|
// only the delta the conflicting writer added rather than full history.
|
|
228
228
|
let seed = ref(cacheGet())
|
|
229
229
|
|
|
230
|
+
// One hit/miss sample per command, keyed on whether the warm-instance cache
|
|
231
|
+
// seeded this command's decision read. If Miss dominates Hit for a slice, the
|
|
232
|
+
// flat-capacity cache is thrashing a wide entity set — the operator's cue to
|
|
233
|
+
// disable it. Paired with DcbDecisionModelDeltaEventCount (the delta size a
|
|
234
|
+
// hit actually read); both consumed by the reventless-aws metric filters.
|
|
235
|
+
Metrics.emitCount(
|
|
236
|
+
~metric=seed.contents->Option.isSome
|
|
237
|
+
? "DcbDecisionModelCacheHit"
|
|
238
|
+
: "DcbDecisionModelCacheMiss",
|
|
239
|
+
~slice=Spec.name,
|
|
240
|
+
)
|
|
241
|
+
|
|
230
242
|
let rec attempt = (~retries) => {
|
|
231
243
|
let (baseState, afterPos) = switch seed.contents {
|
|
232
244
|
| Some((state, head)) => (state, head)
|
|
@@ -279,14 +291,27 @@ module Make = (
|
|
|
279
291
|
},
|
|
280
292
|
]),
|
|
281
293
|
))
|
|
282
|
-
->Effect.tap(((_, _, reads)) =>
|
|
294
|
+
->Effect.tap(((_, _, reads)) => {
|
|
295
|
+
// On a warm first attempt, record how many events the delta read
|
|
296
|
+
// returned — near-zero on a healthy hit; a large value means the entity
|
|
297
|
+
// churned between commands, so the cache saved little. Gated to the first
|
|
298
|
+
// attempt (retries at maxRetries) so each command yields at most one
|
|
299
|
+
// sample, aligned 1:1 with a CacheHit. Conflict-driven retry reads are
|
|
300
|
+
// covered by AppendRetry instead.
|
|
301
|
+
if cacheHit && retries == maxRetries {
|
|
302
|
+
Metrics.emitCount(
|
|
303
|
+
~metric="DcbDecisionModelDeltaEventCount",
|
|
304
|
+
~slice=Spec.name,
|
|
305
|
+
~value=reads->Array.length,
|
|
306
|
+
)
|
|
307
|
+
}
|
|
283
308
|
EffectLogger.logInfo(
|
|
284
309
|
~comp,
|
|
285
310
|
`read${cacheHit ? " (cached, delta)" : ""}: ${reads->Array.length->Int.toString} event(s)${reads->Array.length == 0
|
|
286
311
|
? ""
|
|
287
312
|
: ` [${reads->Array.join(", ")}]`}`,
|
|
288
313
|
)
|
|
289
|
-
)
|
|
314
|
+
})
|
|
290
315
|
->Effect.flatMap(((state, headPosition, _)) =>
|
|
291
316
|
EffectLogger.logDebug(
|
|
292
317
|
~comp,
|
|
@@ -101,6 +101,7 @@ function Make(Spec) {
|
|
|
101
101
|
let seed = {
|
|
102
102
|
contents: cacheGet()
|
|
103
103
|
};
|
|
104
|
+
Metrics$ReventlessCore.emitCount(Stdlib_Option.isSome(seed.contents) ? "DcbDecisionModelCacheHit" : "DcbDecisionModelCacheMiss", Spec.name, undefined);
|
|
104
105
|
let attempt = retries => {
|
|
105
106
|
let match = seed.contents;
|
|
106
107
|
let match$1 = match !== undefined ? [
|
|
@@ -152,6 +153,9 @@ function Make(Spec) {
|
|
|
152
153
|
];
|
|
153
154
|
}), param => {
|
|
154
155
|
let reads = param[2];
|
|
156
|
+
if (cacheHit && retries === 3) {
|
|
157
|
+
Metrics$ReventlessCore.emitCount("DcbDecisionModelDeltaEventCount", Spec.name, reads.length);
|
|
158
|
+
}
|
|
155
159
|
return EffectLogger$ReventlessCore.logInfo(comp, undefined, `read` + (
|
|
156
160
|
cacheHit ? " (cached, delta)" : ""
|
|
157
161
|
) + `: ` + reads.length.toString() + ` event(s)` + (
|
|
@@ -95,16 +95,30 @@ module Make = (
|
|
|
95
95
|
let total = events->Array.length->Int.toString
|
|
96
96
|
events
|
|
97
97
|
->Array.mapWithIndex((json, i) => {
|
|
98
|
-
let
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
let envelopeDict = json->JSON.Decode.object->Option.getOr(Dict.make())
|
|
99
|
+
let rawEvent = envelopeDict->Dict.get("event")->Option.getOr(json)
|
|
100
|
+
// Events arrive as `{id, meta, recordedAt, event}` envelopes
|
|
101
|
+
// (Message.composeEventJson' / ProjectionCheckpoint catch-up).
|
|
102
|
+
// Surface `meta` + `recordedAt` to the projection as `consumed`;
|
|
103
|
+
// fall back defensively so a malformed envelope never drops the event.
|
|
104
|
+
let meta = switch envelopeDict->Dict.get("meta") {
|
|
105
|
+
| Some(m) =>
|
|
106
|
+
switch m->S.parseJsonOrThrow(Message.metaSchema) {
|
|
107
|
+
| parsed => parsed
|
|
108
|
+
| exception _ => Message.generateMeta(~service=Spec.name)
|
|
109
|
+
}
|
|
110
|
+
| None => Message.generateMeta(~service=Spec.name)
|
|
111
|
+
}
|
|
112
|
+
let recordedAt =
|
|
113
|
+
envelopeDict
|
|
114
|
+
->Dict.get("recordedAt")
|
|
115
|
+
->Option.flatMap(JSON.Decode.string)
|
|
116
|
+
->Option.getOr("")
|
|
103
117
|
let (eventType, dataDict) = rawEvent->Message.splitMessage
|
|
104
118
|
switch decoder.decode(~eventType, ~data=dataDict) {
|
|
105
119
|
| Some(event) =>
|
|
106
120
|
let actions =
|
|
107
|
-
try Projection.project(event)
|
|
121
|
+
try Projection.project({event, meta, recordedAt})
|
|
108
122
|
catch {
|
|
109
123
|
| exn =>
|
|
110
124
|
let errMsg =
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
3
4
|
import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
|
|
4
5
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
6
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
@@ -80,7 +81,20 @@ function Make(RuntimeEnvironment) {
|
|
|
80
81
|
let jsonEventsHandler = stream => Effect.flatMap(Stream.runCollect(stream), events => {
|
|
81
82
|
let total = events.length.toString();
|
|
82
83
|
return Stdlib_Array.reduce(events.map((json, i) => {
|
|
83
|
-
let
|
|
84
|
+
let envelopeDict = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(json), {});
|
|
85
|
+
let rawEvent = Stdlib_Option.getOr(envelopeDict["event"], json);
|
|
86
|
+
let m = envelopeDict["meta"];
|
|
87
|
+
let meta;
|
|
88
|
+
if (m !== undefined) {
|
|
89
|
+
try {
|
|
90
|
+
meta = S.parseJsonOrThrow(m, Message$ReventlessCore.metaSchema);
|
|
91
|
+
} catch (exn) {
|
|
92
|
+
meta = Message$ReventlessCore.generateMeta(Spec.name, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
meta = Message$ReventlessCore.generateMeta(Spec.name, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
|
|
96
|
+
}
|
|
97
|
+
let recordedAt = Stdlib_Option.getOr(Stdlib_Option.flatMap(envelopeDict["recordedAt"], Stdlib_JSON.Decode.string), "");
|
|
84
98
|
let match = Message$ReventlessCore.splitMessage(rawEvent);
|
|
85
99
|
let dataDict = match[1];
|
|
86
100
|
let eventType = match[0];
|
|
@@ -90,10 +104,14 @@ function Make(RuntimeEnvironment) {
|
|
|
90
104
|
}
|
|
91
105
|
let actions;
|
|
92
106
|
try {
|
|
93
|
-
actions = Projection.project(
|
|
107
|
+
actions = Projection.project({
|
|
108
|
+
event: Primitive_option.valFromOption(event),
|
|
109
|
+
meta: meta,
|
|
110
|
+
recordedAt: recordedAt
|
|
111
|
+
});
|
|
94
112
|
} catch (raw_exn) {
|
|
95
|
-
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
96
|
-
let errMsg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown");
|
|
113
|
+
let exn$1 = Primitive_exceptions.internalToException(raw_exn);
|
|
114
|
+
let errMsg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn$1), Stdlib_JsExn.message), "unknown");
|
|
97
115
|
Effect.runSync(EffectLogger$ReventlessCore.logError(comp, undefined, `project failed for ` + eventType + `: ` + errMsg));
|
|
98
116
|
actions = [];
|
|
99
117
|
}
|
|
@@ -140,4 +158,4 @@ export {
|
|
|
140
158
|
FrameworkProjection,
|
|
141
159
|
Make,
|
|
142
160
|
}
|
|
143
|
-
/*
|
|
161
|
+
/* S Not a pure module */
|
|
@@ -40,7 +40,7 @@ module Make = (
|
|
|
40
40
|
| Some(event) =>
|
|
41
41
|
idx := idx.contents + 1
|
|
42
42
|
let id = raw.tags->Array.get(0)->Option.map(tag => tag.value)->Option.getOr("?")
|
|
43
|
-
let actions = Projection.project(event)
|
|
43
|
+
let actions = Projection.project({event, meta: raw.meta, recordedAt: raw.recordedAt})
|
|
44
44
|
let actionsStr = LogFormat.actionNames(actions)
|
|
45
45
|
EffectLogger.logInfo(
|
|
46
46
|
~comp,
|
|
@@ -28,7 +28,11 @@ function Make(Spec) {
|
|
|
28
28
|
let event = Primitive_option.valFromOption(decoded);
|
|
29
29
|
idx.contents = idx.contents + 1 | 0;
|
|
30
30
|
let id = Stdlib_Option.getOr(Stdlib_Option.map(raw.tags[0], tag => tag.value), "?");
|
|
31
|
-
let actions = Projection.project(
|
|
31
|
+
let actions = Projection.project({
|
|
32
|
+
event: event,
|
|
33
|
+
meta: raw.meta,
|
|
34
|
+
recordedAt: raw.recordedAt
|
|
35
|
+
});
|
|
32
36
|
let actionsStr = LogFormat$ReventlessCore.actionNames(actions);
|
|
33
37
|
Effect.runSync(EffectLogger$ReventlessCore.logInfo(comp, raw.data, `handling event ` + idx.contents.toString() + `/` + count + `: ` + LogFormat$ReventlessCore.bold(raw.eventType) + `(` + id + `) ` + actionsStr));
|
|
34
38
|
allActions.push(...actions);
|
|
@@ -932,7 +932,10 @@ type platformHooks = {
|
|
|
932
932
|
// InboundTranslationSlice — phase 1: register SDL + stub.
|
|
933
933
|
inboundMutationResolverHook?: (~fieldName: string, ~externalInputSchema: S.t<unknown>) => unit,
|
|
934
934
|
// InboundTranslationSlice — phase 2: bind receive.
|
|
935
|
-
inboundMutationBindReceiveHook?: (
|
|
935
|
+
inboundMutationBindReceiveHook?: (
|
|
936
|
+
~fieldName: string,
|
|
937
|
+
~receive: JSON.t => promise<ReventlessInfra.InboundTranslationSlice.receiveResult>,
|
|
938
|
+
) => unit,
|
|
936
939
|
// GraphQL type definitions.
|
|
937
940
|
schemaTypeRegistrationHook?: array<string> => unit,
|
|
938
941
|
// Subscription infrastructure (AWS): wire StateTopic + EventLogSubscription Lambdas.
|
|
@@ -171,6 +171,10 @@ let make = (
|
|
|
171
171
|
// markAllowedStates). Look it up by variant name; back-compat
|
|
172
172
|
// None when the variant lacks an @allowedStates annotation.
|
|
173
173
|
let allowedStates = ApiAllowedStatesHelpers.getAllowedStates(parentSchema, ~variantName)
|
|
174
|
+
// Declared `@targetState` (the command's *to* status), read the same way
|
|
175
|
+
// as allowedStates. None ⇒ AutoUI's board resolver falls back to its
|
|
176
|
+
// name-stem heuristic.
|
|
177
|
+
let targetState = ApiTargetStateHelpers.getTargetState(parentSchema, ~variantName)
|
|
174
178
|
// API-exposed iff the whole command isn't @noApi and this variant
|
|
175
179
|
// isn't in its @noApi-variants set — mirrors the API-generation filter
|
|
176
180
|
// (Plugin_Helpers / PluginBaseFragment). Drives the event-graph API badge.
|
|
@@ -185,9 +189,19 @@ let make = (
|
|
|
185
189
|
schema: (v->S.toJSONSchema->Obj.magic: JSON.t)->JSON.stringify,
|
|
186
190
|
level,
|
|
187
191
|
aggregateIdField,
|
|
188
|
-
|
|
192
|
+
// A non-exposed (`@noApi`) variant has no callable mutation field. For a
|
|
193
|
+
// single-exposed-command slice, `mutationFieldFor` resolves *every*
|
|
194
|
+
// variant — including the `@noApi` one — to the slice's one mutation
|
|
195
|
+
// field, so emitting it here would hand the non-exposed variant a
|
|
196
|
+
// sibling's callable-looking field (e.g. `ReopenOrder` →
|
|
197
|
+
// `Ordering_CancelOrder`). Emit an empty sentinel instead; the variant
|
|
198
|
+
// stays listed with `apiExposed: false` for the event-graph badge, but
|
|
199
|
+
// no consumer can mistake it for a callable field. Exposed variants are
|
|
200
|
+
// byte-identical.
|
|
201
|
+
mutationField: apiExposed ? mutationFieldFor(variantName) : "",
|
|
189
202
|
references,
|
|
190
203
|
allowedStates,
|
|
204
|
+
targetState,
|
|
191
205
|
apiExposed: Some(apiExposed),
|
|
192
206
|
}: Reventless.Plugin.commandDef)
|
|
193
207
|
}
|
|
@@ -13,6 +13,7 @@ import * as Api_Naming$ReventlessCore from "../../components/Api/Api_Naming.res.
|
|
|
13
13
|
import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
|
|
14
14
|
import * as ApiNoApiHelpers$ReventlessCore from "../../components/Api/ApiNoApiHelpers.res.mjs";
|
|
15
15
|
import * as SuryToJsonSchema$ReventlessCore from "../../components/Api/SuryToJsonSchema.res.mjs";
|
|
16
|
+
import * as ApiTargetStateHelpers$ReventlessCore from "../../components/Api/ApiTargetStateHelpers.res.mjs";
|
|
16
17
|
import * as ApiAllowedStatesHelpers$ReventlessCore from "../../components/Api/ApiAllowedStatesHelpers.res.mjs";
|
|
17
18
|
|
|
18
19
|
let log = Logger$ReventlessCore.fromEnv();
|
|
@@ -147,6 +148,7 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
147
148
|
}));
|
|
148
149
|
});
|
|
149
150
|
let allowedStates = ApiAllowedStatesHelpers$ReventlessCore.getAllowedStates(parentSchema, variantName);
|
|
151
|
+
let targetState = ApiTargetStateHelpers$ReventlessCore.getTargetState(parentSchema, variantName);
|
|
150
152
|
let apiExposed = false;
|
|
151
153
|
if (!ApiNoApiHelpers$ReventlessCore.isNoApi(parentSchema)) {
|
|
152
154
|
let excluded = ApiNoApiHelpers$ReventlessCore.getExcludedVariants(parentSchema);
|
|
@@ -157,9 +159,10 @@ function make(name, aggregatesOpt, readModelsOpt, stateViewSlicesOpt, stateChang
|
|
|
157
159
|
schema: JSON.stringify(S.toJSONSchema(v)),
|
|
158
160
|
level: match[0],
|
|
159
161
|
aggregateIdField: match[1],
|
|
160
|
-
mutationField: mutationFieldFor(variantName),
|
|
162
|
+
mutationField: apiExposed ? mutationFieldFor(variantName) : "",
|
|
161
163
|
references: references,
|
|
162
164
|
allowedStates: allowedStates,
|
|
165
|
+
targetState: targetState,
|
|
163
166
|
apiExposed: apiExposed
|
|
164
167
|
};
|
|
165
168
|
};
|