@reventlessdev/reventless-core 3.0.0-alpha.301 → 3.0.0-alpha.303
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 +31 -0
- package/package.json +7 -7
- package/src/adapter/Runtime/AggregateRuntime_Builder.res +2 -2
- package/src/adapter/Runtime/AggregateRuntime_Builder_Common.res +10 -5
- package/src/adapter/Runtime/AggregateRuntime_Builder_Micro.res +2 -2
- package/src/adapter/Runtime/EventCollectorRuntime_Builder.res +1 -1
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +8 -3
- package/src/adapter/Runtime/ExtensionPointRuntime_Builder.res +1 -1
- package/src/adapter/Runtime/PluginRuntime_Builder.res +3 -3
- package/src/adapter/Runtime/Runtime.res +10 -9
- package/src/adapter/Runtime/TaskRuntime_Builder.res +1 -1
- package/src/admin/Platform_Admin_Structure.res +3 -3
- package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
- package/src/admin/Platform_BakedManifest.res +7 -7
- package/src/admin/Platform_BakedManifest.res.mjs +2 -2
- package/src/admin/Platform_ComponentDefinitionsApi.res +12 -12
- package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +11 -11
- package/src/admin/Platform_PluginStructuresApi.res +6 -6
- package/src/admin/Platform_PluginStructuresApi.res.mjs +4 -4
- package/src/admin/UiFragmentRegistry/StateView/UiFragments.res.mjs +1 -4
- package/src/components/Aggregate/Aggregate_Callback.res +1 -1
- package/src/components/Aggregate/Aggregate_Callback.res.mjs +2 -1
- package/src/components/Api/GraphQL_FragmentGenerator.res +9 -0
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +6 -0
- package/src/components/Api/SuryToJsonSchema.res +1 -1
- package/src/components/CommandGenerator/CommandGenerator.res +1 -1
- package/src/components/CommandGenerator/CommandGenerator_Adapter.res +6 -1
- package/src/components/CommandTopic/CommandTopic.res +2 -2
- package/src/components/CommandTopic/CommandTopic.res.mjs +3 -0
- package/src/components/CommandTopic/CommandTopic_Adapter.res +1 -1
- package/src/components/CommandTopic/CommandTopic_Helpers.res +33 -15
- package/src/components/CommandTopic/CommandTopic_Helpers.res.mjs +34 -11
- package/src/components/EventCollector/EventCollector.res +1 -1
- package/src/components/EventCollector/EventCollector_Adapter.res +1 -1
- package/src/components/Heartbeat/Heartbeat.res +1 -1
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +20 -3
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +18 -4
- package/src/components/ReadModel/ReadModel.res +1 -1
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +3 -3
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +4 -3
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +2 -2
- package/src/components/StateViewSlice/StateViewSlice_Builder.res.mjs +1 -1
- package/src/components/StateViewSlice/StateViewSlice_Callback.res +2 -2
- package/src/components/StateViewSlice/StateViewSlice_Callback.res.mjs +1 -1
- package/src/components/Task/Task_Adapter.res +1 -1
- package/src/components/Task/Task_Builder.res +1 -1
- package/src/plugin/component/Plugin_Builder.res +7 -7
- package/src/plugin/component/Plugin_Builder.res.mjs +3 -3
- package/src/plugin/component/Plugin_BuiltHook.res +1 -1
- package/src/plugin/component/Plugin_Structure.res +18 -18
- package/src/plugin/component/Plugin_Structure.res.mjs +8 -8
- package/tests/admin/AdminApiSchemaDriftTest.res +8 -8
- package/tests/admin/AdminApiSchemaDriftTest.res.mjs +54 -54
- package/tests/admin/Platform_Admin_StructureTest.res +10 -10
- package/tests/admin/Platform_Admin_StructureTest.res.mjs +5 -5
- package/tests/admin/Platform_BakedManifestTest.res +7 -7
- package/tests/admin/Platform_BakedManifestTest.res.mjs +9 -9
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +11 -11
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +5 -5
- package/tests/admin/Platform_PluginStructuresApiTest.res +3 -3
- package/tests/aggregate/AggregateRejectionTest.res +8 -1
- package/tests/aggregate/AggregateRejectionTest.res.mjs +9 -1
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +54 -2
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res.mjs +51 -0
- package/tests/message/MessageTest.res +1 -1
- package/tests/plugin/ManifestVisibilityTest.res +2 -2
- package/tests/plugin/PluginStructureAccessTest.res.mjs +2 -2
- package/tests/plugin/PluginStructureTest.res +9 -9
- package/tests/plugin/PluginStructureTest.res.mjs +7 -7
- package/tests/plugin/StateView/PsAnnotatedView.res +1 -1
- package/tests/plugin/StateView/PsAnnotatedView.res.mjs +1 -4
- package/tests/plugin/StateView/PsAvailableProductsView.res.mjs +1 -4
- package/tests/plugin/StateView/PsCategoriesView.res.mjs +1 -4
- package/tests/plugin/StateView/PsCustomersView.res.mjs +1 -4
- package/tests/plugin/StateView/PsGatedView.res.mjs +1 -4
- package/tests/plugin/StateView/PsOrdersView.res.mjs +1 -4
- package/tests/plugin/StateView/PsShipmentsView.res.mjs +1 -4
|
@@ -12,17 +12,26 @@ let log = Logger.fromEnv()
|
|
|
12
12
|
// fragments. These SDL declarations are injected into any fragment that emits
|
|
13
13
|
// at least one mutation field; the stitcher dedupes by type name so
|
|
14
14
|
// concatenating across plugin + admin fragments stays safe.
|
|
15
|
+
//
|
|
16
|
+
// `cause` says which no a rejection is: the model's (DOMAIN, `errorCode` is the
|
|
17
|
+
// declared error), the caller's (ACCESS) or the platform's (INFRASTRUCTURE).
|
|
15
18
|
let commandResultSdlTypes: array<string> = [
|
|
16
19
|
`union CommandResult = CommandAccepted | CommandRejected | CommandPending`,
|
|
17
20
|
`type CommandAccepted {
|
|
18
21
|
msgId: ID!
|
|
19
22
|
entityId: ID
|
|
20
23
|
eventCount: Int!
|
|
24
|
+
}`,
|
|
25
|
+
`enum CommandRejectionCause {
|
|
26
|
+
DOMAIN
|
|
27
|
+
ACCESS
|
|
28
|
+
INFRASTRUCTURE
|
|
21
29
|
}`,
|
|
22
30
|
`type CommandRejected {
|
|
23
31
|
msgId: ID!
|
|
24
32
|
errorCode: String!
|
|
25
33
|
errorDetail: String
|
|
34
|
+
cause: CommandRejectionCause!
|
|
26
35
|
}`,
|
|
27
36
|
`type CommandPending {
|
|
28
37
|
msgId: ID!
|
|
@@ -22,11 +22,17 @@ let commandResultSdlTypes = [
|
|
|
22
22
|
msgId: ID!
|
|
23
23
|
entityId: ID
|
|
24
24
|
eventCount: Int!
|
|
25
|
+
}`,
|
|
26
|
+
`enum CommandRejectionCause {
|
|
27
|
+
DOMAIN
|
|
28
|
+
ACCESS
|
|
29
|
+
INFRASTRUCTURE
|
|
25
30
|
}`,
|
|
26
31
|
`type CommandRejected {
|
|
27
32
|
msgId: ID!
|
|
28
33
|
errorCode: String!
|
|
29
34
|
errorDetail: String
|
|
35
|
+
cause: CommandRejectionCause!
|
|
30
36
|
}`,
|
|
31
37
|
`type CommandPending {
|
|
32
38
|
msgId: ID!
|
|
@@ -392,7 +392,7 @@ and objectRefToJsonSchema = (
|
|
|
392
392
|
// not declare — which is precisely the mismatch that made read-model queries
|
|
393
393
|
// reference fields the server rejects.
|
|
394
394
|
//
|
|
395
|
-
// This is the single point for every JSON-Schema consumer: `
|
|
395
|
+
// This is the single point for every JSON-Schema consumer: `readSideDef.schema`,
|
|
396
396
|
// the MCP generator and the manifest all derive through this walk.
|
|
397
397
|
let internal = annotations->Option.flatMap(spec => spec.internal)->Option.getOr([])
|
|
398
398
|
fields
|
|
@@ -27,7 +27,7 @@ external asEvent: 'a => event = "%identity"
|
|
|
27
27
|
let metaInfo = event => (event->asEvent).meta->Option.map(({info}) => info)
|
|
28
28
|
|
|
29
29
|
type commandGenerator = payload => Effect.t<CommandTopic.commandOutcome, unit, unit>
|
|
30
|
-
type effectEventHandler<'context> = Runtime.
|
|
30
|
+
type effectEventHandler<'context> = Runtime.effectInvocationHandler<
|
|
31
31
|
payload,
|
|
32
32
|
'context,
|
|
33
33
|
CommandTopic.commandOutcome,
|
|
@@ -15,7 +15,12 @@ module type Resolvers = {
|
|
|
15
15
|
type runtimeParts
|
|
16
16
|
|
|
17
17
|
let handleResolversEvent: CommandGenerator.commandGenerator => Pulumi.Output.t<
|
|
18
|
-
Runtime.
|
|
18
|
+
Runtime.effectInvocationHandler<
|
|
19
|
+
CommandGenerator.payload,
|
|
20
|
+
'context,
|
|
21
|
+
CommandTopic.commandOutcome,
|
|
22
|
+
unit,
|
|
23
|
+
>,
|
|
19
24
|
>
|
|
20
25
|
|
|
21
26
|
let make: resolversMaker<api, runtimeParts>
|
|
@@ -48,12 +48,12 @@ module type T = {
|
|
|
48
48
|
let makeHandler: (
|
|
49
49
|
~commandTopic: component,
|
|
50
50
|
~commandsHandler: commandsHandler,
|
|
51
|
-
) => Pulumi.Output.t<Runtime.
|
|
51
|
+
) => Pulumi.Output.t<Runtime.effectInvocationHandler<callbackEvent, 'context, unit, string>>
|
|
52
52
|
|
|
53
53
|
// Returns the filtering handler output for runtime connection
|
|
54
54
|
// Registers the handler with the channel's event routing
|
|
55
55
|
let makeFilteringHandler: component => Pulumi.Output.t<
|
|
56
|
-
Runtime.
|
|
56
|
+
Runtime.effectInvocationHandler<callbackEvent, 'context, unit, string>,
|
|
57
57
|
>
|
|
58
58
|
|
|
59
59
|
let make: (
|
|
@@ -23,6 +23,8 @@ function filter(allCommandTopics, names) {
|
|
|
23
23
|
|
|
24
24
|
let componentType = "CommandTopic";
|
|
25
25
|
|
|
26
|
+
let refusalCauseToGraphQL = CommandTopic_Helpers$ReventlessCore.refusalCauseToGraphQL;
|
|
27
|
+
|
|
26
28
|
let commandOutcomeHook = CommandTopic_Helpers$ReventlessCore.commandOutcomeHook;
|
|
27
29
|
|
|
28
30
|
let registerCommandOutcome = CommandTopic_Helpers$ReventlessCore.registerCommandOutcome;
|
|
@@ -60,6 +62,7 @@ let getHandlers = CommandTopic_Helpers$ReventlessCore.getHandlers;
|
|
|
60
62
|
export {
|
|
61
63
|
componentType,
|
|
62
64
|
NotPublishedToChannel,
|
|
65
|
+
refusalCauseToGraphQL,
|
|
63
66
|
commandOutcomeHook,
|
|
64
67
|
registerCommandOutcome,
|
|
65
68
|
clearCommandOutcome,
|
|
@@ -12,7 +12,7 @@ and channel<'callbackEvent, 'context, 'channelParts, 'runtimeParts> = {
|
|
|
12
12
|
publishJsonsStream: Pulumi.Output.t<ReventlessInfra.CommandTopic.publishJsonsStream>,
|
|
13
13
|
publishJsonsAndWait: Pulumi.Output.t<option<CommandTopic.publishJsonsAndWait>>,
|
|
14
14
|
handleChannelEvent: CommandTopic.jsonCommandsHandler => Pulumi.Output.t<
|
|
15
|
-
Runtime.
|
|
15
|
+
Runtime.effectInvocationHandler<'callbackEvent, 'context, unit, string>,
|
|
16
16
|
>,
|
|
17
17
|
connect: connect<'callbackEvent, 'context, 'channelParts, 'runtimeParts>,
|
|
18
18
|
}
|
|
@@ -31,6 +31,14 @@ own what the command acts on (`OwnerScope.decideActing`).
|
|
|
31
31
|
*/
|
|
32
32
|
type refusalCause = DomainRejection | InfrastructureFailure | AccessRefusal
|
|
33
33
|
|
|
34
|
+
/** The `CommandRejectionCause` enum value a client sees for a cause. */
|
|
35
|
+
let refusalCauseToGraphQL = (cause: refusalCause): string =>
|
|
36
|
+
switch cause {
|
|
37
|
+
| DomainRejection => "DOMAIN"
|
|
38
|
+
| AccessRefusal => "ACCESS"
|
|
39
|
+
| InfrastructureFailure => "INFRASTRUCTURE"
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
/**
|
|
35
43
|
What became of one command. Constructors are prefixed to stay clear of `commandOutcome`'s
|
|
36
44
|
`Accepted`/`Rejected` below — that type is the producer-facing result shape (and the GraphQL
|
|
@@ -91,10 +99,9 @@ let fireCommandOutcome = (~component: string, ~reference: string, outcome: obser
|
|
|
91
99
|
// Callback implementations call reportAccepted during inline dispatch.
|
|
92
100
|
let acceptedResultChannel: ref<option<(string, acceptedResult) => unit>> = ref(None)
|
|
93
101
|
|
|
94
|
-
// Sibling side-channel for
|
|
95
|
-
// `
|
|
96
|
-
|
|
97
|
-
let rejectedResultChannel: ref<option<(string, rejectedResult) => unit>> = ref(None)
|
|
102
|
+
// Sibling side-channel for rejections, with their cause. Read by runInlineAndCollect
|
|
103
|
+
// to build `Rejected` outcomes that carry the real error code, detail and cause.
|
|
104
|
+
let rejectedResultChannel: ref<option<(string, rejectedResult, refusalCause) => unit>> = ref(None)
|
|
98
105
|
|
|
99
106
|
/**
|
|
100
107
|
Turn an `acting` verdict into the refusal a command handler reports, or `None` to let
|
|
@@ -112,7 +119,10 @@ let ownershipRefusal = (
|
|
|
112
119
|
~command: string,
|
|
113
120
|
): option<ownershipRefusal> => {
|
|
114
121
|
let refuse = (~isDefect=false, why) => Some({
|
|
115
|
-
rejected: {
|
|
122
|
+
rejected: {
|
|
123
|
+
errorCode: Reventless.CommandOutcome.code(Forbidden),
|
|
124
|
+
errorDetail: `${component}.${command}: ${why}`,
|
|
125
|
+
},
|
|
116
126
|
isDefect,
|
|
117
127
|
})
|
|
118
128
|
switch verdict {
|
|
@@ -148,7 +158,7 @@ let reportRejected = (
|
|
|
148
158
|
reference: string,
|
|
149
159
|
result: rejectedResult,
|
|
150
160
|
) => {
|
|
151
|
-
rejectedResultChannel.contents->Option.forEach(cb => cb(reference, result))
|
|
161
|
+
rejectedResultChannel.contents->Option.forEach(cb => cb(reference, result, cause))
|
|
152
162
|
fireCommandOutcome(
|
|
153
163
|
~component,
|
|
154
164
|
~reference,
|
|
@@ -160,7 +170,7 @@ let reportRejected = (
|
|
|
160
170
|
// without importing Adapter.res → @pulumi/pulumi. Re-exported by CommandTopic via include.
|
|
161
171
|
type commandOutcome =
|
|
162
172
|
| Accepted({msgId: string, entityId?: string, eventCount: int})
|
|
163
|
-
| Rejected({msgId: string, errorCode: string, errorDetail: option<string
|
|
173
|
+
| Rejected({msgId: string, errorCode: string, errorDetail: option<string>, cause: refusalCause})
|
|
164
174
|
| Pending({msgId: string})
|
|
165
175
|
|
|
166
176
|
// Serialise a commandOutcome to the JSON shape the CommandResult GraphQL union
|
|
@@ -179,13 +189,14 @@ let commandOutcomeToJson = (outcome: commandOutcome): JSON.t =>
|
|
|
179
189
|
("eventCount", JSON.Number(eventCount->Int.toFloat)),
|
|
180
190
|
]),
|
|
181
191
|
)
|
|
182
|
-
| Rejected({msgId, errorCode, errorDetail}) =>
|
|
192
|
+
| Rejected({msgId, errorCode, errorDetail, cause}) =>
|
|
183
193
|
JSON.Object(
|
|
184
194
|
Dict.fromArray([
|
|
185
195
|
("__typename", JSON.String("CommandRejected")),
|
|
186
196
|
("msgId", JSON.String(msgId)),
|
|
187
197
|
("errorCode", JSON.String(errorCode)),
|
|
188
198
|
("errorDetail", errorDetail->Option.map(s => JSON.String(s))->Option.getOr(JSON.Null)),
|
|
199
|
+
("cause", JSON.String(refusalCauseToGraphQL(cause))),
|
|
189
200
|
]),
|
|
190
201
|
)
|
|
191
202
|
| Pending({msgId}) =>
|
|
@@ -222,27 +233,34 @@ let runInlineAndCollect = async (
|
|
|
222
233
|
item
|
|
223
234
|
})
|
|
224
235
|
let acceptedResults: dict<acceptedResult> = Dict.make()
|
|
225
|
-
let rejectedResults: dict<rejectedResult> = Dict.make()
|
|
236
|
+
let rejectedResults: dict<(rejectedResult, refusalCause)> = Dict.make()
|
|
226
237
|
acceptedResultChannel.contents = Some(
|
|
227
238
|
(reference, result) => acceptedResults->Dict.set(reference, result),
|
|
228
239
|
)
|
|
229
240
|
rejectedResultChannel.contents = Some(
|
|
230
|
-
(reference, result) => rejectedResults->Dict.set(reference, result),
|
|
241
|
+
(reference, result, cause) => rejectedResults->Dict.set(reference, (result, cause)),
|
|
231
242
|
)
|
|
232
243
|
let results = await handleCmds(Stream.fromIterable(items))->Effect.runPromise
|
|
233
244
|
acceptedResultChannel.contents = None
|
|
234
245
|
rejectedResultChannel.contents = None
|
|
235
246
|
jsons->Array.mapWithIndex((cmdJson, i) => {
|
|
236
247
|
let msgId = cmdJson.meta.msgId
|
|
237
|
-
//
|
|
238
|
-
//
|
|
248
|
+
// A reported rejection takes precedence over both Accepted and the
|
|
249
|
+
// synthesized "Conflict" — it carries the most informative error.
|
|
239
250
|
switch rejectedResults->Dict.get(msgId) {
|
|
240
|
-
| Some({errorCode, errorDetail}) =>
|
|
251
|
+
| Some(({errorCode, errorDetail}, cause)) =>
|
|
241
252
|
let detail = errorDetail == "" ? None : Some(errorDetail)
|
|
242
|
-
Rejected({msgId, errorCode, errorDetail: detail})
|
|
253
|
+
Rejected({msgId, errorCode, errorDetail: detail, cause})
|
|
243
254
|
| None =>
|
|
244
255
|
switch results->Array.get(i) {
|
|
245
|
-
|
|
256
|
+
// The handler failed outright: the platform, not the model.
|
|
257
|
+
| Some(Error(msg)) =>
|
|
258
|
+
Rejected({
|
|
259
|
+
msgId,
|
|
260
|
+
errorCode: Reventless.CommandOutcome.code(Conflict),
|
|
261
|
+
errorDetail: Some(msg),
|
|
262
|
+
cause: InfrastructureFailure,
|
|
263
|
+
})
|
|
246
264
|
| _ =>
|
|
247
265
|
let ar = acceptedResults->Dict.get(msgId)->Option.getOr({eventCount: 0})
|
|
248
266
|
switch ar.entityId {
|
|
@@ -8,8 +8,20 @@ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/component
|
|
|
8
8
|
import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
|
|
9
9
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
10
10
|
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
11
|
+
import * as CommandOutcome$Reventless from "@reventlessdev/reventless-spec/src/components/CommandOutcome.res.mjs";
|
|
11
12
|
import * as EffectLogger$ReventlessCore from "../../util/EffectLogger.res.mjs";
|
|
12
13
|
|
|
14
|
+
function refusalCauseToGraphQL(cause) {
|
|
15
|
+
switch (cause) {
|
|
16
|
+
case "DomainRejection" :
|
|
17
|
+
return "DOMAIN";
|
|
18
|
+
case "InfrastructureFailure" :
|
|
19
|
+
return "INFRASTRUCTURE";
|
|
20
|
+
case "AccessRefusal" :
|
|
21
|
+
return "ACCESS";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
let commandOutcomeHook = {
|
|
14
26
|
contents: undefined
|
|
15
27
|
};
|
|
@@ -51,7 +63,7 @@ function ownershipRefusal(verdict, component, command) {
|
|
|
51
63
|
let isDefect = isDefectOpt !== undefined ? isDefectOpt : false;
|
|
52
64
|
return {
|
|
53
65
|
rejected: {
|
|
54
|
-
errorCode: "Forbidden",
|
|
66
|
+
errorCode: CommandOutcome$Reventless.code("Forbidden"),
|
|
55
67
|
errorDetail: component + `.` + command + `: ` + why
|
|
56
68
|
},
|
|
57
69
|
isDefect: isDefect
|
|
@@ -84,7 +96,7 @@ function reportAccepted(component, reference, result) {
|
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
function reportRejected(component, cause, reference, result) {
|
|
87
|
-
Stdlib_Option.forEach(rejectedResultChannel.contents, cb => cb(reference, result));
|
|
99
|
+
Stdlib_Option.forEach(rejectedResultChannel.contents, cb => cb(reference, result, cause));
|
|
88
100
|
fireCommandOutcome(component, reference, {
|
|
89
101
|
TAG: "OutcomeRejected",
|
|
90
102
|
errorCode: result.errorCode,
|
|
@@ -131,6 +143,10 @@ function commandOutcomeToJson(outcome) {
|
|
|
131
143
|
[
|
|
132
144
|
"errorDetail",
|
|
133
145
|
Stdlib_Option.getOr(Stdlib_Option.map(outcome.errorDetail, s => (s)), null)
|
|
146
|
+
],
|
|
147
|
+
[
|
|
148
|
+
"cause",
|
|
149
|
+
refusalCauseToGraphQL(outcome.cause)
|
|
134
150
|
]
|
|
135
151
|
]);
|
|
136
152
|
case "Pending" :
|
|
@@ -177,8 +193,11 @@ async function runInlineAndCollect(jsons, handleCmds) {
|
|
|
177
193
|
acceptedResultChannel.contents = (reference, result) => {
|
|
178
194
|
acceptedResults[reference] = result;
|
|
179
195
|
};
|
|
180
|
-
rejectedResultChannel.contents = (reference, result) => {
|
|
181
|
-
rejectedResults[reference] =
|
|
196
|
+
rejectedResultChannel.contents = (reference, result, cause) => {
|
|
197
|
+
rejectedResults[reference] = [
|
|
198
|
+
result,
|
|
199
|
+
cause
|
|
200
|
+
];
|
|
182
201
|
};
|
|
183
202
|
let results = await Effect.runPromise(handleCmds(Stream.fromIterable(items)));
|
|
184
203
|
acceptedResultChannel.contents = undefined;
|
|
@@ -187,22 +206,25 @@ async function runInlineAndCollect(jsons, handleCmds) {
|
|
|
187
206
|
let msgId = cmdJson.meta.msgId;
|
|
188
207
|
let match = rejectedResults[msgId];
|
|
189
208
|
if (match !== undefined) {
|
|
190
|
-
let
|
|
209
|
+
let match$1 = match[0];
|
|
210
|
+
let errorDetail = match$1.errorDetail;
|
|
191
211
|
let detail = errorDetail === "" ? undefined : errorDetail;
|
|
192
212
|
return {
|
|
193
213
|
TAG: "Rejected",
|
|
194
214
|
msgId: msgId,
|
|
195
|
-
errorCode: match.errorCode,
|
|
196
|
-
errorDetail: detail
|
|
215
|
+
errorCode: match$1.errorCode,
|
|
216
|
+
errorDetail: detail,
|
|
217
|
+
cause: match[1]
|
|
197
218
|
};
|
|
198
219
|
}
|
|
199
|
-
let match$
|
|
200
|
-
if (match$
|
|
220
|
+
let match$2 = results[i];
|
|
221
|
+
if (match$2 !== undefined && match$2.TAG !== "Ok") {
|
|
201
222
|
return {
|
|
202
223
|
TAG: "Rejected",
|
|
203
224
|
msgId: msgId,
|
|
204
|
-
errorCode: "Conflict",
|
|
205
|
-
errorDetail: match$
|
|
225
|
+
errorCode: CommandOutcome$Reventless.code("Conflict"),
|
|
226
|
+
errorDetail: match$2._0,
|
|
227
|
+
cause: "InfrastructureFailure"
|
|
206
228
|
};
|
|
207
229
|
}
|
|
208
230
|
let ar = Stdlib_Option.getOr(acceptedResults[msgId], {
|
|
@@ -249,6 +271,7 @@ function getHandlers(typeName) {
|
|
|
249
271
|
}
|
|
250
272
|
|
|
251
273
|
export {
|
|
274
|
+
refusalCauseToGraphQL,
|
|
252
275
|
commandOutcomeHook,
|
|
253
276
|
registerCommandOutcome,
|
|
254
277
|
clearCommandOutcome,
|
|
@@ -24,7 +24,7 @@ module type T = {
|
|
|
24
24
|
let makeHandler: (
|
|
25
25
|
~eventCollector: component,
|
|
26
26
|
~jsonEventsHandler: jsonEventsHandler,
|
|
27
|
-
) => Pulumi.Output.t<Runtime.
|
|
27
|
+
) => Pulumi.Output.t<Runtime.effectInvocationHandler<callbackEvent, 'context, unit, string>>
|
|
28
28
|
|
|
29
29
|
let make: (
|
|
30
30
|
~name: string,
|
|
@@ -3,7 +3,7 @@ type channel<'callbackEvent, 'context, 'channelParts> = {
|
|
|
3
3
|
resources: array<ReventlessInfra.Adapter.resource>,
|
|
4
4
|
enqueueEvent: Pulumi.Output.t<EventCollector.enqueueEvent>,
|
|
5
5
|
handleChannelEvent: EventCollector.jsonEventsHandler => Pulumi.Output.t<
|
|
6
|
-
Runtime.
|
|
6
|
+
Runtime.effectInvocationHandler<'callbackEvent, 'context, unit, string>,
|
|
7
7
|
>,
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -19,7 +19,7 @@ module type T = {
|
|
|
19
19
|
~id: string,
|
|
20
20
|
~timeout: int,
|
|
21
21
|
~publishToPluginExtensionPoint: CommandTopic.publishJsons,
|
|
22
|
-
) => Pulumi.Output.t<Runtime.
|
|
22
|
+
) => Pulumi.Output.t<Runtime.invocationHandler<unit, 'context, unit>>
|
|
23
23
|
|
|
24
24
|
let make: (~name: string, ~opts: Pulumi.ComponentResource.options=?) => component
|
|
25
25
|
}
|
|
@@ -19,6 +19,17 @@ type auditRow = {
|
|
|
19
19
|
door?: ReventlessInfra.InboundTranslationSlice.door,
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** A refused caller is access, a failed publish the platform; anything else is
|
|
23
|
+
the translation refusing its input. */
|
|
24
|
+
let rejectionCauseOf = (errorCode: string): CommandTopic_Helpers.refusalCause =>
|
|
25
|
+
if errorCode == Reventless.CommandOutcome.code(Forbidden) {
|
|
26
|
+
AccessRefusal
|
|
27
|
+
} else if errorCode == Reventless.CommandOutcome.code(PublishFailed) {
|
|
28
|
+
InfrastructureFailure
|
|
29
|
+
} else {
|
|
30
|
+
DomainRejection
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
/** The `CommandResult` a `receive` outcome answers with: the first target as
|
|
23
34
|
`entityId` (omitted when nothing was produced), the fan-out as `eventCount`. */
|
|
24
35
|
let receiveResultToOutcome = (
|
|
@@ -31,10 +42,12 @@ let receiveResultToOutcome = (
|
|
|
31
42
|
| None => Accepted({msgId: requestId, eventCount: commandCount})
|
|
32
43
|
}
|
|
33
44
|
| Error({requestId, error, ?errorCode}) =>
|
|
45
|
+
let errorCode = errorCode->Option.getOr(Reventless.CommandOutcome.code(TranslationFailed))
|
|
34
46
|
Rejected({
|
|
35
47
|
msgId: requestId,
|
|
36
|
-
errorCode
|
|
48
|
+
errorCode,
|
|
37
49
|
errorDetail: Some(error),
|
|
50
|
+
cause: rejectionCauseOf(errorCode),
|
|
38
51
|
})
|
|
39
52
|
}
|
|
40
53
|
|
|
@@ -216,7 +229,7 @@ module Make = (
|
|
|
216
229
|
)
|
|
217
230
|
) =>
|
|
218
231
|
fail(
|
|
219
|
-
~errorCode=
|
|
232
|
+
~errorCode=Reventless.CommandOutcome.code(Forbidden),
|
|
220
233
|
`${Spec.name}: the caller is not authorized for every command this input translates into`,
|
|
221
234
|
)
|
|
222
235
|
| Ok([]) => succeed([])
|
|
@@ -225,7 +238,11 @@ module Make = (
|
|
|
225
238
|
await publishJsons(msgs)
|
|
226
239
|
succeed(pairs->Array.map(((targetId, _)) => targetId))
|
|
227
240
|
} catch {
|
|
228
|
-
| exn =>
|
|
241
|
+
| exn =>
|
|
242
|
+
fail(
|
|
243
|
+
~errorCode=Reventless.CommandOutcome.code(PublishFailed),
|
|
244
|
+
messageOf(exn, "publish failed"),
|
|
245
|
+
)
|
|
229
246
|
}
|
|
230
247
|
}
|
|
231
248
|
}
|
|
@@ -16,6 +16,7 @@ import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/U
|
|
|
16
16
|
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
17
17
|
import * as Message$ReventlessCore from "../../Message.res.mjs";
|
|
18
18
|
import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
|
|
19
|
+
import * as CommandOutcome$Reventless from "@reventlessdev/reventless-spec/src/components/CommandOutcome.res.mjs";
|
|
19
20
|
import * as EffectLogger$ReventlessCore from "../../util/EffectLogger.res.mjs";
|
|
20
21
|
import * as ApiNoApiHelpers$ReventlessCore from "../Api/ApiNoApiHelpers.res.mjs";
|
|
21
22
|
import * as InboundTranslationSlice$ReventlessInfra from "@reventlessdev/reventless-infra/src/components/InboundTranslationSlice.res.mjs";
|
|
@@ -36,6 +37,16 @@ let auditRowSchema = Sury.$schema(s => ({
|
|
|
36
37
|
door: s.m(Sury.$option(InboundTranslationSlice$ReventlessInfra.doorSchema))
|
|
37
38
|
}));
|
|
38
39
|
|
|
40
|
+
function rejectionCauseOf(errorCode) {
|
|
41
|
+
if (errorCode === CommandOutcome$Reventless.code("Forbidden")) {
|
|
42
|
+
return "AccessRefusal";
|
|
43
|
+
} else if (errorCode === CommandOutcome$Reventless.code("PublishFailed")) {
|
|
44
|
+
return "InfrastructureFailure";
|
|
45
|
+
} else {
|
|
46
|
+
return "DomainRejection";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
function receiveResultToOutcome(result) {
|
|
40
51
|
if (result.TAG === "Ok") {
|
|
41
52
|
let match = result._0;
|
|
@@ -58,11 +69,13 @@ function receiveResultToOutcome(result) {
|
|
|
58
69
|
}
|
|
59
70
|
}
|
|
60
71
|
let match$1 = result._0;
|
|
72
|
+
let errorCode = Stdlib_Option.getOr(match$1.errorCode, CommandOutcome$Reventless.code("TranslationFailed"));
|
|
61
73
|
return {
|
|
62
74
|
TAG: "Rejected",
|
|
63
75
|
msgId: match$1.requestId,
|
|
64
|
-
errorCode:
|
|
65
|
-
errorDetail: match$1.error
|
|
76
|
+
errorCode: errorCode,
|
|
77
|
+
errorDetail: match$1.error,
|
|
78
|
+
cause: rejectionCauseOf(errorCode)
|
|
66
79
|
};
|
|
67
80
|
}
|
|
68
81
|
|
|
@@ -213,7 +226,7 @@ function Make(Spec) {
|
|
|
213
226
|
}
|
|
214
227
|
let msgs = msg$1._0;
|
|
215
228
|
if (msgs.some(param => !callerAdmits(Authorization$Reventless.named(Spec.authorizationOf(Message$Reventless.variantNameOfJson(param.commandJson))), caller))) {
|
|
216
|
-
return fail("Forbidden", Spec.name + `: the caller is not authorized for every command this input translates into`);
|
|
229
|
+
return fail(CommandOutcome$Reventless.code("Forbidden"), Spec.name + `: the caller is not authorized for every command this input translates into`);
|
|
217
230
|
}
|
|
218
231
|
if (msgs.length === 0) {
|
|
219
232
|
return succeed([]);
|
|
@@ -223,7 +236,7 @@ function Make(Spec) {
|
|
|
223
236
|
return succeed(pairs.map(param => param[0]));
|
|
224
237
|
} catch (raw_exn$1) {
|
|
225
238
|
let exn$1 = Primitive_exceptions.internalToException(raw_exn$1);
|
|
226
|
-
return fail("PublishFailed", messageOf(exn$1, "publish failed"));
|
|
239
|
+
return fail(CommandOutcome$Reventless.code("PublishFailed"), messageOf(exn$1, "publish failed"));
|
|
227
240
|
}
|
|
228
241
|
};
|
|
229
242
|
let refuse = (body, errorCode, error) => failWith(Uuid.v4(), body, "Webhook", errorCode, error);
|
|
@@ -241,6 +254,7 @@ function Make(Spec) {
|
|
|
241
254
|
export {
|
|
242
255
|
auditStatusSchema,
|
|
243
256
|
auditRowSchema,
|
|
257
|
+
rejectionCauseOf,
|
|
244
258
|
receiveResultToOutcome,
|
|
245
259
|
requestIdOf,
|
|
246
260
|
takeAuditRow,
|
|
@@ -23,7 +23,7 @@ module type T = {
|
|
|
23
23
|
/** Bare event-variant names consumed across every projection mapping's source event
|
|
24
24
|
schema — names the actual events (so a DCB-log-sourced mapping is captured), unlike
|
|
25
25
|
`sourceNames` which only lists source aggregate / DCB-log names. Surfaced by
|
|
26
|
-
Plugin_Structure as `
|
|
26
|
+
Plugin_Structure as `readSideDef.consumedEventTypes`. */
|
|
27
27
|
let consumedEventNames: array<string>
|
|
28
28
|
let outputs: component => outputs
|
|
29
29
|
let operations: component => Pulumi.Output.t<operations>
|
|
@@ -478,10 +478,10 @@ module Make = (
|
|
|
478
478
|
// which disagreed with the backends' casing).
|
|
479
479
|
let (errorCode, errDetail) = switch err {
|
|
480
480
|
| ReventlessInfra.DcbEventLog.Conflict => (
|
|
481
|
-
|
|
481
|
+
Reventless.CommandOutcome.code(Conflict),
|
|
482
482
|
"conflict: condition check failed",
|
|
483
483
|
)
|
|
484
|
-
| StorageFailure(msg) => (
|
|
484
|
+
| StorageFailure(msg) => (Reventless.CommandOutcome.code(AppendFailed), msg)
|
|
485
485
|
}
|
|
486
486
|
if retries > 0 {
|
|
487
487
|
// Re-seed from the just-read snapshot so the retry reads only
|
|
@@ -504,7 +504,7 @@ module Make = (
|
|
|
504
504
|
// A surfaced Conflict means the 3-retry loop could not absorb
|
|
505
505
|
// the contention — the operator signal for "this slice is hot"
|
|
506
506
|
// (consider sharding / async — Issue 10, §4 of the analysis).
|
|
507
|
-
if errorCode ==
|
|
507
|
+
if errorCode == Reventless.CommandOutcome.code(Conflict) {
|
|
508
508
|
Metrics.emitCount(~metric="AppendConflict", ~slice=Spec.name)
|
|
509
509
|
}
|
|
510
510
|
// Both arms here are storage, not the model: a surfaced Conflict is
|
|
@@ -17,6 +17,7 @@ import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types
|
|
|
17
17
|
import * as Message$ReventlessCore from "../../Message.res.mjs";
|
|
18
18
|
import * as Metrics$ReventlessCore from "../../util/Metrics.res.mjs";
|
|
19
19
|
import * as LogFormat$ReventlessCore from "../../util/LogFormat.res.mjs";
|
|
20
|
+
import * as CommandOutcome$Reventless from "@reventlessdev/reventless-spec/src/components/CommandOutcome.res.mjs";
|
|
20
21
|
import * as EffectLogger$ReventlessCore from "../../util/EffectLogger.res.mjs";
|
|
21
22
|
import * as CommandTopic_Helpers$ReventlessCore from "../CommandTopic/CommandTopic_Helpers.res.mjs";
|
|
22
23
|
|
|
@@ -311,10 +312,10 @@ function Make(Spec) {
|
|
|
311
312
|
let err = appendResult._0;
|
|
312
313
|
let match;
|
|
313
314
|
match = typeof err !== "object" ? [
|
|
314
|
-
"Conflict",
|
|
315
|
+
CommandOutcome$Reventless.code("Conflict"),
|
|
315
316
|
"conflict: condition check failed"
|
|
316
317
|
] : [
|
|
317
|
-
"AppendFailed",
|
|
318
|
+
CommandOutcome$Reventless.code("AppendFailed"),
|
|
318
319
|
err._0
|
|
319
320
|
];
|
|
320
321
|
let errDetail = match[1];
|
|
@@ -329,7 +330,7 @@ function Make(Spec) {
|
|
|
329
330
|
return Effect.flatMap(EffectLogger$ReventlessCore.logWarn(comp, undefined, `append failed (retrying ` + ((3 - retries | 0) + 1 | 0).toString() + `/` + (3).toString() + `): ` + errDetail), () => attempt(retries - 1 | 0));
|
|
330
331
|
} else {
|
|
331
332
|
cacheInvalidate();
|
|
332
|
-
if (errorCode === "Conflict") {
|
|
333
|
+
if (errorCode === CommandOutcome$Reventless.code("Conflict")) {
|
|
333
334
|
Metrics$ReventlessCore.emitCount("AppendConflict", Spec.name, undefined);
|
|
334
335
|
}
|
|
335
336
|
CommandTopic_Helpers$ReventlessCore.reportRejected(Spec.name, "InfrastructureFailure", cmdJson.meta.msgId, {
|
|
@@ -109,7 +109,7 @@ function Make(RuntimeEnvironment) {
|
|
|
109
109
|
event: Primitive_option.valFromOption(event),
|
|
110
110
|
meta: meta,
|
|
111
111
|
recordedAt: recordedAt
|
|
112
|
-
}).map(__x => Projection$Reventless.mapActionId(__x, Spec.
|
|
112
|
+
}).map(__x => Projection$Reventless.mapActionId(__x, Spec.Id.toString, Spec.Id.makeFromString));
|
|
113
113
|
} catch (raw_exn) {
|
|
114
114
|
let exn$1 = Primitive_exceptions.internalToException(raw_exn);
|
|
115
115
|
let errMsg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn$1), Stdlib_JsExn.message), "unknown");
|
|
@@ -44,8 +44,8 @@ module Make = (
|
|
|
44
44
|
Projection.project({event, meta: raw.meta, recordedAt: raw.recordedAt})->Array.map(
|
|
45
45
|
Reventless.Projection.mapActionId(
|
|
46
46
|
_,
|
|
47
|
-
~to_=Spec.
|
|
48
|
-
~from=Spec.
|
|
47
|
+
~to_=Spec.Id.toString,
|
|
48
|
+
~from=Spec.Id.makeFromString,
|
|
49
49
|
),
|
|
50
50
|
)
|
|
51
51
|
let actionsStr = LogFormat.actionNames(actions)
|
|
@@ -33,7 +33,7 @@ function Make(Spec) {
|
|
|
33
33
|
event: event,
|
|
34
34
|
meta: raw.meta,
|
|
35
35
|
recordedAt: raw.recordedAt
|
|
36
|
-
}).map(__x => Projection$Reventless.mapActionId(__x, Spec.
|
|
36
|
+
}).map(__x => Projection$Reventless.mapActionId(__x, Spec.Id.toString, Spec.Id.makeFromString));
|
|
37
37
|
let actionsStr = LogFormat$ReventlessCore.actionNames(actions);
|
|
38
38
|
Effect.runSync(EffectLogger$ReventlessCore.logInfo(comp, raw.data, `handling event ` + idx.contents.toString() + `/` + count + `: ` + LogFormat$ReventlessCore.bold(raw.eventType) + `(` + id + `) ` + actionsStr));
|
|
39
39
|
allActions.push(...actions.map(__x => Projection$ReventlessCore.rewriteAction(__x, raw.meta.time, Spec.stateSchema)));
|
|
@@ -24,7 +24,7 @@ module type Bucket = {
|
|
|
24
24
|
type bucketParts
|
|
25
25
|
|
|
26
26
|
let connect: connect<bucketParts, runtimeParts>
|
|
27
|
-
let makeHandler: Task.bucketCallback => Runtime.
|
|
27
|
+
let makeHandler: Task.bucketCallback => Runtime.invocationHandler<
|
|
28
28
|
callbackEvent,
|
|
29
29
|
context,
|
|
30
30
|
array<Task.taskAction>,
|
|
@@ -97,7 +97,7 @@ module Make = (
|
|
|
97
97
|
): Task.T => {
|
|
98
98
|
module Spec = Spec
|
|
99
99
|
type component = Task.component
|
|
100
|
-
// type handler = Runtime.
|
|
100
|
+
// type handler = Runtime.invocationHandler<
|
|
101
101
|
// TaskRuntimeBuilder.callbackEvent,
|
|
102
102
|
// TaskRuntimeBuilder.context,
|
|
103
103
|
// array<Task.taskAction>,
|