@reventlessdev/reventless-core 3.0.0-alpha.215 → 3.0.0-alpha.217
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 +18 -0
- package/package.json +6 -6
- package/src/ResourceAttribution.res +60 -4
- package/src/ResourceAttribution.res.mjs +26 -0
- package/src/adapter/QueryInterception/QueryInterception.res +74 -0
- package/src/adapter/QueryInterception/QueryInterception.res.mjs +26 -0
- package/src/adapter/ResourceAttribution_Deploytime.res +43 -0
- package/src/adapter/ResourceAttribution_Deploytime.res.mjs +20 -0
- package/src/admin/Platform_Admin_Structure.res +4 -0
- package/src/admin/Platform_Admin_Structure.res.mjs +5 -0
- package/src/admin/Platform_ComponentDefinitionsApi.res +16 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +24 -1
- package/src/components/Aggregate/Aggregate_Callback.res +14 -2
- package/src/components/Aggregate/Aggregate_Callback.res.mjs +3 -3
- package/src/components/Builder_Helpers.res +21 -3
- package/src/components/Builder_Helpers.res.mjs +4 -3
- package/src/components/CommandTopic/CommandTopic.res.mjs +12 -0
- package/src/components/CommandTopic/CommandTopic_Helpers.res +88 -2
- package/src/components/CommandTopic/CommandTopic_Helpers.res.mjs +48 -2
- package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +4 -1
- package/src/components/ExtensionPoint/ExtensionPoint_Builder.res.mjs +2 -1
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +13 -1
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +4 -4
- package/src/plugin/component/Plugin_Builder.res +6 -1
- package/src/plugin/component/Plugin_Builder.res.mjs +4 -2
- package/src/plugin/component/Plugin_Helpers.res +7 -2
- package/src/plugin/component/Plugin_Helpers.res.mjs +4 -3
- package/src/plugin/component/Plugin_Structure.res +66 -51
- package/src/plugin/component/Plugin_Structure.res.mjs +61 -46
- package/tests/ResourceAttributionTest.res +115 -0
- package/tests/ResourceAttributionTest.res.mjs +106 -0
- package/tests/adapter/QueryInterceptionTest.res +55 -0
- package/tests/adapter/QueryInterceptionTest.res.mjs +49 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +21 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +21 -0
- package/tests/commandtopic/CommandOutcomeHookTest.res +258 -0
- package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +372 -0
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +9 -1
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res.mjs +3 -3
- package/tests/message/MessageTest.res +1 -0
- package/tests/message/MessageTest.res.mjs +1 -0
- package/tests/plugin/PluginStructureTest.res +27 -0
- package/tests/plugin/PluginStructureTest.res.mjs +27 -0
- package/tests/plugin/StateChangeSlice/PsShipOrder.res +3 -1
- package/tests/plugin/StateChangeSlice/PsShipOrder.res.mjs +7 -1
- package/tests/plugin/pluginDefinitionRequiredScalars.txt +8 -0
|
@@ -18,6 +18,71 @@ is the JSON-stringified error payload (or empty string for payload-less variants
|
|
|
18
18
|
*/
|
|
19
19
|
type rejectedResult = {errorCode: string, errorDetail: string}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
Why a command was refused.
|
|
23
|
+
|
|
24
|
+
`DomainRejection` is `Behavior.decide` returning `Error` — the model declining the command,
|
|
25
|
+
which is the model working. `InfrastructureFailure` is the decision never landing: an event-log
|
|
26
|
+
append that failed outright or exhausted its conflict retries. Both arrive on `reportRejected`,
|
|
27
|
+
so without this a consumer would read a broken event log as a business rejection.
|
|
28
|
+
*/
|
|
29
|
+
type refusalCause = DomainRejection | InfrastructureFailure
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
What became of one command. Constructors are prefixed to stay clear of `commandOutcome`'s
|
|
33
|
+
`Accepted`/`Rejected` below — that type is the producer-facing result shape (and the GraphQL
|
|
34
|
+
`CommandResult` union); this one is the observation, and carries the refusal cause it doesn't.
|
|
35
|
+
*/
|
|
36
|
+
type observedOutcome =
|
|
37
|
+
| OutcomeAccepted({entityId: option<string>, eventCount: int})
|
|
38
|
+
| OutcomeRejected({errorCode: string, errorDetail: string, cause: refusalCause})
|
|
39
|
+
|
|
40
|
+
/** `reference` is the command's `meta.msgId` on every current transport. */
|
|
41
|
+
type commandOutcomeReport = {
|
|
42
|
+
component: string,
|
|
43
|
+
reference: string,
|
|
44
|
+
outcome: observedOutcome,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type commandOutcomeHook = commandOutcomeReport => unit
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
Module-level outcome hook, in the shape the `RuntimeExtension` cold-start seam already reaches
|
|
51
|
+
— so an extension registers it from `onColdStart` alongside the interception and publish hooks,
|
|
52
|
+
and no new registration path is needed. None = no-op (default).
|
|
53
|
+
*/
|
|
54
|
+
let commandOutcomeHook: ref<option<commandOutcomeHook>> = ref(None)
|
|
55
|
+
|
|
56
|
+
let registerCommandOutcome = (hook: commandOutcomeHook) => {
|
|
57
|
+
commandOutcomeHook.contents = Some(hook)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let clearCommandOutcome = () => {
|
|
61
|
+
commandOutcomeHook.contents = None
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Fired from reportAccepted/reportRejected below, which both write-side kinds call
|
|
65
|
+
// unconditionally — so this sees fire-and-forget dispatches too, not just the ones a
|
|
66
|
+
// producer awaited on the side-channels.
|
|
67
|
+
//
|
|
68
|
+
// A throwing hook is logged and swallowed: the outcome it observes has already happened,
|
|
69
|
+
// and letting an observer fail a completed command would make an extension an outage
|
|
70
|
+
// (the same failure policy the cold-start seam settled on).
|
|
71
|
+
let fireCommandOutcome = (~component: string, ~reference: string, outcome: observedOutcome) =>
|
|
72
|
+
commandOutcomeHook.contents->Option.forEach(hook =>
|
|
73
|
+
switch hook({component, reference, outcome}) {
|
|
74
|
+
| () => ()
|
|
75
|
+
| exception err =>
|
|
76
|
+
EffectLogger.logError(
|
|
77
|
+
~comp=`CommandTopic(${component})`,
|
|
78
|
+
`command-outcome hook threw (ignored): ${err
|
|
79
|
+
->JsExn.fromException
|
|
80
|
+
->Option.flatMap(JsExn.message)
|
|
81
|
+
->Option.getOr("unknown")}`,
|
|
82
|
+
)->Effect.runSync
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
|
|
21
86
|
// Side-channel for publishJsonsAndWait result propagation.
|
|
22
87
|
// Set by runInlineAndCollect before invoking the handler; cleared after.
|
|
23
88
|
// Callback implementations call reportAccepted during inline dispatch.
|
|
@@ -28,11 +93,32 @@ let acceptedResultChannel: ref<option<(string, acceptedResult) => unit>> = ref(N
|
|
|
28
93
|
// `Rejected` outcomes that carry the real error code and detail.
|
|
29
94
|
let rejectedResultChannel: ref<option<(string, rejectedResult) => unit>> = ref(None)
|
|
30
95
|
|
|
31
|
-
|
|
96
|
+
// `~component` is the owning component's `Spec.name`: an outcome isn't interpretable without
|
|
97
|
+
// knowing which component produced it, and the side-channel (keyed per inline dispatch) never
|
|
98
|
+
// had to say. `~cause` is required rather than defaulted so a new rejection site has to state
|
|
99
|
+
// which kind it is instead of silently reporting infrastructure as a domain decision.
|
|
100
|
+
let reportAccepted = (~component: string, reference: string, result: acceptedResult) => {
|
|
32
101
|
acceptedResultChannel.contents->Option.forEach(cb => cb(reference, result))
|
|
102
|
+
fireCommandOutcome(
|
|
103
|
+
~component,
|
|
104
|
+
~reference,
|
|
105
|
+
OutcomeAccepted({entityId: result.entityId, eventCount: result.eventCount}),
|
|
106
|
+
)
|
|
107
|
+
}
|
|
33
108
|
|
|
34
|
-
let reportRejected = (
|
|
109
|
+
let reportRejected = (
|
|
110
|
+
~component: string,
|
|
111
|
+
~cause: refusalCause,
|
|
112
|
+
reference: string,
|
|
113
|
+
result: rejectedResult,
|
|
114
|
+
) => {
|
|
35
115
|
rejectedResultChannel.contents->Option.forEach(cb => cb(reference, result))
|
|
116
|
+
fireCommandOutcome(
|
|
117
|
+
~component,
|
|
118
|
+
~reference,
|
|
119
|
+
OutcomeRejected({errorCode: result.errorCode, errorDetail: result.errorDetail, cause}),
|
|
120
|
+
)
|
|
121
|
+
}
|
|
36
122
|
|
|
37
123
|
// Placed here (rather than CommandTopic.res) so runInlineAndCollect can use commandOutcome
|
|
38
124
|
// without importing Adapter.res → @pulumi/pulumi. Re-exported by CommandTopic via include.
|
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
4
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
6
|
import * as Effect from "effect/Effect";
|
|
6
7
|
import * as Stream from "effect/Stream";
|
|
7
8
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
8
9
|
import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
|
|
10
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
11
|
+
import * as EffectLogger$ReventlessCore from "../../util/EffectLogger.res.mjs";
|
|
12
|
+
|
|
13
|
+
let commandOutcomeHook = {
|
|
14
|
+
contents: undefined
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function registerCommandOutcome(hook) {
|
|
18
|
+
commandOutcomeHook.contents = hook;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function clearCommandOutcome() {
|
|
22
|
+
commandOutcomeHook.contents = undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function fireCommandOutcome(component, reference, outcome) {
|
|
26
|
+
Stdlib_Option.forEach(commandOutcomeHook.contents, hook => {
|
|
27
|
+
try {
|
|
28
|
+
hook({
|
|
29
|
+
component: component,
|
|
30
|
+
reference: reference,
|
|
31
|
+
outcome: outcome
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
} catch (raw_err) {
|
|
35
|
+
let err = Primitive_exceptions.internalToException(raw_err);
|
|
36
|
+
return Effect.runSync(EffectLogger$ReventlessCore.logError(`CommandTopic(` + component + `)`, undefined, `command-outcome hook threw (ignored): ` + Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(err), Stdlib_JsExn.message), "unknown")));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
9
40
|
|
|
10
41
|
let acceptedResultChannel = {
|
|
11
42
|
contents: undefined
|
|
@@ -15,12 +46,23 @@ let rejectedResultChannel = {
|
|
|
15
46
|
contents: undefined
|
|
16
47
|
};
|
|
17
48
|
|
|
18
|
-
function reportAccepted(reference, result) {
|
|
49
|
+
function reportAccepted(component, reference, result) {
|
|
19
50
|
Stdlib_Option.forEach(acceptedResultChannel.contents, cb => cb(reference, result));
|
|
51
|
+
fireCommandOutcome(component, reference, {
|
|
52
|
+
TAG: "OutcomeAccepted",
|
|
53
|
+
entityId: result.entityId,
|
|
54
|
+
eventCount: result.eventCount
|
|
55
|
+
});
|
|
20
56
|
}
|
|
21
57
|
|
|
22
|
-
function reportRejected(reference, result) {
|
|
58
|
+
function reportRejected(component, cause, reference, result) {
|
|
23
59
|
Stdlib_Option.forEach(rejectedResultChannel.contents, cb => cb(reference, result));
|
|
60
|
+
fireCommandOutcome(component, reference, {
|
|
61
|
+
TAG: "OutcomeRejected",
|
|
62
|
+
errorCode: result.errorCode,
|
|
63
|
+
errorDetail: result.errorDetail,
|
|
64
|
+
cause: cause
|
|
65
|
+
});
|
|
24
66
|
}
|
|
25
67
|
|
|
26
68
|
function commandOutcomeToJson(outcome) {
|
|
@@ -179,6 +221,10 @@ function getHandlers(typeName) {
|
|
|
179
221
|
}
|
|
180
222
|
|
|
181
223
|
export {
|
|
224
|
+
commandOutcomeHook,
|
|
225
|
+
registerCommandOutcome,
|
|
226
|
+
clearCommandOutcome,
|
|
227
|
+
fireCommandOutcome,
|
|
182
228
|
acceptedResultChannel,
|
|
183
229
|
rejectedResultChannel,
|
|
184
230
|
reportAccepted,
|
|
@@ -63,7 +63,10 @@ module Make = (
|
|
|
63
63
|
|
|
64
64
|
let (commandTopic, eventTopic, outgoingJsonEventsHandler) =
|
|
65
65
|
commandTopicResources
|
|
66
|
-
|
|
66
|
+
// The extension point's runtime and its event topic are both built in
|
|
67
|
+
// here, once the command topic's resources resolve — i.e. after the
|
|
68
|
+
// plugin's construct has returned. Reinstate its attribution.
|
|
69
|
+
->ResourceAttribution_Deploytime.flatMapAttributed(commandTopicResources => {
|
|
67
70
|
module ExtensionPointCallback = ExtensionPoint_Callback.Make(
|
|
68
71
|
{
|
|
69
72
|
let publishToAggregates = publishToAggregates
|
|
@@ -15,6 +15,7 @@ import * as EventTopic_Builder$ReventlessCore from "../EventTopic/EventTopic_Bui
|
|
|
15
15
|
import * as CommandTopic_Builder$ReventlessCore from "../CommandTopic/CommandTopic_Builder.res.mjs";
|
|
16
16
|
import * as ExtensionPoint_Callback$ReventlessCore from "./ExtensionPoint_Callback.res.mjs";
|
|
17
17
|
import * as ExtensionPoint_Operations$ReventlessCore from "./ExtensionPoint_Operations.res.mjs";
|
|
18
|
+
import * as ResourceAttribution_Deploytime$ReventlessCore from "../../adapter/ResourceAttribution_Deploytime.res.mjs";
|
|
18
19
|
|
|
19
20
|
function Make(Spec) {
|
|
20
21
|
return Mappings => (RuntimeEnvironment => (CommandTopicChannel => (EventTopicAdapter => (ExtensionPointRuntimeBuilder => (Defaults => {
|
|
@@ -42,7 +43,7 @@ function Make(Spec) {
|
|
|
42
43
|
}, opts);
|
|
43
44
|
let aggregateNames = Stdlib_Array.filterMap(Mappings.mappings, Mapping => Stdlib_Option.map(Mapping.mapOutgoingEvent, param => Mapping.delegateName));
|
|
44
45
|
let commandTopicResources = Adapter$ReventlessCore.resourcesToResolvedOutput(commandTopic.channel.resources);
|
|
45
|
-
let match = Output$Pulumi.unzip3(
|
|
46
|
+
let match = Output$Pulumi.unzip3(ResourceAttribution_Deploytime$ReventlessCore.flatMapAttributed(commandTopicResources, commandTopicResources => {
|
|
46
47
|
let ExtensionPointCallback = ExtensionPoint_Callback$ReventlessCore.Make({
|
|
47
48
|
publishToAggregates: publishToAggregates,
|
|
48
49
|
commandTopicResources: commandTopicResources,
|
|
@@ -322,6 +322,7 @@ module Make = (
|
|
|
322
322
|
// No append, but the read snapshot is valid — cache it for the next command.
|
|
323
323
|
cachePut((state, headPosition))
|
|
324
324
|
CommandTopic_Helpers.reportAccepted(
|
|
325
|
+
~component=Spec.name,
|
|
325
326
|
cmdJson.meta.msgId,
|
|
326
327
|
switch entityId {
|
|
327
328
|
| Some(eid) => {entityId: eid, eventCount: 0}
|
|
@@ -373,6 +374,7 @@ module Make = (
|
|
|
373
374
|
// next command's delta read picks our events up from `headPosition`.
|
|
374
375
|
cachePut((state, headPosition))
|
|
375
376
|
CommandTopic_Helpers.reportAccepted(
|
|
377
|
+
~component=Spec.name,
|
|
376
378
|
cmdJson.meta.msgId,
|
|
377
379
|
switch entityId {
|
|
378
380
|
| Some(eid) => {entityId: eid, eventCount: rawEvents->Array.length}
|
|
@@ -412,7 +414,12 @@ module Make = (
|
|
|
412
414
|
if errorCode == "Conflict" {
|
|
413
415
|
Metrics.emitCount(~metric="AppendConflict", ~slice=Spec.name)
|
|
414
416
|
}
|
|
417
|
+
// Both arms here are storage, not the model: a surfaced Conflict is
|
|
418
|
+
// contention the retry loop couldn't absorb, and AppendFailed is the
|
|
419
|
+
// store failing. `decide` had already said Ok.
|
|
415
420
|
CommandTopic_Helpers.reportRejected(
|
|
421
|
+
~component=Spec.name,
|
|
422
|
+
~cause=InfrastructureFailure,
|
|
416
423
|
cmdJson.meta.msgId,
|
|
417
424
|
{errorCode, errorDetail: errDetail},
|
|
418
425
|
)
|
|
@@ -433,7 +440,12 @@ module Make = (
|
|
|
433
440
|
payloadDict->Dict.toArray->Array.length == 0
|
|
434
441
|
? ""
|
|
435
442
|
: payloadDict->JSON.Encode.object->JSON.stringify
|
|
436
|
-
CommandTopic_Helpers.reportRejected(
|
|
443
|
+
CommandTopic_Helpers.reportRejected(
|
|
444
|
+
~component=Spec.name,
|
|
445
|
+
~cause=DomainRejection,
|
|
446
|
+
cmdJson.meta.msgId,
|
|
447
|
+
{errorCode, errorDetail},
|
|
448
|
+
)
|
|
437
449
|
EffectLogger.logError(
|
|
438
450
|
~comp,
|
|
439
451
|
`decide rejected: ${errorCode} ${errorDetail}`,
|
|
@@ -173,7 +173,7 @@ function Make(Spec) {
|
|
|
173
173
|
state,
|
|
174
174
|
headPosition
|
|
175
175
|
]);
|
|
176
|
-
CommandTopic_Helpers$ReventlessCore.reportAccepted(cmdJson.meta.msgId, entityId !== undefined ? ({
|
|
176
|
+
CommandTopic_Helpers$ReventlessCore.reportAccepted(Spec.name, cmdJson.meta.msgId, entityId !== undefined ? ({
|
|
177
177
|
entityId: entityId,
|
|
178
178
|
eventCount: 0
|
|
179
179
|
}) : ({
|
|
@@ -223,7 +223,7 @@ function Make(Spec) {
|
|
|
223
223
|
state,
|
|
224
224
|
headPosition
|
|
225
225
|
]);
|
|
226
|
-
CommandTopic_Helpers$ReventlessCore.reportAccepted(cmdJson.meta.msgId, entityId !== undefined ? ({
|
|
226
|
+
CommandTopic_Helpers$ReventlessCore.reportAccepted(Spec.name, cmdJson.meta.msgId, entityId !== undefined ? ({
|
|
227
227
|
entityId: entityId,
|
|
228
228
|
eventCount: rawEvents.length
|
|
229
229
|
}) : ({
|
|
@@ -257,7 +257,7 @@ function Make(Spec) {
|
|
|
257
257
|
if (errorCode === "Conflict") {
|
|
258
258
|
Metrics$ReventlessCore.emitCount("AppendConflict", Spec.name, undefined);
|
|
259
259
|
}
|
|
260
|
-
CommandTopic_Helpers$ReventlessCore.reportRejected(cmdJson.meta.msgId, {
|
|
260
|
+
CommandTopic_Helpers$ReventlessCore.reportRejected(Spec.name, "InfrastructureFailure", cmdJson.meta.msgId, {
|
|
261
261
|
errorCode: errorCode,
|
|
262
262
|
errorDetail: errDetail
|
|
263
263
|
});
|
|
@@ -277,7 +277,7 @@ function Make(Spec) {
|
|
|
277
277
|
let match = Message$ReventlessCore.splitMessage(errorJson);
|
|
278
278
|
let payloadDict = match[1];
|
|
279
279
|
let errorDetail = Object.entries(payloadDict).length === 0 ? "" : JSON.stringify(payloadDict);
|
|
280
|
-
CommandTopic_Helpers$ReventlessCore.reportRejected(cmdJson.meta.msgId, {
|
|
280
|
+
CommandTopic_Helpers$ReventlessCore.reportRejected(Spec.name, "DomainRejection", cmdJson.meta.msgId, {
|
|
281
281
|
errorCode: errorCode,
|
|
282
282
|
errorDetail: errorDetail
|
|
283
283
|
});
|
|
@@ -529,6 +529,7 @@ module Make = (
|
|
|
529
529
|
{
|
|
530
530
|
commandTypes: extractTypes(Scs.Spec.commandSchema),
|
|
531
531
|
eventTypes: extractEventTypes(Scs.Spec.eventSchema->S.castToUnknown),
|
|
532
|
+
errorTypes: extractTypes(Scs.Spec.errorSchema),
|
|
532
533
|
consumedEventTypes: extractEventTypes(Scs.Spec.consumedEventSchema->S.castToUnknown),
|
|
533
534
|
chapter: ?chapterFor(Scs.Spec.name),
|
|
534
535
|
},
|
|
@@ -647,7 +648,11 @@ module Make = (
|
|
|
647
648
|
queryEngine,
|
|
648
649
|
)
|
|
649
650
|
->Pulumi.Output.all6
|
|
650
|
-
|
|
651
|
+
// `applyAttributed`, not `apply`: this callback provisions real resources —
|
|
652
|
+
// extension points, tasks, the heartbeat, resolvers — and it runs after
|
|
653
|
+
// this construct has returned, when the ambient attribution context is
|
|
654
|
+
// already empty. It reinstates the plugin's context for the duration.
|
|
655
|
+
->ResourceAttribution_Deploytime.applyAttributed(((
|
|
651
656
|
(interstackAdminExtensionPoints, localAdminResolvedEP, _),
|
|
652
657
|
aggregateResources,
|
|
653
658
|
publishToAggregates,
|
|
@@ -36,6 +36,7 @@ import * as ResourceAttribution$ReventlessCore from "../../ResourceAttribution.r
|
|
|
36
36
|
import * as Plugin_EventQuerySchema$ReventlessCore from "./Plugin_EventQuerySchema.res.mjs";
|
|
37
37
|
import * as PluginExtensionPointSpec$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/PluginExtensionPointSpec.res.mjs";
|
|
38
38
|
import * as Plugin_SubscriptionSchema$ReventlessCore from "./Plugin_SubscriptionSchema.res.mjs";
|
|
39
|
+
import * as ResourceAttribution_Deploytime$ReventlessCore from "../../adapter/ResourceAttribution_Deploytime.res.mjs";
|
|
39
40
|
|
|
40
41
|
function Make(Spec) {
|
|
41
42
|
return ApiSpec => (FragmentProvider => (RuntimeEnvironment => (EventCollectorChannel => (QueryEngineAdapter => (PluginExtensionPointRemoteChannel => (HeartbeatRunner => (PluginRuntimeBuilder => (DcbEventLogStorage => (DcbEventTopicPublisher => (DcbCommandTopicChannel => (DcbCommandTopicChannelAsync => {
|
|
@@ -373,6 +374,7 @@ function Make(Spec) {
|
|
|
373
374
|
stateSchemas[Scs.Spec.name] = {
|
|
374
375
|
commandTypes: DcbTag$Reventless.extractAllVariantNames(Scs.Spec.commandSchema),
|
|
375
376
|
eventTypes: DcbTag$Reventless.extractVariantNames(Scs.Spec.eventSchema),
|
|
377
|
+
errorTypes: DcbTag$Reventless.extractAllVariantNames(Scs.Spec.errorSchema),
|
|
376
378
|
consumedEventTypes: DcbTag$Reventless.extractVariantNames(Scs.Spec.consumedEventSchema),
|
|
377
379
|
chapter: chapterByName[Scs.Spec.name]
|
|
378
380
|
};
|
|
@@ -427,7 +429,7 @@ function Make(Spec) {
|
|
|
427
429
|
let schemaPushed = pushSchema !== undefined ? pushSchema(extra$1, version, apiSchemaFragment) : Pulumi.output();
|
|
428
430
|
let apiSchemaFragmentPayload = Plugin_Helpers$ReventlessCore.offloadPayload(apiSchemaFragment, Plugin$Reventless.apiSchemaFragmentSchema, "pluginApiFragments");
|
|
429
431
|
let structurePayload = Stdlib_Option.map(pluginStructure, s => Plugin_Helpers$ReventlessCore.offloadPayload(s, Plugin$Reventless.pluginStructureSchema, "pluginStructures"));
|
|
430
|
-
let builderOutputs = Pulumi.all([
|
|
432
|
+
let builderOutputs = ResourceAttribution_Deploytime$ReventlessCore.applyAttributed(Pulumi.all([
|
|
431
433
|
Pulumi.all([
|
|
432
434
|
interstackAdminExtensionPoints,
|
|
433
435
|
localAdminResolvedEP,
|
|
@@ -438,7 +440,7 @@ function Make(Spec) {
|
|
|
438
440
|
Pulumi.all(Plugin_Helpers$ReventlessCore.publishToReadModels),
|
|
439
441
|
scheduler,
|
|
440
442
|
queryEngine
|
|
441
|
-
])
|
|
443
|
+
]), param => {
|
|
442
444
|
let queryEngine = param[5];
|
|
443
445
|
let scheduler = param[4];
|
|
444
446
|
let publishToReadModels = param[3];
|
|
@@ -383,7 +383,10 @@ let createConnectPluginExtension = (
|
|
|
383
383
|
~opts,
|
|
384
384
|
) =>
|
|
385
385
|
pluginDefinition
|
|
386
|
-
|
|
386
|
+
// Provisions the plugin's connect-extension and its event-collector runtime,
|
|
387
|
+
// so it needs the plugin's attribution reinstated — the apply runs after
|
|
388
|
+
// construct has emptied it.
|
|
389
|
+
->ResourceAttribution_Deploytime.applyAttributed(pluginDefinition => {
|
|
387
390
|
module ConnectPluginExtension = PluginConnectExtension_Builder.Make({
|
|
388
391
|
let pluginDefinition = pluginDefinition
|
|
389
392
|
let uiFragments = uiFragments
|
|
@@ -548,7 +551,9 @@ module MakeEventCollectorHelper = (
|
|
|
548
551
|
resources,
|
|
549
552
|
)
|
|
550
553
|
->Pulumi.Output.all5
|
|
551
|
-
|
|
554
|
+
// Builds the plugin's own event-collector runtime, so it needs the plugin's
|
|
555
|
+
// attribution reinstated — this callback runs after construct emptied it.
|
|
556
|
+
->ResourceAttribution_Deploytime.applyAttributed(((
|
|
552
557
|
pluginDefinition,
|
|
553
558
|
connectExtData,
|
|
554
559
|
extensionsHandlers,
|
|
@@ -58,6 +58,7 @@ import * as OutboundTranslationSlice$ReventlessInterop from "@reventlessdev/reve
|
|
|
58
58
|
import * as PluginExtensionPoint_Plugin$ReventlessCore from "../connect/PluginExtensionPoint_Plugin.res.mjs";
|
|
59
59
|
import * as PluginConnectExtension_Builder$ReventlessCore from "../connect/PluginConnectExtension_Builder.res.mjs";
|
|
60
60
|
import * as PluginConnectExtension_Mapping$ReventlessCore from "../connect/PluginConnectExtension_Mapping.res.mjs";
|
|
61
|
+
import * as ResourceAttribution_Deploytime$ReventlessCore from "../../adapter/ResourceAttribution_Deploytime.res.mjs";
|
|
61
62
|
|
|
62
63
|
let log = Logger$ReventlessCore.fromEnv();
|
|
63
64
|
|
|
@@ -224,7 +225,7 @@ function extractExtensionDefinitions(extensionsOutputs) {
|
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
function createConnectPluginExtension(pluginDefinition, uiFragments, publishToPluginExtensionPoint, publishToAggregates, readModelNamesForSourceName, publishToReadModels, queryEngine, opts) {
|
|
227
|
-
return Output$Pulumi.unzip(
|
|
228
|
+
return Output$Pulumi.unzip(ResourceAttribution_Deploytime$ReventlessCore.applyAttributed(pluginDefinition, pluginDefinition => {
|
|
228
229
|
let ConnectPluginExtension = PluginConnectExtension_Builder$ReventlessCore.Make({
|
|
229
230
|
pluginDefinition: pluginDefinition,
|
|
230
231
|
uiFragments: uiFragments
|
|
@@ -292,13 +293,13 @@ function MakeEventCollectorHelper(RuntimeEnvironment) {
|
|
|
292
293
|
param[0],
|
|
293
294
|
param[1]
|
|
294
295
|
]) : Pulumi.output(undefined);
|
|
295
|
-
return Pulumi.all([
|
|
296
|
+
return ResourceAttribution_Deploytime$ReventlessCore.applyAttributed(Pulumi.all([
|
|
296
297
|
pluginDefinition,
|
|
297
298
|
connectExtData,
|
|
298
299
|
Pulumi.all(extensionsHandlers),
|
|
299
300
|
Pulumi.all(extensionPointsHandlers),
|
|
300
301
|
resources
|
|
301
|
-
])
|
|
302
|
+
]), param => {
|
|
302
303
|
let extensionsHandlers = param[2];
|
|
303
304
|
let connectExtData = param[1];
|
|
304
305
|
let pluginDefinition = param[0];
|
|
@@ -157,6 +157,70 @@ let labelFieldsFromStateSchema = (
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
// ── Per-variant event / error field extraction (Phase 6.3) ───────────────────
|
|
161
|
+
// Mirrors toCommandDef/extractCommandDefs for emitted events: name (TAG const),
|
|
162
|
+
// payload JSON Schema, and cross-entity references. Module-level rather than
|
|
163
|
+
// inside `make` because the synthetic Platform_Admin structure — hand-written,
|
|
164
|
+
// because its components never pass through `make` — has to derive its defs the
|
|
165
|
+
// same way, and a second copy of this walk would be free to drift from this one.
|
|
166
|
+
|
|
167
|
+
let toEventDef = (v: S.t<unknown>): option<Reventless.Plugin.eventDef> => {
|
|
168
|
+
let mkDef = (~variantName, ~properties) => {
|
|
169
|
+
let references =
|
|
170
|
+
properties
|
|
171
|
+
->Dict.toArray
|
|
172
|
+
->Array.filterMap(((fieldName, fieldSchema)) =>
|
|
173
|
+
Reventless.Reference.getTarget(fieldSchema)->Option.map(target => (
|
|
174
|
+
{
|
|
175
|
+
Reventless.Plugin.fieldName,
|
|
176
|
+
entity: target.entity,
|
|
177
|
+
plugin: target.plugin,
|
|
178
|
+
}: Reventless.Plugin.fieldReference
|
|
179
|
+
))
|
|
180
|
+
)
|
|
181
|
+
({
|
|
182
|
+
Reventless.Plugin.name: variantName,
|
|
183
|
+
// Derived for the same reason as `commandDef.schema`: an event's field
|
|
184
|
+
// markers are the write side's half of the same vocabulary.
|
|
185
|
+
schema: v->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
|
|
186
|
+
references,
|
|
187
|
+
}: Reventless.Plugin.eventDef)
|
|
188
|
+
}
|
|
189
|
+
switch v {
|
|
190
|
+
| Object({properties}) =>
|
|
191
|
+
properties
|
|
192
|
+
->Dict.get("TAG")
|
|
193
|
+
->Option.flatMap(tagSchema =>
|
|
194
|
+
switch tagSchema {
|
|
195
|
+
| String({const: ?Some(variantName)}) => Some(mkDef(~variantName, ~properties))
|
|
196
|
+
| _ => None
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
// Payload-less event variants (`| Archived`) compile to a bare string literal.
|
|
200
|
+
// DCB-projection lookups can't WHERE-clause on them, so they stay out of
|
|
201
|
+
// producedEventTypes/consumedEventTypes; but the full `events` list carries them
|
|
202
|
+
// so the event graph can still draw the emitted (orphan) event node.
|
|
203
|
+
| String({const: ?Some(variantName)}) => Some(mkDef(~variantName, ~properties=Dict.make()))
|
|
204
|
+
| _ => None
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let extractEventDefs = (eventSchema: S.t<unknown>): array<Reventless.Plugin.eventDef> =>
|
|
209
|
+
switch eventSchema {
|
|
210
|
+
| Union({anyOf}) => anyOf->Array.filterMap(toEventDef)
|
|
211
|
+
| _ => toEventDef(eventSchema)->Option.mapOr([], def => [def])
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Declared errors walk identically to emitted events — an error variant is a
|
|
215
|
+
// variant, and the payload-less form (`| CategoryNotFound`) is the common case
|
|
216
|
+
// the bare-string branch above already handles. Reusing the walk rather than
|
|
217
|
+
// copying it keeps the two from drifting; only the def type differs, so that a
|
|
218
|
+
// consumer (and the SDL) can tell a refusal from a fact.
|
|
219
|
+
let extractErrorDefs = (errorSchema: S.t<unknown>): array<Reventless.Plugin.errorDef> =>
|
|
220
|
+
extractEventDefs(errorSchema)->Array.map(({name, schema, references}) => (
|
|
221
|
+
{name, schema, references}: Reventless.Plugin.errorDef
|
|
222
|
+
))
|
|
223
|
+
|
|
160
224
|
let make = (
|
|
161
225
|
type api role,
|
|
162
226
|
~name: string,
|
|
@@ -330,57 +394,6 @@ let make = (
|
|
|
330
394
|
->Option.mapOr([], def => [def])
|
|
331
395
|
}
|
|
332
396
|
|
|
333
|
-
// ── Per-variant event field extraction (Phase 6.3) ─────────────────────────
|
|
334
|
-
// Mirrors toCommandDef/extractCommandDefs for emitted events: name (TAG const),
|
|
335
|
-
// payload JSON Schema, and cross-entity references.
|
|
336
|
-
|
|
337
|
-
let toEventDef = (v: S.t<unknown>): option<Reventless.Plugin.eventDef> => {
|
|
338
|
-
let mkDef = (~variantName, ~properties) => {
|
|
339
|
-
let references =
|
|
340
|
-
properties
|
|
341
|
-
->Dict.toArray
|
|
342
|
-
->Array.filterMap(((fieldName, fieldSchema)) =>
|
|
343
|
-
Reventless.Reference.getTarget(fieldSchema)->Option.map(target => (
|
|
344
|
-
{
|
|
345
|
-
Reventless.Plugin.fieldName,
|
|
346
|
-
entity: target.entity,
|
|
347
|
-
plugin: target.plugin,
|
|
348
|
-
}: Reventless.Plugin.fieldReference
|
|
349
|
-
))
|
|
350
|
-
)
|
|
351
|
-
({
|
|
352
|
-
Reventless.Plugin.name: variantName,
|
|
353
|
-
// Derived for the same reason as `commandDef.schema` above: an event's
|
|
354
|
-
// field markers are the write side's half of the same vocabulary.
|
|
355
|
-
schema: v->SuryToJsonSchema.deriveObjectSchema->JSON.stringify,
|
|
356
|
-
references,
|
|
357
|
-
}: Reventless.Plugin.eventDef)
|
|
358
|
-
}
|
|
359
|
-
switch v {
|
|
360
|
-
| Object({properties}) =>
|
|
361
|
-
properties
|
|
362
|
-
->Dict.get("TAG")
|
|
363
|
-
->Option.flatMap(tagSchema =>
|
|
364
|
-
switch tagSchema {
|
|
365
|
-
| String({const: ?Some(variantName)}) => Some(mkDef(~variantName, ~properties))
|
|
366
|
-
| _ => None
|
|
367
|
-
}
|
|
368
|
-
)
|
|
369
|
-
// Payload-less event variants (`| Archived`) compile to a bare string literal.
|
|
370
|
-
// DCB-projection lookups can't WHERE-clause on them, so they stay out of
|
|
371
|
-
// producedEventTypes/consumedEventTypes; but the full `events` list carries them
|
|
372
|
-
// so the event graph can still draw the emitted (orphan) event node.
|
|
373
|
-
| String({const: ?Some(variantName)}) => Some(mkDef(~variantName, ~properties=Dict.make()))
|
|
374
|
-
| _ => None
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
let extractEventDefs = (eventSchema: S.t<unknown>): array<Reventless.Plugin.eventDef> =>
|
|
379
|
-
switch eventSchema {
|
|
380
|
-
| Union({anyOf}) => anyOf->Array.filterMap(toEventDef)
|
|
381
|
-
| _ => toEventDef(eventSchema)->Option.mapOr([], def => [def])
|
|
382
|
-
}
|
|
383
|
-
|
|
384
397
|
// ── Per-component event type extraction ────────────────────────────────────
|
|
385
398
|
|
|
386
399
|
let scsProduced =
|
|
@@ -704,6 +717,7 @@ let make = (
|
|
|
704
717
|
linkedViews: linkedSvsFor(produced),
|
|
705
718
|
consistencyRead: consistencyReadFor(consumed),
|
|
706
719
|
events: extractEventDefs(SCS.Spec.eventSchema->S.castToUnknown),
|
|
720
|
+
errors: extractErrorDefs(SCS.Spec.errorSchema->S.castToUnknown),
|
|
707
721
|
chapter: chapterOf(SCS.Spec.name),
|
|
708
722
|
}: Reventless.Plugin.writableDef)
|
|
709
723
|
})
|
|
@@ -726,6 +740,7 @@ let make = (
|
|
|
726
740
|
linkedViews: Array.concat(linkedSvsFor(produced), linkedReadModelsFor(A.Spec.name)),
|
|
727
741
|
consistencyRead: None,
|
|
728
742
|
events: extractEventDefs(A.Spec.eventSchema->S.castToUnknown),
|
|
743
|
+
errors: extractErrorDefs(A.Spec.errorSchema->S.castToUnknown),
|
|
729
744
|
chapter: chapterOf(A.Spec.name),
|
|
730
745
|
}: Reventless.Plugin.writableDef)
|
|
731
746
|
})
|