@reventlessdev/reventless-core 3.0.0-alpha.207 → 3.0.0-alpha.209
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 +17 -0
- package/package.json +4 -4
- package/src/admin/Platform_Admin.res.mjs +1 -1
- package/src/components/Aggregate/Aggregate_Builder.res +12 -6
- package/src/components/Aggregate/Aggregate_Builder.res.mjs +17 -15
- package/src/components/AutomationSlice/AutomationSlice_Callback.res +26 -2
- package/src/components/Dcb/Dcb_Builder.res +21 -1
- package/src/components/Dcb/Dcb_Builder.res.mjs +4 -2
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res +30 -2
- package/src/plugin/component/Plugin_Builder.res +3 -0
- package/src/plugin/component/Plugin_Builder.res.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.209 (2026-08-03)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** give slice command publishes a queue they can actually reach ([42fdb55](https://github.com/ReventlessDev/reventless-core/commit/42fdb55b751a197d586c49e3615ce12ffea42046))
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **dcb:** route an outbound slice's command to the aggregate it targets ([897b37b](https://github.com/ReventlessDev/reventless-core/commit/897b37b46a4e856567646dfdc2f0652e81cf7388))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# 3.0.0-alpha.208 (2026-08-03)
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **aws:** make the slice callback protocol one declaration, not three ([d49e543](https://github.com/ReventlessDev/reventless-core/commit/d49e543f4715c955c75cec8bd691ee0927b7e2f4))
|
|
21
|
+
|
|
22
|
+
|
|
6
23
|
# 3.0.0-alpha.207 (2026-08-03)
|
|
7
24
|
|
|
8
25
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.209",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
"sury": "11.0.0-alpha.4",
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
31
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
32
|
+
"@reventlessdev/rescript-fast-csv": "2.0.0-alpha.1",
|
|
32
33
|
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
|
|
33
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.1",
|
|
34
34
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
35
|
-
"@reventlessdev/rescript-
|
|
35
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.1",
|
|
36
36
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
37
|
-
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
38
37
|
"@reventlessdev/rescript-ssh2": "2.0.0-alpha.1",
|
|
38
|
+
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
39
39
|
"@reventlessdev/reventless-infra": "3.0.0-alpha.124",
|
|
40
40
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.30",
|
|
41
41
|
"@reventlessdev/reventless-spec": "3.0.0-alpha.99"
|
|
@@ -78,7 +78,7 @@ function Make(RuntimeEnvironment) {
|
|
|
78
78
|
A.Spec.eventSchema
|
|
79
79
|
]);
|
|
80
80
|
Plugin_Helpers$ReventlessCore.registerAdminAggregateMutations(aggregates, Config.hooks);
|
|
81
|
-
let dcbResult = DcbBuilder.construct(Platform_Admin_Structure$ReventlessCore.pluginId, Platform_Admin_Structure$ReventlessCore.pluginId, "Platform", true, undefined, undefined, aggregateEventTopics, aggregateProducedEvents, stateChangeSlices, stateViewSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, undefined, undefined, undefined, opts);
|
|
81
|
+
let dcbResult = DcbBuilder.construct(Platform_Admin_Structure$ReventlessCore.pluginId, Platform_Admin_Structure$ReventlessCore.pluginId, "Platform", true, undefined, undefined, aggregateEventTopics, aggregateProducedEvents, undefined, stateChangeSlices, stateViewSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, undefined, undefined, undefined, opts);
|
|
82
82
|
let slicePublishJsons = dcbResult.dcbPublishJsons;
|
|
83
83
|
if (slicePublishJsons !== undefined) {
|
|
84
84
|
stateChangeSlices.forEach(Sc => {
|
|
@@ -66,7 +66,17 @@ module Make = (
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
let createCommandTopic = (eventLog: SpecificEventLog.component, name, opts, ~memorySize, ~timeout) =>
|
|
69
|
+
let createCommandTopic = (eventLog: SpecificEventLog.component, name, opts, ~memorySize, ~timeout) => {
|
|
70
|
+
// Created outside the apply below, so the topic — and with it the channel's
|
|
71
|
+
// queue — exists while plugin construction is still synchronous. An outbound
|
|
72
|
+
// slice targeting this aggregate resolves its publish queue in a finalizer
|
|
73
|
+
// that runs before any Output settles; a queue created inside the apply is
|
|
74
|
+
// invisible there. Only the handler needs the event log's operations.
|
|
75
|
+
let commandTopic = SpecificCommandTopic.make(
|
|
76
|
+
~name,
|
|
77
|
+
~owner={kind: ComponentType.Aggregate, name: Spec.name},
|
|
78
|
+
~opts,
|
|
79
|
+
)
|
|
70
80
|
eventLog
|
|
71
81
|
->Component.operations
|
|
72
82
|
->Pulumi.Output.apply(eventLogOps => {
|
|
@@ -79,11 +89,6 @@ module Make = (
|
|
|
79
89
|
let eventLog = eventLogOps
|
|
80
90
|
},
|
|
81
91
|
)
|
|
82
|
-
let commandTopic = SpecificCommandTopic.make(
|
|
83
|
-
~name,
|
|
84
|
-
~owner={kind: ComponentType.Aggregate, name: Spec.name},
|
|
85
|
-
~opts,
|
|
86
|
-
)
|
|
87
92
|
let handler = SpecificCommandTopic.makeHandler(
|
|
88
93
|
~commandTopic,
|
|
89
94
|
~commandsHandler=AggregateCallback.handleCommands,
|
|
@@ -98,6 +103,7 @@ module Make = (
|
|
|
98
103
|
)
|
|
99
104
|
commandTopic
|
|
100
105
|
})
|
|
106
|
+
}
|
|
101
107
|
|
|
102
108
|
let createCommandGenerator = (
|
|
103
109
|
commandTopic: Pulumi.Output.t<SpecificCommandTopic.component>,
|
|
@@ -30,25 +30,27 @@ function Make(Spec) {
|
|
|
30
30
|
Id: Spec.Id,
|
|
31
31
|
commandSchema: Spec.commandSchema
|
|
32
32
|
})(SpecificEventCollector)(EventMappings)(AggregateRuntimeBuilder);
|
|
33
|
-
let createCommandTopic = (eventLog, name, opts, memorySize, timeout) =>
|
|
34
|
-
let AggregateCallback = Aggregate_Callback$ReventlessCore.Make(Spec)(Behavior)({
|
|
35
|
-
Spec: Spec,
|
|
36
|
-
EventLog: SpecificEventLog,
|
|
37
|
-
eventLog: eventLogOps
|
|
38
|
-
});
|
|
33
|
+
let createCommandTopic = (eventLog, name, opts, memorySize, timeout) => {
|
|
39
34
|
let commandTopic = SpecificCommandTopic.make(name, {
|
|
40
35
|
kind: "Aggregate",
|
|
41
36
|
name: Spec.name
|
|
42
37
|
}, opts);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
return Component$ReventlessCore.operations(eventLog).apply(eventLogOps => {
|
|
39
|
+
let AggregateCallback = Aggregate_Callback$ReventlessCore.Make(Spec)(Behavior)({
|
|
40
|
+
Spec: Spec,
|
|
41
|
+
EventLog: SpecificEventLog,
|
|
42
|
+
eventLog: eventLogOps
|
|
43
|
+
});
|
|
44
|
+
let handler = SpecificCommandTopic.makeHandler(commandTopic, AggregateCallback.handleCommands);
|
|
45
|
+
let eventLog$1 = Component$ReventlessCore.outputs(eventLog);
|
|
46
|
+
let resources = [
|
|
47
|
+
eventLog$1.resources,
|
|
48
|
+
eventLog$1.eventTopic.resources
|
|
49
|
+
].flat();
|
|
50
|
+
AggregateRuntimeBuilder.forCommandTopic(handler, none => SpecificCommandTopic.connect(none, resources, commandTopic), memorySize, timeout, commandTopic);
|
|
51
|
+
return commandTopic;
|
|
52
|
+
});
|
|
53
|
+
};
|
|
52
54
|
let createCommandGenerator = (commandTopic, api, name, opts, memorySize, timeout) => Output$Pulumi.flatMap(commandTopic, commandTopic => Component$ReventlessCore.operations(commandTopic).apply(param => {
|
|
53
55
|
let publishJsonsAndWait = param.publishJsonsAndWait;
|
|
54
56
|
let publishJsons = param.publishJsons;
|
|
@@ -31,6 +31,30 @@ type todoRow = {
|
|
|
31
31
|
retryCount: int,
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
The runtime protocol between a built callback and whoever drives it — the same
|
|
36
|
+
arrangement as `OutboundTranslationSlice_Callback`, and for the same reason. The
|
|
37
|
+
AWS entry point builds callbacks dynamically and has to ascribe a structural type
|
|
38
|
+
at the JS boundary; pointing that ascription at these aliases is what keeps it
|
|
39
|
+
from becoming an unreconcilable second declaration of the shape.
|
|
40
|
+
|
|
41
|
+
Unlike outbound, phase 1 here takes *raw* envelope JSONs: the callback routes by
|
|
42
|
+
`meta.service` and unwraps `{id, meta, event}` itself, per-source, through the
|
|
43
|
+
registered Mappings.
|
|
44
|
+
*/
|
|
45
|
+
type phase1 = (array<JSON.t>, Reventless.AutomationSlice.context) => unit
|
|
46
|
+
|
|
47
|
+
/** Phase 2: process all pending items, publishing commands. */
|
|
48
|
+
type phase2 = ReventlessInfra.CommandTopic.publishJsons => promise<unit>
|
|
49
|
+
|
|
50
|
+
/** The whole protocol as a value, for a driver that holds a built callback and
|
|
51
|
+
knows nothing of the Spec behind it. */
|
|
52
|
+
type runtime = {
|
|
53
|
+
todoItems: Dict.t<todoRow>,
|
|
54
|
+
phase1: phase1,
|
|
55
|
+
phase2: phase2,
|
|
56
|
+
}
|
|
57
|
+
|
|
34
58
|
module type T = {
|
|
35
59
|
module Spec: Reventless.AutomationSlice.Spec
|
|
36
60
|
module Automation: Reventless.AutomationSlice.Automation with module Spec := Spec
|
|
@@ -40,10 +64,10 @@ module type T = {
|
|
|
40
64
|
|
|
41
65
|
/** Phase 1: update TODO list from a batch of raw event JSONs. Internally
|
|
42
66
|
decodes per-source via the registered Mappings. */
|
|
43
|
-
let phase1:
|
|
67
|
+
let phase1: phase1
|
|
44
68
|
|
|
45
69
|
/** Phase 2: process all pending items, publishing commands via publishJsons. */
|
|
46
|
-
let phase2:
|
|
70
|
+
let phase2: phase2
|
|
47
71
|
}
|
|
48
72
|
|
|
49
73
|
module Make = (
|
|
@@ -94,6 +94,17 @@ module Make = (
|
|
|
94
94
|
// also drives DCB GSI creation: an aggregate's events live on its own
|
|
95
95
|
// EventLog and must not mint indexes on the DCB table.
|
|
96
96
|
~aggregateProducedEvents: array<(string, S.t<unknown>)>=[],
|
|
97
|
+
// Per-aggregate command publishers, keyed by Aggregate `Spec.name`. An
|
|
98
|
+
// OutboundTranslationSlice naming one in `targetName` publishes its command
|
|
99
|
+
// through that aggregate's CommandTopic instead of this plugin's DCB topic —
|
|
100
|
+
// the two are handled by different Lambdas, so sending an aggregate command
|
|
101
|
+
// to the DCB queue delivers it somewhere nothing handles it.
|
|
102
|
+
//
|
|
103
|
+
// Populated before this runs (`createAggregatesWithoutEventMappers`), so the
|
|
104
|
+
// publisher is in hand by the time a slice is built. DCB StateChangeSlice
|
|
105
|
+
// publishers are registered *after* and are deliberately absent: a slice
|
|
106
|
+
// targeting one wants the DCB topic, which is the fallback.
|
|
107
|
+
~publishToAggregates: dict<Pulumi.Output.t<CommandTopic.publishJsons>>=Dict.make(),
|
|
97
108
|
~stateChangeSlices: array<module(StateChangeSlice.T)>,
|
|
98
109
|
~stateViewSlices: array<module(StateViewSlice.T)>,
|
|
99
110
|
~automationSlices: array<module(AutomationSlice.T)>,
|
|
@@ -629,12 +640,21 @@ module Make = (
|
|
|
629
640
|
let outboundTranslationSlicesOutputs =
|
|
630
641
|
outboundTranslationSlices
|
|
631
642
|
->Array.map((module(OTS: OutboundTranslationSlice.T)) => {
|
|
643
|
+
// Where this slice's inbound command goes. `targetName` naming an
|
|
644
|
+
// Aggregate routes to that aggregate's CommandTopic; anything else —
|
|
645
|
+
// a DCB slice, or no target at all — keeps the plugin's DCB topic,
|
|
646
|
+
// which is what every slice wanted before aggregates were reachable.
|
|
647
|
+
let targetPublishJsons =
|
|
648
|
+
OTS.Spec.targetName
|
|
649
|
+
->Option.flatMap(target => publishToAggregates->Dict.get(target))
|
|
650
|
+
->Option.getOr(publishJsons)
|
|
651
|
+
|
|
632
652
|
let ots = OTS.make(
|
|
633
653
|
~dcbEventLog,
|
|
634
654
|
// Same dict the AutomationSlices get, so an outbound slice can
|
|
635
655
|
// name an Aggregate source by its Spec.name.
|
|
636
656
|
~allEventTopics,
|
|
637
|
-
~publishJsons,
|
|
657
|
+
~publishJsons=targetPublishJsons,
|
|
638
658
|
~runtime=?componentRuntime->Dict.get(OTS.Spec.name),
|
|
639
659
|
~opts,
|
|
640
660
|
)
|
|
@@ -83,13 +83,14 @@ let emptyResult = {
|
|
|
83
83
|
|
|
84
84
|
function Make(DcbEventLogStorage) {
|
|
85
85
|
return DcbEventTopicPublisher => (DcbCommandTopicChannel => (DcbCommandTopicChannelAsync => (RuntimeBuilder => (HooksConfig => {
|
|
86
|
-
let construct = (name, childName, apiNamePrefixOpt, onAdminApiOpt, environmentOpt, platformNameOpt, aggregateEventTopicsOpt, aggregateProducedEventsOpt, stateChangeSlices, stateViewSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, systemCallableComponentsOpt, componentRuntimeOpt, pluginStructure, opts) => {
|
|
86
|
+
let construct = (name, childName, apiNamePrefixOpt, onAdminApiOpt, environmentOpt, platformNameOpt, aggregateEventTopicsOpt, aggregateProducedEventsOpt, publishToAggregatesOpt, stateChangeSlices, stateViewSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, systemCallableComponentsOpt, componentRuntimeOpt, pluginStructure, opts) => {
|
|
87
87
|
let apiNamePrefix = apiNamePrefixOpt !== undefined ? apiNamePrefixOpt : name;
|
|
88
88
|
let onAdminApi = onAdminApiOpt !== undefined ? onAdminApiOpt : false;
|
|
89
89
|
let environment = environmentOpt !== undefined ? environmentOpt : "";
|
|
90
90
|
let platformName = platformNameOpt !== undefined ? platformNameOpt : "";
|
|
91
91
|
let aggregateEventTopics = aggregateEventTopicsOpt !== undefined ? aggregateEventTopicsOpt : ({});
|
|
92
92
|
let aggregateProducedEvents = aggregateProducedEventsOpt !== undefined ? aggregateProducedEventsOpt : [];
|
|
93
|
+
let publishToAggregates = publishToAggregatesOpt !== undefined ? publishToAggregatesOpt : ({});
|
|
93
94
|
let systemCallableComponents = systemCallableComponentsOpt !== undefined ? systemCallableComponentsOpt : [];
|
|
94
95
|
let componentRuntime = componentRuntimeOpt !== undefined ? componentRuntimeOpt : ({});
|
|
95
96
|
let hasDcb = stateChangeSlices.length !== 0;
|
|
@@ -330,7 +331,8 @@ function Make(DcbEventLogStorage) {
|
|
|
330
331
|
];
|
|
331
332
|
}));
|
|
332
333
|
let outboundTranslationSlicesOutputs = Object.fromEntries(outboundTranslationSlices.map(OTS => {
|
|
333
|
-
let
|
|
334
|
+
let targetPublishJsons = Stdlib_Option.getOr(Stdlib_Option.flatMap(OTS.Spec.targetName, target => publishToAggregates[target]), publishJsons);
|
|
335
|
+
let ots = OTS.make(dcbEventLog, allEventTopics, targetPublishJsons, componentRuntime[OTS.Spec.name], opts);
|
|
334
336
|
return [
|
|
335
337
|
OTS.Spec.name,
|
|
336
338
|
Component$ReventlessCore.outputs(ots)
|
|
@@ -22,6 +22,34 @@ type todoRow = {
|
|
|
22
22
|
lastError?: string,
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
The runtime protocol between a built callback and whoever drives it.
|
|
27
|
+
|
|
28
|
+
Defined here, once, and referenced by every party — `module type T` below, the
|
|
29
|
+
in-process builder, and the AWS entry point, which cannot name `T` because it
|
|
30
|
+
builds callbacks dynamically per `HANDLER_CONFIG` entry and must ascribe a
|
|
31
|
+
structural type at the JS boundary.
|
|
32
|
+
|
|
33
|
+
That ascription is the reason these aliases exist. A driver that restates the
|
|
34
|
+
shape instead of referencing it becomes a second source of truth that the
|
|
35
|
+
compiler cannot reconcile with this one: when `phase1` gained its `sourceId`,
|
|
36
|
+
the AWS entry point kept passing bare events and nothing complained, because its
|
|
37
|
+
hand-written copy was internally consistent. The mismatch surfaced only at
|
|
38
|
+
runtime, as `undefined` destructured from a tuple that was never a tuple.
|
|
39
|
+
*/
|
|
40
|
+
type phase1<'event> = array<(string, 'event)> => unit
|
|
41
|
+
|
|
42
|
+
/** Phase 2: translate all pending items, optionally publishing commands. */
|
|
43
|
+
type phase2 = ReventlessInfra.CommandTopic.publishJsons => promise<unit>
|
|
44
|
+
|
|
45
|
+
/** The whole protocol as a value — what a driver holding a built callback needs,
|
|
46
|
+
with no knowledge of the Spec it was built from. */
|
|
47
|
+
type runtime<'event> = {
|
|
48
|
+
todoItems: Dict.t<todoRow>,
|
|
49
|
+
phase1: phase1<'event>,
|
|
50
|
+
phase2: phase2,
|
|
51
|
+
}
|
|
52
|
+
|
|
25
53
|
module type T = {
|
|
26
54
|
module Spec: Reventless.OutboundTranslationSlice.Spec
|
|
27
55
|
module Translation: Reventless.OutboundTranslationSlice.Translation with module Spec := Spec
|
|
@@ -32,10 +60,10 @@ module type T = {
|
|
|
32
60
|
/** Phase 1: update TODO list from a batch of `(sourceId, event)` pairs
|
|
33
61
|
(collect only, no resolve). `sourceId` is the envelope id of the entity the
|
|
34
62
|
event was published for. */
|
|
35
|
-
let phase1:
|
|
63
|
+
let phase1: phase1<Spec.consumedEvent>
|
|
36
64
|
|
|
37
65
|
/** Phase 2: translate all pending items, optionally publishing commands via publishJsons. */
|
|
38
|
-
let phase2:
|
|
66
|
+
let phase2: phase2
|
|
39
67
|
}
|
|
40
68
|
|
|
41
69
|
module Make = (
|
|
@@ -168,6 +168,9 @@ module Make = (
|
|
|
168
168
|
~platformName=Spec.platformName,
|
|
169
169
|
~aggregateEventTopics,
|
|
170
170
|
~aggregateProducedEvents,
|
|
171
|
+
// Carries the aggregate publishers registered just above, so an outbound
|
|
172
|
+
// slice can route its command to the aggregate it names.
|
|
173
|
+
~publishToAggregates,
|
|
171
174
|
~stateChangeSlices,
|
|
172
175
|
~stateViewSlices,
|
|
173
176
|
~automationSlices,
|
|
@@ -156,7 +156,7 @@ function Make(Spec) {
|
|
|
156
156
|
let DcbBuilder = Dcb_Builder$ReventlessCore.Make(DcbEventLogStorage)(DcbEventTopicPublisher)(DcbCommandTopicChannel)(DcbCommandTopicChannelAsync)(PluginRuntimeBuilder)({
|
|
157
157
|
hooks: Spec.hooks
|
|
158
158
|
});
|
|
159
|
-
let dcbResult = DcbBuilder.construct(extra$1, childName, undefined, undefined, Spec.environment, Spec.platformName, aggregateEventTopics, aggregateProducedEvents, stateChangeSlices, stateViewSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, systemCallableComponents, componentRuntime, pluginStructure, opts);
|
|
159
|
+
let dcbResult = DcbBuilder.construct(extra$1, childName, undefined, undefined, Spec.environment, Spec.platformName, aggregateEventTopics, aggregateProducedEvents, Plugin_Helpers$ReventlessCore.publishToAggregates, stateChangeSlices, stateViewSlices, automationSlices, outboundTranslationSlices, inboundTranslationSlices, systemCallableComponents, componentRuntime, pluginStructure, opts);
|
|
160
160
|
let mutationEntriesFromAggregates = aggregates.flatMap(M => {
|
|
161
161
|
let commandSchema = M.Spec.commandSchema;
|
|
162
162
|
if (ApiNoApiHelpers$ReventlessCore.isNoApi(commandSchema)) {
|