@reventlessdev/reventless-core 3.0.0-alpha.208 → 3.0.0-alpha.210

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,26 @@
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.210 (2026-08-04)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **core:** tag a slice's published command with its target, not itself ([bb2b063](https://github.com/ReventlessDev/reventless-core/commit/bb2b063f914e45fb8efcba28e0b691713cf93d8a))
11
+ ### Features
12
+
13
+ * **outbound:** hand translate its geocoder instead of making it fetch one ([fb18312](https://github.com/ReventlessDev/reventless-core/commit/fb1831216b37c9562868c46e1a09054e69418c67))
14
+
15
+
16
+ # 3.0.0-alpha.209 (2026-08-03)
17
+
18
+ ### Bug Fixes
19
+
20
+ * **aws:** give slice command publishes a queue they can actually reach ([42fdb55](https://github.com/ReventlessDev/reventless-core/commit/42fdb55b751a197d586c49e3615ce12ffea42046))
21
+ ### Features
22
+
23
+ * **dcb:** route an outbound slice's command to the aggregate it targets ([897b37b](https://github.com/ReventlessDev/reventless-core/commit/897b37b46a4e856567646dfdc2f0652e81cf7388))
24
+
25
+
6
26
  # 3.0.0-alpha.208 (2026-08-03)
7
27
 
8
28
  ### 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.210",
4
4
  "description": "Core package for Reventless framework",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -29,16 +29,16 @@
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",
33
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
34
32
  "@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
33
+ "@reventlessdev/rescript-fast-csv": "2.0.0-alpha.1",
35
34
  "@reventlessdev/rescript-node": "2.0.0-alpha.1",
35
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
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",
40
39
  "@reventlessdev/reventless-interop": "3.0.0-alpha.30",
41
- "@reventlessdev/reventless-spec": "3.0.0-alpha.99"
40
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.125",
41
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.100"
42
42
  },
43
43
  "devDependencies": {
44
44
  "rescript": "12.3.0",
@@ -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;
@@ -86,8 +86,12 @@ module Make = (
86
86
  // only carries `item` data), so causation across the automation hop is
87
87
  // lost — emitted commands are roots of a fresh correlation chain. If we
88
88
  // later thread source meta through todoItems, switch to deriveMeta.
89
- let makeMeta = (): Reventless.Message.meta =>
90
- Message.generateMeta(~service=`AutomationSlice:${Spec.name}`)
89
+ // `service` names the command's TARGET, matching the API path
90
+ // (`CommandGenerator_Callback` passes `~serviceName=AggregateSpec.name`). An
91
+ // aggregate derives its event meta from the command's, and ReadModel /
92
+ // AutomationSlice callbacks dispatch mappings on `meta.service` — so naming
93
+ // the slice here makes the target's own events unmatchable, silently.
94
+ let makeMeta = (): Reventless.Message.meta => Message.generateMeta(~service=Spec.targetName)
91
95
 
92
96
  // Per-source erased dispatch — pre-compile decoders once at module init.
93
97
  type dispatch = {
@@ -33,7 +33,7 @@ let todoRowSchema = S.schema(s => ({
33
33
  function Make(Spec) {
34
34
  return Automation => {
35
35
  let todoItems = {};
36
- let makeMeta = () => Message$ReventlessCore.generateMeta(`AutomationSlice:` + Spec.name, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
36
+ let makeMeta = () => Message$ReventlessCore.generateMeta(Spec.targetName, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
37
37
  let dispatches = Automation.mappings.map(M => {
38
38
  let decoder = DcbDecode$Reventless.makeDecoder(M.sourceEventSchema);
39
39
  let handle = (json, ctx) => {
@@ -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)
@@ -102,8 +102,9 @@ module Make = (
102
102
  // no upstream Reventless meta to derive from, so emitted commands are roots
103
103
  // of a fresh correlation chain. `traceparent` populated from an inbound HTTP
104
104
  // header would need to be threaded in here by the API/ingress adapter.
105
- let makeMeta = (): Reventless.Message.meta =>
106
- Message.generateMeta(~service=`InboundTranslationSlice:${Spec.name}`)
105
+ // `service` names the command's TARGET, as on the API path — see the note in
106
+ // `AutomationSlice_Callback`. The target's events are dispatched on it.
107
+ let makeMeta = (): Reventless.Message.meta => Message.generateMeta(~service=Spec.targetName)
107
108
 
108
109
  let receive = async (
109
110
  publishJsons: ReventlessInfra.CommandTopic.publishJsons,
@@ -69,7 +69,7 @@ function takeAuditRow(auditLog, requestId) {
69
69
  function Make(Spec) {
70
70
  return Translation => {
71
71
  let auditLog = {};
72
- let makeMeta = () => Message$ReventlessCore.generateMeta(`InboundTranslationSlice:` + Spec.name, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
72
+ let makeMeta = () => Message$ReventlessCore.generateMeta(Spec.targetName, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
73
73
  let receive = async (publishJsons, inputJson) => {
74
74
  let requestId = Uuid.v4();
75
75
  let input;
@@ -17,6 +17,17 @@ module Make = (
17
17
  let api: unit => QueryDbStorage.api
18
18
  let apiRole: unit => QueryDbStorage.role
19
19
  },
20
+ // What the platform provisioned, for `translate` to reach. A thunk like
21
+ // `Api.api` rather than a value, so a platform can resolve it when the runtime
22
+ // exists rather than when this functor is applied.
23
+ //
24
+ // A functor parameter, not a mutable slot: the platform that omits it does not
25
+ // compile, which is the property D9 chose injection for. This supplies the
26
+ // in-process path; the Lambda entry point supplies its own, because there the
27
+ // callback is driven by a bundled shell rather than by this builder.
28
+ Capabilities: {
29
+ let capabilities: unit => Reventless.Capabilities.t
30
+ },
20
31
  ) => {
21
32
  let finish = EventCollectorRuntimeBuilder.finish
22
33
  module Make = (
@@ -187,7 +198,7 @@ module Make = (
187
198
  // same topic, awaiting would self-deadlock the bus — same
188
199
  // shape as AutomationSlice. Detach it; errors are logged.
189
200
  let _ =
190
- Callback.phase2(publishJsonsFn)
201
+ Callback.phase2(publishJsonsFn, ~capabilities=Capabilities.capabilities())
191
202
  ->Promise.then(() => syncToQueryDb(queryDbOps))
192
203
  ->Promise.catch(exn => {
193
204
  let errMsg =
@@ -223,7 +234,8 @@ module Make = (
223
234
  ->Pulumi.Output.apply(((ecOps, publishJsonsFn)) => {
224
235
  let ops: OutboundTranslationSlice.operations = {
225
236
  enqueueEvent: ecOps.enqueueEvent,
226
- translatePending: async () => await Callback.phase2(publishJsonsFn),
237
+ translatePending: async () =>
238
+ await Callback.phase2(publishJsonsFn, ~capabilities=Capabilities.capabilities()),
227
239
  }
228
240
  ops
229
241
  }),
@@ -29,7 +29,7 @@ import * as OutboundTranslationSlice$ReventlessCore from "./OutboundTranslationS
29
29
  import * as OutboundTranslationSlice_Callback$ReventlessCore from "./OutboundTranslationSlice_Callback.res.mjs";
30
30
 
31
31
  function Make(RuntimeEnvironment) {
32
- return QueryDbStorage => (QueryDbResolvers => (EventCollectorChannel => (EventCollectorRuntimeBuilder => (Api => {
32
+ return QueryDbStorage => (QueryDbResolvers => (EventCollectorChannel => (EventCollectorRuntimeBuilder => (Api => (Capabilities => {
33
33
  let Make = Spec => (Translation => {
34
34
  let Callback = OutboundTranslationSlice_Callback$ReventlessCore.Make(Spec)(Translation);
35
35
  let queryDbName = Spec.name + "Todo";
@@ -108,7 +108,7 @@ function Make(RuntimeEnvironment) {
108
108
  })), events => Stream$1.fromIterable(events))), eventsArr => Effect.promise(async () => {
109
109
  Callback.phase1(eventsArr);
110
110
  await syncToQueryDb(queryDbOps);
111
- Stdlib_Promise.$$catch(Callback.phase2(publishJsonsFn).then(() => syncToQueryDb(queryDbOps)), exn => {
111
+ Stdlib_Promise.$$catch(Callback.phase2(publishJsonsFn, Capabilities.capabilities()).then(() => syncToQueryDb(queryDbOps)), exn => {
112
112
  let errMsg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown");
113
113
  Effect.runSync(EffectLogger$ReventlessCore.logError(`OutboundTranslationSlice(` + Spec.name + `)`, undefined, `detached phase 2 error: ` + errMsg));
114
114
  return Promise.resolve();
@@ -126,7 +126,7 @@ function Make(RuntimeEnvironment) {
126
126
  let publishJsonsFn = param[1];
127
127
  return {
128
128
  enqueueEvent: param[0].enqueueEvent,
129
- translatePending: async () => await Callback.phase2(publishJsonsFn)
129
+ translatePending: async () => await Callback.phase2(publishJsonsFn, Capabilities.capabilities())
130
130
  };
131
131
  }));
132
132
  let aggregatedResources = Object.values(eventTopics).flatMap(t => t.resources);
@@ -149,7 +149,7 @@ function Make(RuntimeEnvironment) {
149
149
  finish: EventCollectorRuntimeBuilder.finish,
150
150
  Make: Make
151
151
  };
152
- }))));
152
+ })))));
153
153
  }
154
154
 
155
155
  export {
@@ -39,8 +39,16 @@ runtime, as `undefined` destructured from a tuple that was never a tuple.
39
39
  */
40
40
  type phase1<'event> = array<(string, 'event)> => unit
41
41
 
42
- /** Phase 2: translate all pending items, optionally publishing commands. */
43
- type phase2 = ReventlessInfra.CommandTopic.publishJsons => promise<unit>
42
+ /** Phase 2: translate all pending items, optionally publishing commands.
43
+
44
+ `~capabilities` is supplied by whoever drives the callback — the in-process
45
+ builder or the Lambda entry point — because that is the layer that knows what
46
+ the platform provisioned. It arrives here rather than at `Make` so the
47
+ functor stays a pure function of the Spec, appliable before a runtime exists. */
48
+ type phase2 = (
49
+ ReventlessInfra.CommandTopic.publishJsons,
50
+ ~capabilities: Reventless.Capabilities.t,
51
+ ) => promise<unit>
44
52
 
45
53
  /** The whole protocol as a value — what a driver holding a built callback needs,
46
54
  with no knowledge of the Spec it was built from. */
@@ -81,8 +89,20 @@ module Make = (
81
89
  // meta is not threaded from phase1 → phase2 today, so causation across the
82
90
  // translation hop is not preserved. If sources later carry meta into todoItems,
83
91
  // switch to Message.deriveMeta.
92
+ //
93
+ // `service` names the command's TARGET, matching what the API path does
94
+ // (`CommandGenerator_Callback` passes `~serviceName=AggregateSpec.name`). It is
95
+ // load-bearing rather than descriptive: an aggregate derives its event's meta
96
+ // from the command's, and ReadModel/AutomationSlice callbacks dispatch mappings
97
+ // on `meta.service`. Naming the slice here instead makes the aggregate emit an
98
+ // event no mapping for that aggregate matches — projected as zero actions, with
99
+ // no error. Falls back to the slice's own name only when there is no target, in
100
+ // which case the command goes to the plugin's DCB topic and nothing dispatches
101
+ // on it.
84
102
  let makeMeta = (): Reventless.Message.meta =>
85
- Message.generateMeta(~service=`OutboundTranslationSlice:${Spec.name}`)
103
+ Message.generateMeta(
104
+ ~service=Spec.targetName->Option.getOr(`OutboundTranslationSlice:${Spec.name}`),
105
+ )
86
106
 
87
107
  let phase1 = (events: array<(string, Spec.consumedEvent)>) => {
88
108
  events->Array.forEach(((sourceId, event)) => {
@@ -103,7 +123,10 @@ module Make = (
103
123
  })
104
124
  }
105
125
 
106
- let phase2 = async (publishJsons: ReventlessInfra.CommandTopic.publishJsons) => {
126
+ let phase2 = async (
127
+ publishJsons: ReventlessInfra.CommandTopic.publishJsons,
128
+ ~capabilities: Reventless.Capabilities.t,
129
+ ) => {
107
130
  let pending =
108
131
  todoItems
109
132
  ->Dict.toArray
@@ -131,7 +154,7 @@ module Make = (
131
154
  | Some(item) =>
132
155
  todoItems->Dict.set(id, {...row, status: Processing, processedAt: now()})
133
156
 
134
- let result = try await Translation.translate(id, item) catch {
157
+ let result = try await Translation.translate(id, item, ~capabilities) catch {
135
158
  | exn =>
136
159
  let msg =
137
160
  exn
@@ -32,7 +32,7 @@ let todoRowSchema = S.schema(s => ({
32
32
  function Make(Spec) {
33
33
  return Translation => {
34
34
  let todoItems = {};
35
- let makeMeta = () => Message$ReventlessCore.generateMeta(`OutboundTranslationSlice:` + Spec.name, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
35
+ let makeMeta = () => Message$ReventlessCore.generateMeta(Stdlib_Option.getOr(Spec.targetName, `OutboundTranslationSlice:` + Spec.name), undefined, undefined, undefined, undefined, undefined, undefined, undefined);
36
36
  let phase1 = events => {
37
37
  events.forEach(param => {
38
38
  Translation.collect(param[1], param[0]).forEach(param => {
@@ -53,7 +53,7 @@ function Make(Spec) {
53
53
  });
54
54
  });
55
55
  };
56
- let phase2 = async publishJsons => {
56
+ let phase2 = async (publishJsons, capabilities) => {
57
57
  let pending = Object.entries(todoItems).filter(param => {
58
58
  let row = param[1];
59
59
  if (row.status === "Pending") {
@@ -86,7 +86,7 @@ function Make(Spec) {
86
86
  todoItems[id] = newrecord;
87
87
  let result;
88
88
  try {
89
- result = await Translation.translate(id, Primitive_option.valFromOption(item));
89
+ result = await Translation.translate(id, Primitive_option.valFromOption(item), capabilities);
90
90
  } catch (raw_exn$1) {
91
91
  let exn$1 = Primitive_exceptions.internalToException(raw_exn$1);
92
92
  let msg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn$1), Stdlib_JsExn.message), "unknown error");
@@ -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)) {