@reventlessdev/online-shop-hybrid-ordering 1.0.0-alpha.183 → 1.0.0-alpha.185
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 +15 -0
- package/package.json +7 -7
- package/src/Customer/OutboundTranslationSlice/GeocodeCustomerAddress_Translation.res +6 -2
- package/src/Customer/OutboundTranslationSlice/GeocodeCustomerAddress_Translation.res.mjs +3 -4
- package/src/Order/OutboundTranslationSlice/SendOrderConfirmation_Translation.res +3 -1
- package/src/Order/OutboundTranslationSlice/SendOrderConfirmation_Translation.res.mjs +1 -1
- package/src/Service/GeocodingService.res +0 -91
- package/src/Service/GeocodingService.res.mjs +0 -123
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
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
|
+
# 1.0.0-alpha.185 (2026-08-04)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @reventlessdev/online-shop-hybrid-ordering
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 1.0.0-alpha.184 (2026-08-04)
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **outbound:** hand translate its geocoder instead of making it fetch one ([fb18312](https://github.com/ReventlessDev/reventless-core/commit/fb1831216b37c9562868c46e1a09054e69418c67))
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# 1.0.0-alpha.183 (2026-08-03)
|
|
7
22
|
|
|
8
23
|
**Note:** Version bump only for package @reventlessdev/online-shop-hybrid-ordering
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/online-shop-hybrid-ordering",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.185",
|
|
4
4
|
"description": "Online Shop Hybrid example — Ordering plugin (Customer aggregate + Order/CatalogProduct DCB)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"sury": "^11.0.0-alpha.4",
|
|
24
|
-
"@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.
|
|
25
|
-
"@reventlessdev/
|
|
24
|
+
"@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.95",
|
|
25
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.158",
|
|
26
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.126",
|
|
27
|
+
"@reventlessdev/online-shop-hybrid-ordering-spec": "1.0.0-alpha.95",
|
|
26
28
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
27
|
-
"@reventlessdev/reventless-
|
|
28
|
-
"@reventlessdev/reventless-
|
|
29
|
-
"@reventlessdev/reventless-local": "3.0.0-alpha.197",
|
|
30
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.99"
|
|
29
|
+
"@reventlessdev/reventless-local": "3.0.0-alpha.199",
|
|
30
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.100"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"rescript": "12.3.0",
|
|
@@ -17,9 +17,13 @@ let collect = (event, ~sourceId) =>
|
|
|
17
17
|
| AddressUpdated({address}) => [(`${sourceId}:${address}`, {customerId: sourceId, address})]
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
// The geocoder arrives as a capability rather than being reached for. This
|
|
21
|
+
// plugin cannot name Amazon Location — it depends on `reventless-spec`, not on
|
|
22
|
+
// `reventless-aws` — and under this shape it does not need to: the deployment
|
|
23
|
+
// decides what answers, and nothing below changes when that answer does.
|
|
24
|
+
let translate = async (_id, item, ~capabilities: Reventless.Capabilities.t) => {
|
|
21
25
|
let customerId = item.customerId
|
|
22
|
-
switch await
|
|
26
|
+
switch await capabilities.geocode(~text=item.address) {
|
|
23
27
|
| Error(Unavailable(why)) =>
|
|
24
28
|
// Retryable: the service could not answer. Do NOT report a verdict on the
|
|
25
29
|
// address — one outage would otherwise mark every address in flight bad.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Geocoding$Reventless from "@reventlessdev/reventless-spec/src/semantic/Geocoding.res.mjs";
|
|
4
|
-
import * as GeocodingService$OrderingPlugin from "../../Service/GeocodingService.res.mjs";
|
|
5
4
|
|
|
6
5
|
function collect(event, sourceId) {
|
|
7
6
|
if (event.TAG === "Registered") {
|
|
@@ -24,9 +23,9 @@ function collect(event, sourceId) {
|
|
|
24
23
|
]];
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
async function translate(_id, item) {
|
|
26
|
+
async function translate(_id, item, capabilities) {
|
|
28
27
|
let customerId = item.customerId;
|
|
29
|
-
let candidates = await
|
|
28
|
+
let candidates = await capabilities.geocode(item.address);
|
|
30
29
|
if (candidates.TAG === "Ok") {
|
|
31
30
|
let match = Geocoding$Reventless.confidentMatch(candidates._0, undefined, undefined);
|
|
32
31
|
if (match !== undefined) {
|
|
@@ -86,4 +85,4 @@ export {
|
|
|
86
85
|
translate,
|
|
87
86
|
moduleUrl,
|
|
88
87
|
}
|
|
89
|
-
/*
|
|
88
|
+
/* No side effect */
|
|
@@ -6,7 +6,9 @@ let collect = (event, ~sourceId as _) =>
|
|
|
6
6
|
| OrderPlaced({orderId, customerId}) => [(orderId, {orderId, customerId})]
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// This slice calls a service the framework does not broker, so it reaches its
|
|
10
|
+
// mailer directly and ignores the injected capabilities.
|
|
11
|
+
let translate = async (_id, item, ~capabilities as _) => {
|
|
10
12
|
try {
|
|
11
13
|
await EmailService.sendOrderConfirmation(
|
|
12
14
|
~email=item.customerId,
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
// The plugin's geocoding client.
|
|
2
|
-
//
|
|
3
|
-
// A plugin is provider-agnostic — it cannot reach `ReventlessAws` and should not
|
|
4
|
-
// know that Amazon Location is what answers. So this talks HTTP to whatever
|
|
5
|
-
// endpoint the deployment configured and speaks the framework's provider-neutral
|
|
6
|
-
// vocabulary (`Reventless.Geocoding`) on both sides.
|
|
7
|
-
//
|
|
8
|
-
// The endpoint's contract, which is what makes an unattended caller possible:
|
|
9
|
-
// `200` with a (possibly empty) array is an answer, and any other status is the
|
|
10
|
-
// service failing to give one. The distinction is the whole reason this returns
|
|
11
|
-
// `result` rather than a list — see `Reventless.Geocoding.failure`.
|
|
12
|
-
|
|
13
|
-
@val external fetch: (string, 'options) => promise<'response> = "fetch"
|
|
14
|
-
|
|
15
|
-
type response = {ok: bool, status: int}
|
|
16
|
-
@send external json: response => promise<JSON.t> = "json"
|
|
17
|
-
|
|
18
|
-
@val external encodeURIComponent: string => string = "encodeURIComponent"
|
|
19
|
-
|
|
20
|
-
// `process.env` without a Node binding — a plugin depends on neither
|
|
21
|
-
// rescript-node nor any provider package.
|
|
22
|
-
type processT = {env: dict<string>}
|
|
23
|
-
@val external process: processT = "process"
|
|
24
|
-
|
|
25
|
-
let endpoint = () =>
|
|
26
|
-
switch process.env->Dict.get("GEOCODER_ENDPOINT") {
|
|
27
|
-
| Some("") | None => None
|
|
28
|
-
| Some(v) => Some(v)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// One candidate off the wire. A row missing a coordinate, or carrying one
|
|
32
|
-
// outside the valid ranges, is dropped rather than propagated — it cannot be
|
|
33
|
-
// stored, and one bad row should not lose the good ones beside it.
|
|
34
|
-
let decodeCandidate = (json: JSON.t): option<Reventless.Geocoding.candidate> =>
|
|
35
|
-
switch json->JSON.Decode.object {
|
|
36
|
-
| None => None
|
|
37
|
-
| Some(o) =>
|
|
38
|
-
switch (
|
|
39
|
-
o->Dict.get("lat")->Option.flatMap(JSON.Decode.float),
|
|
40
|
-
o->Dict.get("lng")->Option.flatMap(JSON.Decode.float),
|
|
41
|
-
) {
|
|
42
|
-
| (Some(lat), Some(lng)) =>
|
|
43
|
-
switch Reventless.GeoPoint.make(~lat, ~lng) {
|
|
44
|
-
| Ok(point) =>
|
|
45
|
-
Some({
|
|
46
|
-
Reventless.Geocoding.label: o
|
|
47
|
-
->Dict.get("label")
|
|
48
|
-
->Option.flatMap(JSON.Decode.string)
|
|
49
|
-
->Option.getOr(""),
|
|
50
|
-
point,
|
|
51
|
-
relevance: o->Dict.get("relevance")->Option.flatMap(JSON.Decode.float),
|
|
52
|
-
})
|
|
53
|
-
| Error(_) => None
|
|
54
|
-
}
|
|
55
|
-
| _ => None
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Annotated against the framework's port rather than spelling the signature out
|
|
60
|
-
// again. The endpoint this reads is a deployment's *current* answer to "who
|
|
61
|
-
// geocodes"; the type is the answer that does not change when that one does, so
|
|
62
|
-
// a later supplier swaps in here without `translate` noticing.
|
|
63
|
-
let search: Reventless.Geocoding.search = async (~text) => {
|
|
64
|
-
let trimmed = text->String.trim
|
|
65
|
-
switch endpoint() {
|
|
66
|
-
| None => Error(Unavailable("no geocoder endpoint configured (GEOCODER_ENDPOINT unset)"))
|
|
67
|
-
| Some(_) if trimmed == "" =>
|
|
68
|
-
// An empty query was never going to match; asking would spend a request to
|
|
69
|
-
// be told so.
|
|
70
|
-
Error(NoMatch)
|
|
71
|
-
| Some(base) =>
|
|
72
|
-
let url = `${base}?q=${encodeURIComponent(trimmed)}`
|
|
73
|
-
switch await fetch(url, {"method": "GET"}) {
|
|
74
|
-
| response =>
|
|
75
|
-
if !response.ok {
|
|
76
|
-
Error(Unavailable(`geocoder responded ${response.status->Int.toString}`))
|
|
77
|
-
} else {
|
|
78
|
-
switch await response->json {
|
|
79
|
-
| body =>
|
|
80
|
-
let candidates =
|
|
81
|
-
body->JSON.Decode.array->Option.getOr([])->Array.filterMap(decodeCandidate)
|
|
82
|
-
candidates->Array.length == 0 ? Error(NoMatch) : Ok(candidates)
|
|
83
|
-
| exception _ => Error(Unavailable("geocoder returned a body that is not JSON"))
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
| exception exn =>
|
|
87
|
-
let msg = exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown")
|
|
88
|
-
Error(Unavailable(msg))
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
-
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
-
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
6
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
-
import * as GeoPoint$Reventless from "@reventlessdev/reventless-spec/src/semantic/GeoPoint.res.mjs";
|
|
8
|
-
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
9
|
-
|
|
10
|
-
function endpoint() {
|
|
11
|
-
let v = process.env["GEOCODER_ENDPOINT"];
|
|
12
|
-
if (v !== undefined && v !== "") {
|
|
13
|
-
return v;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function decodeCandidate(json) {
|
|
18
|
-
let o = Stdlib_JSON.Decode.object(json);
|
|
19
|
-
if (o === undefined) {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
let match = Stdlib_Option.flatMap(o["lat"], Stdlib_JSON.Decode.float);
|
|
23
|
-
let match$1 = Stdlib_Option.flatMap(o["lng"], Stdlib_JSON.Decode.float);
|
|
24
|
-
if (match === undefined) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (match$1 === undefined) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
let point = GeoPoint$Reventless.make(match, match$1);
|
|
31
|
-
if (point.TAG === "Ok") {
|
|
32
|
-
return {
|
|
33
|
-
label: Stdlib_Option.getOr(Stdlib_Option.flatMap(o["label"], Stdlib_JSON.Decode.string), ""),
|
|
34
|
-
point: point._0,
|
|
35
|
-
relevance: Stdlib_Option.flatMap(o["relevance"], Stdlib_JSON.Decode.float)
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function search(text) {
|
|
41
|
-
let trimmed = text.trim();
|
|
42
|
-
let base = endpoint();
|
|
43
|
-
if (base === undefined) {
|
|
44
|
-
return {
|
|
45
|
-
TAG: "Error",
|
|
46
|
-
_0: {
|
|
47
|
-
TAG: "Unavailable",
|
|
48
|
-
_0: "no geocoder endpoint configured (GEOCODER_ENDPOINT unset)"
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
if (trimmed === "") {
|
|
53
|
-
return {
|
|
54
|
-
TAG: "Error",
|
|
55
|
-
_0: "NoMatch"
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
let url = base + `?q=` + encodeURIComponent(trimmed);
|
|
59
|
-
let exit = 0;
|
|
60
|
-
let response;
|
|
61
|
-
try {
|
|
62
|
-
response = await fetch(url, {
|
|
63
|
-
method: "GET"
|
|
64
|
-
});
|
|
65
|
-
exit = 1;
|
|
66
|
-
} catch (raw_exn) {
|
|
67
|
-
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
68
|
-
let msg = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(exn), Stdlib_JsExn.message), "unknown");
|
|
69
|
-
return {
|
|
70
|
-
TAG: "Error",
|
|
71
|
-
_0: {
|
|
72
|
-
TAG: "Unavailable",
|
|
73
|
-
_0: msg
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
if (exit === 1) {
|
|
78
|
-
if (!response.ok) {
|
|
79
|
-
return {
|
|
80
|
-
TAG: "Error",
|
|
81
|
-
_0: {
|
|
82
|
-
TAG: "Unavailable",
|
|
83
|
-
_0: `geocoder responded ` + response.status.toString()
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
let exit$1 = 0;
|
|
88
|
-
let body;
|
|
89
|
-
try {
|
|
90
|
-
body = await response.json();
|
|
91
|
-
exit$1 = 2;
|
|
92
|
-
} catch (exn$1) {
|
|
93
|
-
return {
|
|
94
|
-
TAG: "Error",
|
|
95
|
-
_0: {
|
|
96
|
-
TAG: "Unavailable",
|
|
97
|
-
_0: "geocoder returned a body that is not JSON"
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
if (exit$1 === 2) {
|
|
102
|
-
let candidates = Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_JSON.Decode.array(body), []), decodeCandidate);
|
|
103
|
-
if (candidates.length === 0) {
|
|
104
|
-
return {
|
|
105
|
-
TAG: "Error",
|
|
106
|
-
_0: "NoMatch"
|
|
107
|
-
};
|
|
108
|
-
} else {
|
|
109
|
-
return {
|
|
110
|
-
TAG: "Ok",
|
|
111
|
-
_0: candidates
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export {
|
|
119
|
-
endpoint,
|
|
120
|
-
decodeCandidate,
|
|
121
|
-
search,
|
|
122
|
-
}
|
|
123
|
-
/* Stdlib_JsExn Not a pure module */
|