@reventlessdev/reventless-core 3.0.0-alpha.209 → 3.0.0-alpha.211
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 +6 -6
- package/src/admin/Platform_AdminApi.res +19 -0
- package/src/admin/Platform_AdminApi.res.mjs +6 -0
- package/src/components/AutomationSlice/AutomationSlice_Callback.res +6 -2
- package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +1 -1
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res +3 -2
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Callback.res.mjs +1 -1
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +14 -2
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res.mjs +4 -4
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res +28 -5
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res.mjs +3 -3
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.211 (2026-08-04)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **geocoding:** serve geocoding through the platform GraphQL API (D9 half 2) ([914d3c0](https://github.com/ReventlessDev/reventless-core/commit/914d3c0a99a1b67ffb89e2b8259dd30f67ff9bae))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.210 (2026-08-04)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **core:** tag a slice's published command with its target, not itself ([bb2b063](https://github.com/ReventlessDev/reventless-core/commit/bb2b063f914e45fb8efcba28e0b691713cf93d8a))
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **outbound:** hand translate its geocoder instead of making it fetch one ([fb18312](https://github.com/ReventlessDev/reventless-core/commit/fb1831216b37c9562868c46e1a09054e69418c67))
|
|
21
|
+
|
|
22
|
+
|
|
6
23
|
# 3.0.0-alpha.209 (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.211",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -28,17 +28,17 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"sury": "11.0.0-alpha.4",
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
32
31
|
"@reventlessdev/rescript-fast-csv": "2.0.0-alpha.1",
|
|
33
|
-
"@reventlessdev/rescript-
|
|
32
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
34
33
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
34
|
+
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.14",
|
|
35
35
|
"@reventlessdev/rescript-node": "2.0.0-alpha.1",
|
|
36
36
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
37
|
-
"@reventlessdev/rescript-ssh2": "2.0.0-alpha.1",
|
|
38
37
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
39
|
-
"@reventlessdev/
|
|
38
|
+
"@reventlessdev/rescript-ssh2": "2.0.0-alpha.1",
|
|
39
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.126",
|
|
40
40
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.30",
|
|
41
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
41
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.100"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "12.3.0",
|
|
@@ -100,6 +100,25 @@ let uploadMutationFields = [
|
|
|
100
100
|
` Upload_Release(store: ID!, storageRef: String!): Upload_ReleaseResult`,
|
|
101
101
|
]
|
|
102
102
|
|
|
103
|
+
// Geocoding service (D9 half 2): turn an address into ranked coordinate candidates
|
|
104
|
+
// through the platform GraphQL API instead of an anonymous Function URL, so
|
|
105
|
+
// AppSync's Cognito authorizer authenticates the caller. Geocoding is a *read*, so
|
|
106
|
+
// a Query field. Like the upload fields these belong on the **domain** base
|
|
107
|
+
// (`domainBaseFragment` in the AWS/local Platform), which takes the default
|
|
108
|
+
// `AllowAuthenticated` auth — not the Admin-gated admin base, and so deliberately
|
|
109
|
+
// NOT part of `baseFragment` below.
|
|
110
|
+
//
|
|
111
|
+
// This is the client door of the geocoding capability; plugin backend code reaches
|
|
112
|
+
// the same place index through an injected port (`Reventless.Capabilities.geocode`,
|
|
113
|
+
// backed by `Geocoder_AwsLocation_Backend`). One capability, two doors — mirroring
|
|
114
|
+
// the object store's `Upload_Presign` / `Offload.resolve` split. `relevance` is
|
|
115
|
+
// nullable because a provider that does not score its results returns none.
|
|
116
|
+
let geocodeTypes = [
|
|
117
|
+
`type GeocodeCandidate {\n label: String!\n lat: Float!\n lng: Float!\n relevance: Float\n}`,
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
let geocodeQueryFields = [` geocode(text: String!): [GeocodeCandidate!]`]
|
|
121
|
+
|
|
103
122
|
let baseFragment = (~cloner: bool) => {
|
|
104
123
|
let base = GraphQL_FragmentGenerator.generate(
|
|
105
124
|
~mutationEntries=mutationEntries(~cloner),
|
|
@@ -81,6 +81,10 @@ let uploadMutationFields = [
|
|
|
81
81
|
` Upload_Release(store: ID!, storageRef: String!): Upload_ReleaseResult`
|
|
82
82
|
];
|
|
83
83
|
|
|
84
|
+
let geocodeTypes = [`type GeocodeCandidate {\n label: String!\n lat: Float!\n lng: Float!\n relevance: Float\n}`];
|
|
85
|
+
|
|
86
|
+
let geocodeQueryFields = [` geocode(text: String!): [GeocodeCandidate!]`];
|
|
87
|
+
|
|
84
88
|
function baseFragment(cloner) {
|
|
85
89
|
let base = GraphQL_FragmentGenerator$ReventlessCore.generate(mutationEntries(cloner), PluginBaseFragment$ReventlessCore.queryEntries);
|
|
86
90
|
let parts = GraphQL_Stitcher$ReventlessCore.decode(base);
|
|
@@ -120,6 +124,8 @@ export {
|
|
|
120
124
|
pluginStatusSubscriptionSource,
|
|
121
125
|
uploadTypes,
|
|
122
126
|
uploadMutationFields,
|
|
127
|
+
geocodeTypes,
|
|
128
|
+
geocodeQueryFields,
|
|
123
129
|
baseFragment,
|
|
124
130
|
}
|
|
125
131
|
/* cloneArgsSchema Not a pure module */
|
|
@@ -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
|
-
|
|
90
|
-
|
|
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(
|
|
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
|
-
|
|
106
|
-
|
|
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(
|
|
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 () =>
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
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");
|