@reventlessdev/reventless-core 3.0.0-alpha.208 → 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 CHANGED
@@ -3,6 +3,16 @@
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
+
6
16
  # 3.0.0-alpha.208 (2026-08-03)
7
17
 
8
18
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-core",
3
- "version": "3.0.0-alpha.208",
3
+ "version": "3.0.0-alpha.209",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -30,13 +30,13 @@
30
30
  "uuid": "^13.0.0",
31
31
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
32
32
  "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.1",
33
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
34
33
  "@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
34
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
35
35
  "@reventlessdev/rescript-node": "2.0.0-alpha.1",
36
36
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
37
37
  "@reventlessdev/rescript-ssh2": "2.0.0-alpha.1",
38
- "@reventlessdev/reventless-infra": "3.0.0-alpha.124",
39
38
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
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"
42
42
  },
@@ -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) => Component$ReventlessCore.operations(eventLog).apply(eventLogOps => {
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
- let handler = SpecificCommandTopic.makeHandler(commandTopic, AggregateCallback.handleCommands);
44
- let eventLog$1 = Component$ReventlessCore.outputs(eventLog);
45
- let resources = [
46
- eventLog$1.resources,
47
- eventLog$1.eventTopic.resources
48
- ].flat();
49
- AggregateRuntimeBuilder.forCommandTopic(handler, none => SpecificCommandTopic.connect(none, resources, commandTopic), memorySize, timeout, commandTopic);
50
- return commandTopic;
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;
@@ -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 ots = OTS.make(dcbEventLog, allEventTopics, publishJsons, componentRuntime[OTS.Spec.name], opts);
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)
@@ -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)) {