@reventlessdev/reventless-core 3.0.0-alpha.209 → 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,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.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
+
6
16
  # 3.0.0-alpha.209 (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.209",
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
32
  "@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
34
- "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
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
38
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
39
- "@reventlessdev/reventless-infra": "3.0.0-alpha.124",
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",
@@ -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) => {
@@ -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");