@reventlessdev/online-shop-hybrid-ordering 1.0.0-alpha.274 → 1.0.0-alpha.275
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 +10 -10
- package/src/Notification/Automation/MarketingDigest.res +32 -0
- package/src/Notification/Automation/MarketingDigest.res.mjs +62 -0
- package/src/Notification/Automation/MarketingDigest_Automation.res +97 -0
- package/src/Notification/Automation/MarketingDigest_Automation.res.mjs +211 -0
- package/src/Notification/Automation/NotificationIntake.res.mjs +3 -0
- package/src/Notification/Automation/NotificationIntake_Automation.res.mjs +10 -3
- package/src/Notification/StateChange/NotificationPreferences.res +9 -0
- package/src/Notification/StateChange/NotificationPreferences.res.mjs +18 -1
- package/src/Notification/StateChange/NotificationPreferences_Behavior.res +11 -0
- package/src/Notification/StateChange/NotificationPreferences_Behavior.res.mjs +26 -0
- package/src/Order/Automation/AutoShipOrder.res.mjs +3 -0
- package/src/Order/Automation/AutoShipOrder_Automation.res.mjs +10 -3
- package/src/Plugin.res +15 -2
- package/src/Plugin.res.mjs +28 -2
- package/tests/Flow/OrderingFlow_GWT.res.mjs +4 -2
- package/tests/Notification/Automation/MarketingDigest_GWT.res +84 -0
- package/tests/Notification/Automation/MarketingDigest_GWT.res.mjs +235 -0
- package/tests/Notification/Automation/NotificationIntake_GWT.res.mjs +20 -1
- package/tests/Notification/StateChange/NotificationPreferences_GWT.res +27 -0
- package/tests/Notification/StateChange/NotificationPreferences_GWT.res.mjs +29 -2
- package/tests/Order/Automation/AutoShipOrder_GWT.res.mjs +20 -1
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
|
+
# 1.0.0-alpha.275 (2026-10-16)
|
|
7
|
+
|
|
8
|
+
* feat!(automation): schedules set by events ([53bc436](https://github.com/ReventlessDev/reventless-core/commit/53bc4363dffce81ef36475728bf0dae704716021))
|
|
9
|
+
* feat!(automation): declared schedules tick an automation ([420acd2](https://github.com/ReventlessDev/reventless-core/commit/420acd2233aadf700141a31d9d3e0fdc8074bef7))
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* **examples:** a marketing digest at the time each customer chooses ([3521b10](https://github.com/ReventlessDev/reventless-core/commit/3521b10c4f3ff64f3e4561e94e051892cdfe5d5f))
|
|
13
|
+
|
|
14
|
+
### BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
* a hand-written AutomationSlice.Mapping.Make the PPX does
|
|
17
|
+
not see must define `let reschedule = _ => []`.
|
|
18
|
+
* a hand-written AutomationSlice spec the PPX does not see must
|
|
19
|
+
declare `let schedule = None`.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
6
23
|
# 1.0.0-alpha.274 (2026-10-15)
|
|
7
24
|
|
|
8
25
|
* feat!: an automation's process takes ~now, and collect the event's recorded time ([8c8f3f5](https://github.com/ReventlessDev/reventless-core/commit/8c8f3f570474b1658b605b726077debc7d6a1dc7))
|
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.275",
|
|
4
4
|
"description": "Online Shop Hybrid example — Ordering plugin (Customer aggregate + Order/CatalogProduct DCB)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -21,20 +21,20 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"sury": "11.0.0",
|
|
24
|
-
"@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.
|
|
25
|
-
"@reventlessdev/
|
|
24
|
+
"@reventlessdev/online-shop-hybrid-catalog-spec": "1.0.0-alpha.162",
|
|
25
|
+
"@reventlessdev/online-shop-hybrid-ordering-spec": "1.0.0-alpha.161",
|
|
26
26
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.23",
|
|
27
|
-
"@reventlessdev/reventless-gwt": "1.0.0-alpha.
|
|
28
|
-
"@reventlessdev/
|
|
29
|
-
"@reventlessdev/reventless-local": "3.0.0-alpha.
|
|
30
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
31
|
-
"@reventlessdev/trait-
|
|
32
|
-
"@reventlessdev/trait-
|
|
27
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.244",
|
|
28
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.193",
|
|
29
|
+
"@reventlessdev/reventless-local": "3.0.0-alpha.287",
|
|
30
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.165",
|
|
31
|
+
"@reventlessdev/trait-notification": "1.0.0-alpha.46",
|
|
32
|
+
"@reventlessdev/trait-address-geocoding": "1.0.0-alpha.46"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"rescript": "12.3.1",
|
|
36
36
|
"sury-ppx": "11.0.0",
|
|
37
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
37
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.101"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"rescript": "^12.3.1"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// MarketingDigest AutomationSlice.
|
|
2
|
+
//
|
|
3
|
+
// A digest of the shop's offers, sent to each recipient at the time of day they
|
|
4
|
+
// chose. The slice declares no `schedule`: every time comes from an event, one per
|
|
5
|
+
// recipient, keyed by their id, so the tick names whose time came.
|
|
6
|
+
//
|
|
7
|
+
// Not the notification trait's `Digest` delivery, which gathers a rule's
|
|
8
|
+
// occurrences into one message per window. This one has no occurrences to gather;
|
|
9
|
+
// it is a message the shop sends on the recipient's clock.
|
|
10
|
+
|
|
11
|
+
@@reventless.spec
|
|
12
|
+
|
|
13
|
+
@schema
|
|
14
|
+
type todoItem = {recipientId: string}
|
|
15
|
+
|
|
16
|
+
@schema
|
|
17
|
+
type command =
|
|
18
|
+
| RequestNotification({
|
|
19
|
+
recipientId: string,
|
|
20
|
+
category: NotificationPreferences.category,
|
|
21
|
+
reference: string,
|
|
22
|
+
subjectType: string,
|
|
23
|
+
subjectRef: string,
|
|
24
|
+
subject: string,
|
|
25
|
+
body: string,
|
|
26
|
+
sourceId: string,
|
|
27
|
+
origin: NotificationPreferences.origin,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
let maxRetries = 3
|
|
31
|
+
let heartbeatInterval = 60
|
|
32
|
+
let targetName = "NotificationPreferences"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
5
|
+
import * as NotificationPreferences$OrderingPlugin from "../StateChange/NotificationPreferences.res.mjs";
|
|
6
|
+
|
|
7
|
+
let todoItemSchema = Sury.$schema(s => ({
|
|
8
|
+
recipientId: s.m(Sury.string)
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
let commandSchema = Sury.$schema(s => ({
|
|
12
|
+
TAG: "RequestNotification",
|
|
13
|
+
recipientId: s.m(DcbTag$Reventless.string),
|
|
14
|
+
category: s.m(NotificationPreferences$OrderingPlugin.categorySchema),
|
|
15
|
+
reference: s.m(Sury.string),
|
|
16
|
+
subjectType: s.m(Sury.string),
|
|
17
|
+
subjectRef: s.m(Sury.string),
|
|
18
|
+
subject: s.m(Sury.string),
|
|
19
|
+
body: s.m(Sury.string),
|
|
20
|
+
sourceId: s.m(DcbTag$Reventless.string),
|
|
21
|
+
origin: s.m(NotificationPreferences$OrderingPlugin.originSchema)
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
function authorizationOf(param) {
|
|
25
|
+
return "AllowAuthenticated";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function commandTransition(param) {
|
|
29
|
+
return "Undeclared";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let traits = [];
|
|
33
|
+
|
|
34
|
+
let name = "MarketingDigest";
|
|
35
|
+
|
|
36
|
+
let Id;
|
|
37
|
+
|
|
38
|
+
let maxRetries = 3;
|
|
39
|
+
|
|
40
|
+
let heartbeatInterval = 60;
|
|
41
|
+
|
|
42
|
+
let targetName = "NotificationPreferences";
|
|
43
|
+
|
|
44
|
+
let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Notification/Automation/MarketingDigest.res.mjs";
|
|
45
|
+
|
|
46
|
+
let schedule;
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
name,
|
|
50
|
+
Id,
|
|
51
|
+
todoItemSchema,
|
|
52
|
+
commandSchema,
|
|
53
|
+
maxRetries,
|
|
54
|
+
heartbeatInterval,
|
|
55
|
+
targetName,
|
|
56
|
+
moduleUrl,
|
|
57
|
+
authorizationOf,
|
|
58
|
+
commandTransition,
|
|
59
|
+
traits,
|
|
60
|
+
schedule,
|
|
61
|
+
}
|
|
62
|
+
/* todoItemSchema Not a pure module */
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
@@reventless.automation
|
|
2
|
+
|
|
3
|
+
// The recipient's choice and their unsubscribing set and clear their schedule;
|
|
4
|
+
// every outcome of a request finishes the day's digest, as in `NotificationIntake`.
|
|
5
|
+
module OrderingDcbSource = {
|
|
6
|
+
let name = "OrderingDcbEventLog"
|
|
7
|
+
|
|
8
|
+
@schema
|
|
9
|
+
type event =
|
|
10
|
+
| DigestTimeChosen({recipientId: string, hour: int, minute: int, zone: string})
|
|
11
|
+
| NotificationUnsubscribed({
|
|
12
|
+
recipientId: string,
|
|
13
|
+
category: NotificationPreferences.category,
|
|
14
|
+
channel: NotificationPreferences.channel,
|
|
15
|
+
})
|
|
16
|
+
| NotificationRequested({reference: string})
|
|
17
|
+
| NotificationSuppressed({reference: string})
|
|
18
|
+
| NotificationUndeliverable({reference: string})
|
|
19
|
+
| NotificationDeferred({reference: string})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The recipient's time of day, on their own clock. */
|
|
23
|
+
let dailyAt = (~hour, ~minute, ~zone) => Reventless.Schedule.Calendar({
|
|
24
|
+
calendar: Daily({hour, minute}),
|
|
25
|
+
zone: ?(zone == "" ? None : Some(zone)),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// The key is the recipient whose time came.
|
|
29
|
+
module FromClock = Mapping.Make(
|
|
30
|
+
Reventless.Clock,
|
|
31
|
+
MarketingDigest,
|
|
32
|
+
{
|
|
33
|
+
open Reventless.Clock
|
|
34
|
+
let collect = (Tick({key, at}), ~sourceId as _, _ctx) => [
|
|
35
|
+
(itemId(key, at), ({recipientId: key}: MarketingDigest.todoItem)),
|
|
36
|
+
]
|
|
37
|
+
let resolve = _ => None
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
module FromOrderingDcb = Mapping.Make(
|
|
42
|
+
OrderingDcbSource,
|
|
43
|
+
MarketingDigest,
|
|
44
|
+
{
|
|
45
|
+
open OrderingDcbSource
|
|
46
|
+
|
|
47
|
+
let collect = (_, ~sourceId as _, _ctx) => []
|
|
48
|
+
|
|
49
|
+
let resolve = event =>
|
|
50
|
+
switch event {
|
|
51
|
+
| NotificationRequested({reference})
|
|
52
|
+
| NotificationSuppressed({reference})
|
|
53
|
+
| NotificationUndeliverable({reference})
|
|
54
|
+
| NotificationDeferred({reference}) =>
|
|
55
|
+
Some(reference)
|
|
56
|
+
| DigestTimeChosen(_)
|
|
57
|
+
| NotificationUnsubscribed(_) =>
|
|
58
|
+
None
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Unsubscribing from marketing on any channel stops the digest: the request
|
|
62
|
+
// would be suppressed anyway, and a schedule that only ever sends nothing is a
|
|
63
|
+
// schedule nobody should pay for.
|
|
64
|
+
let reschedule = event =>
|
|
65
|
+
switch event {
|
|
66
|
+
| DigestTimeChosen({recipientId, hour, minute, zone}) => [
|
|
67
|
+
Set({key: recipientId, time: dailyAt(~hour, ~minute, ~zone)}),
|
|
68
|
+
]
|
|
69
|
+
| NotificationUnsubscribed({recipientId, category: Marketing}) => [Clear({key: recipientId})]
|
|
70
|
+
| _ => []
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
let mappings: array<module(Mapping)> = [module(FromClock), module(FromOrderingDcb)]
|
|
76
|
+
|
|
77
|
+
// The item id travels as the request's reference and comes back on its outcome.
|
|
78
|
+
// The wording is fixed: what the offers are is the catalog's, and this shop has
|
|
79
|
+
// no campaign component yet to say.
|
|
80
|
+
let process = (id, item: MarketingDigest.todoItem) => Some((
|
|
81
|
+
item.recipientId,
|
|
82
|
+
MarketingDigest.RequestNotification({
|
|
83
|
+
recipientId: item.recipientId,
|
|
84
|
+
category: Marketing,
|
|
85
|
+
reference: id,
|
|
86
|
+
subjectType: "",
|
|
87
|
+
subjectRef: "",
|
|
88
|
+
subject: "This week's offers",
|
|
89
|
+
body: "A look at what is new in the shop this week.",
|
|
90
|
+
sourceId: `${OrderingDcbSource.name}:DigestTimeChosen`,
|
|
91
|
+
origin: Default,
|
|
92
|
+
}),
|
|
93
|
+
))
|
|
94
|
+
|
|
95
|
+
// A day's digest that never got an answer is not worth announcing: tomorrow's
|
|
96
|
+
// comes regardless.
|
|
97
|
+
let onExhausted = (_id, _item) => None
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
5
|
+
import * as Clock$Reventless from "@reventlessdev/reventless-spec/src/components/Clock.res.mjs";
|
|
6
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
7
|
+
import * as AutomationSlice$Reventless from "@reventlessdev/reventless-spec/src/components/AutomationSlice.res.mjs";
|
|
8
|
+
import * as MarketingDigest$OrderingPlugin from "./MarketingDigest.res.mjs";
|
|
9
|
+
import * as NotificationPreferences$OrderingPlugin from "../StateChange/NotificationPreferences.res.mjs";
|
|
10
|
+
|
|
11
|
+
let M = AutomationSlice$Reventless.Mappings.Make({
|
|
12
|
+
name: MarketingDigest$OrderingPlugin.name,
|
|
13
|
+
moduleUrl: MarketingDigest$OrderingPlugin.moduleUrl,
|
|
14
|
+
todoItemSchema: MarketingDigest$OrderingPlugin.todoItemSchema,
|
|
15
|
+
commandSchema: MarketingDigest$OrderingPlugin.commandSchema,
|
|
16
|
+
maxRetries: MarketingDigest$OrderingPlugin.maxRetries,
|
|
17
|
+
heartbeatInterval: MarketingDigest$OrderingPlugin.heartbeatInterval,
|
|
18
|
+
targetName: MarketingDigest$OrderingPlugin.targetName,
|
|
19
|
+
schedule: undefined
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
let name = "OrderingDcbEventLog";
|
|
23
|
+
|
|
24
|
+
let eventSchema = Sury.union([
|
|
25
|
+
Sury.$schema(s => ({
|
|
26
|
+
TAG: "DigestTimeChosen",
|
|
27
|
+
recipientId: s.m(DcbTag$Reventless.string),
|
|
28
|
+
hour: s.m(Sury.int32),
|
|
29
|
+
minute: s.m(Sury.int32),
|
|
30
|
+
zone: s.m(Sury.string)
|
|
31
|
+
})),
|
|
32
|
+
Sury.$schema(s => ({
|
|
33
|
+
TAG: "NotificationUnsubscribed",
|
|
34
|
+
recipientId: s.m(DcbTag$Reventless.string),
|
|
35
|
+
category: s.m(NotificationPreferences$OrderingPlugin.categorySchema),
|
|
36
|
+
channel: s.m(NotificationPreferences$OrderingPlugin.channelSchema)
|
|
37
|
+
})),
|
|
38
|
+
Sury.$schema(s => ({
|
|
39
|
+
TAG: "NotificationRequested",
|
|
40
|
+
reference: s.m(Sury.string)
|
|
41
|
+
})),
|
|
42
|
+
Sury.$schema(s => ({
|
|
43
|
+
TAG: "NotificationSuppressed",
|
|
44
|
+
reference: s.m(Sury.string)
|
|
45
|
+
})),
|
|
46
|
+
Sury.$schema(s => ({
|
|
47
|
+
TAG: "NotificationUndeliverable",
|
|
48
|
+
reference: s.m(Sury.string)
|
|
49
|
+
})),
|
|
50
|
+
Sury.$schema(s => ({
|
|
51
|
+
TAG: "NotificationDeferred",
|
|
52
|
+
reference: s.m(Sury.string)
|
|
53
|
+
}))
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
let OrderingDcbSource = {
|
|
57
|
+
Id: undefined,
|
|
58
|
+
name: name,
|
|
59
|
+
eventSchema: eventSchema
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function dailyAt(hour, minute, zone) {
|
|
63
|
+
return {
|
|
64
|
+
TAG: "Calendar",
|
|
65
|
+
calendar: {
|
|
66
|
+
TAG: "Daily",
|
|
67
|
+
hour: hour,
|
|
68
|
+
minute: minute
|
|
69
|
+
},
|
|
70
|
+
zone: zone === "" ? undefined : zone
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function collect(param, param$1, _ctx) {
|
|
75
|
+
let key = param.key;
|
|
76
|
+
return [[
|
|
77
|
+
Clock$Reventless.itemId(key, param.at),
|
|
78
|
+
{
|
|
79
|
+
recipientId: key
|
|
80
|
+
}
|
|
81
|
+
]];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolve(param) {
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function reschedule(param) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let FromClock = AutomationSlice$Reventless.Mapping.Make({
|
|
93
|
+
Id: Id$Reventless.$$String,
|
|
94
|
+
name: Clock$Reventless.name,
|
|
95
|
+
eventSchema: Clock$Reventless.eventSchema
|
|
96
|
+
})({
|
|
97
|
+
name: MarketingDigest$OrderingPlugin.name,
|
|
98
|
+
moduleUrl: MarketingDigest$OrderingPlugin.moduleUrl,
|
|
99
|
+
todoItemSchema: MarketingDigest$OrderingPlugin.todoItemSchema,
|
|
100
|
+
commandSchema: MarketingDigest$OrderingPlugin.commandSchema,
|
|
101
|
+
maxRetries: MarketingDigest$OrderingPlugin.maxRetries,
|
|
102
|
+
heartbeatInterval: MarketingDigest$OrderingPlugin.heartbeatInterval,
|
|
103
|
+
targetName: MarketingDigest$OrderingPlugin.targetName,
|
|
104
|
+
schedule: undefined
|
|
105
|
+
})({
|
|
106
|
+
collect: collect,
|
|
107
|
+
resolve: resolve,
|
|
108
|
+
reschedule: reschedule
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
function collect$1(param, param$1, _ctx) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resolve$1(event) {
|
|
116
|
+
switch (event.TAG) {
|
|
117
|
+
case "DigestTimeChosen" :
|
|
118
|
+
case "NotificationUnsubscribed" :
|
|
119
|
+
return;
|
|
120
|
+
default:
|
|
121
|
+
return event.reference;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function reschedule$1(event) {
|
|
126
|
+
switch (event.TAG) {
|
|
127
|
+
case "DigestTimeChosen" :
|
|
128
|
+
return [{
|
|
129
|
+
TAG: "Set",
|
|
130
|
+
key: event.recipientId,
|
|
131
|
+
time: dailyAt(event.hour, event.minute, event.zone)
|
|
132
|
+
}];
|
|
133
|
+
case "NotificationUnsubscribed" :
|
|
134
|
+
switch (event.category) {
|
|
135
|
+
case "OrderConfirmation" :
|
|
136
|
+
case "ShippingUpdate" :
|
|
137
|
+
return [];
|
|
138
|
+
case "Marketing" :
|
|
139
|
+
return [{
|
|
140
|
+
TAG: "Clear",
|
|
141
|
+
key: event.recipientId
|
|
142
|
+
}];
|
|
143
|
+
}
|
|
144
|
+
default:
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let FromOrderingDcb = AutomationSlice$Reventless.Mapping.Make({
|
|
150
|
+
Id: Id$Reventless.$$String,
|
|
151
|
+
name: name,
|
|
152
|
+
eventSchema: eventSchema
|
|
153
|
+
})({
|
|
154
|
+
name: MarketingDigest$OrderingPlugin.name,
|
|
155
|
+
moduleUrl: MarketingDigest$OrderingPlugin.moduleUrl,
|
|
156
|
+
todoItemSchema: MarketingDigest$OrderingPlugin.todoItemSchema,
|
|
157
|
+
commandSchema: MarketingDigest$OrderingPlugin.commandSchema,
|
|
158
|
+
maxRetries: MarketingDigest$OrderingPlugin.maxRetries,
|
|
159
|
+
heartbeatInterval: MarketingDigest$OrderingPlugin.heartbeatInterval,
|
|
160
|
+
targetName: MarketingDigest$OrderingPlugin.targetName,
|
|
161
|
+
schedule: undefined
|
|
162
|
+
})({
|
|
163
|
+
collect: collect$1,
|
|
164
|
+
resolve: resolve$1,
|
|
165
|
+
reschedule: reschedule$1
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
let mappings = [
|
|
169
|
+
FromClock,
|
|
170
|
+
FromOrderingDcb
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
function process(id, item, param) {
|
|
174
|
+
return [
|
|
175
|
+
item.recipientId,
|
|
176
|
+
{
|
|
177
|
+
TAG: "RequestNotification",
|
|
178
|
+
recipientId: item.recipientId,
|
|
179
|
+
category: "Marketing",
|
|
180
|
+
reference: id,
|
|
181
|
+
subjectType: "",
|
|
182
|
+
subjectRef: "",
|
|
183
|
+
subject: "This week's offers",
|
|
184
|
+
body: "A look at what is new in the shop this week.",
|
|
185
|
+
sourceId: name + `:DigestTimeChosen`,
|
|
186
|
+
origin: "Default"
|
|
187
|
+
}
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function onExhausted(_id, _item) {
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let Spec;
|
|
196
|
+
|
|
197
|
+
let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Notification/Automation/MarketingDigest_Automation.res.mjs";
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
Spec,
|
|
201
|
+
M,
|
|
202
|
+
OrderingDcbSource,
|
|
203
|
+
dailyAt,
|
|
204
|
+
FromClock,
|
|
205
|
+
FromOrderingDcb,
|
|
206
|
+
mappings,
|
|
207
|
+
process,
|
|
208
|
+
onExhausted,
|
|
209
|
+
moduleUrl,
|
|
210
|
+
}
|
|
211
|
+
/* M Not a pure module */
|
|
@@ -46,6 +46,8 @@ let targetName = "NotificationPreferences";
|
|
|
46
46
|
|
|
47
47
|
let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Notification/Automation/NotificationIntake.res.mjs";
|
|
48
48
|
|
|
49
|
+
let schedule;
|
|
50
|
+
|
|
49
51
|
export {
|
|
50
52
|
name,
|
|
51
53
|
Id,
|
|
@@ -58,5 +60,6 @@ export {
|
|
|
58
60
|
authorizationOf,
|
|
59
61
|
commandTransition,
|
|
60
62
|
traits,
|
|
63
|
+
schedule,
|
|
61
64
|
}
|
|
62
65
|
/* todoItemSchema Not a pure module */
|
|
@@ -19,7 +19,8 @@ let M = AutomationSlice$Reventless.Mappings.Make({
|
|
|
19
19
|
commandSchema: NotificationIntake$OrderingPlugin.commandSchema,
|
|
20
20
|
maxRetries: NotificationIntake$OrderingPlugin.maxRetries,
|
|
21
21
|
heartbeatInterval: NotificationIntake$OrderingPlugin.heartbeatInterval,
|
|
22
|
-
targetName: NotificationIntake$OrderingPlugin.targetName
|
|
22
|
+
targetName: NotificationIntake$OrderingPlugin.targetName,
|
|
23
|
+
schedule: undefined
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
let name = "OrderingDcbEventLog";
|
|
@@ -132,6 +133,10 @@ function resolve(event) {
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
function reschedule(param) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
|
|
135
140
|
let FromOrderingDcb = AutomationSlice$Reventless.Mapping.Make({
|
|
136
141
|
Id: Id$Reventless.$$String,
|
|
137
142
|
name: name,
|
|
@@ -143,10 +148,12 @@ let FromOrderingDcb = AutomationSlice$Reventless.Mapping.Make({
|
|
|
143
148
|
commandSchema: NotificationIntake$OrderingPlugin.commandSchema,
|
|
144
149
|
maxRetries: NotificationIntake$OrderingPlugin.maxRetries,
|
|
145
150
|
heartbeatInterval: NotificationIntake$OrderingPlugin.heartbeatInterval,
|
|
146
|
-
targetName: NotificationIntake$OrderingPlugin.targetName
|
|
151
|
+
targetName: NotificationIntake$OrderingPlugin.targetName,
|
|
152
|
+
schedule: undefined
|
|
147
153
|
})({
|
|
148
154
|
collect: collect,
|
|
149
|
-
resolve: resolve
|
|
155
|
+
resolve: resolve,
|
|
156
|
+
reschedule: reschedule
|
|
150
157
|
});
|
|
151
158
|
|
|
152
159
|
let mappings = [FromOrderingDcb];
|
|
@@ -68,6 +68,12 @@ type command =
|
|
|
68
68
|
// the field with the authenticated caller before the command is published.
|
|
69
69
|
| Subscribe({@owner recipientId: string, category: category, channel: channel})
|
|
70
70
|
| Unsubscribe({@owner recipientId: string, category: category, channel: channel})
|
|
71
|
+
// When in the day the recipient wants the marketing digest, on their own clock:
|
|
72
|
+
// an hour and minute, read in `zone` (an IANA name such as "Europe/Berlin"; empty
|
|
73
|
+
// is UTC). Plain fields rather than a `Schedule.time`, because a person picks a
|
|
74
|
+
// time of day, and an input cannot carry a tagged union. `MarketingDigest` turns
|
|
75
|
+
// it into the recipient's schedule.
|
|
76
|
+
| ChooseDigestTime({@owner recipientId: string, hour: int, minute: int, zone: string})
|
|
71
77
|
// Also relayed: a notifiable thing happened. `reference` is the requester's own
|
|
72
78
|
// key for it, echoed back on whichever outcome follows, so the automation that
|
|
73
79
|
// asked can tell its work is finished. Opaque here on purpose — this slice must
|
|
@@ -112,12 +118,15 @@ type error =
|
|
|
112
118
|
// refusal, unlike the relayed commands, which record a fact instead: a person
|
|
113
119
|
// is at the other end of this one and can be told.
|
|
114
120
|
| RecipientUnknown
|
|
121
|
+
// An hour outside 0 to 23 or a minute outside 0 to 59.
|
|
122
|
+
| DigestTimeInvalid
|
|
115
123
|
|
|
116
124
|
@schema
|
|
117
125
|
type event =
|
|
118
126
|
| RecipientAnnounced({recipientId: string, email: string})
|
|
119
127
|
| NotificationSubscribed({recipientId: string, category: category, channel: channel})
|
|
120
128
|
| NotificationUnsubscribed({recipientId: string, category: category, channel: channel})
|
|
129
|
+
| DigestTimeChosen({recipientId: string, hour: int, minute: int, zone: string})
|
|
121
130
|
// The addressed message. `address` is the snapshot the send slice delivers to,
|
|
122
131
|
// and the reason this event exists rather than the send slice reading state.
|
|
123
132
|
| NotificationRequested({
|
|
@@ -74,6 +74,13 @@ let commandSchema = Sury.union([
|
|
|
74
74
|
category: s.m(categorySchema),
|
|
75
75
|
channel: s.m(channelSchema)
|
|
76
76
|
})),
|
|
77
|
+
Sury.$schema(s => ({
|
|
78
|
+
TAG: "ChooseDigestTime",
|
|
79
|
+
recipientId: s.m(Owner$Reventless.mark(DcbTag$Reventless.string)),
|
|
80
|
+
hour: s.m(Sury.int32),
|
|
81
|
+
minute: s.m(Sury.int32),
|
|
82
|
+
zone: s.m(Sury.string)
|
|
83
|
+
})),
|
|
77
84
|
Sury.$schema(s => ({
|
|
78
85
|
TAG: "RequestNotification",
|
|
79
86
|
recipientId: s.m(DcbTag$Reventless.string),
|
|
@@ -100,7 +107,10 @@ let commandSchema = Sury.union([
|
|
|
100
107
|
}))
|
|
101
108
|
]);
|
|
102
109
|
|
|
103
|
-
let errorSchema = Sury.
|
|
110
|
+
let errorSchema = Sury.union([
|
|
111
|
+
Sury.literal("RecipientUnknown"),
|
|
112
|
+
Sury.literal("DigestTimeInvalid")
|
|
113
|
+
]);
|
|
104
114
|
|
|
105
115
|
let eventSchema = Sury.union([
|
|
106
116
|
Sury.$schema(s => ({
|
|
@@ -120,6 +130,13 @@ let eventSchema = Sury.union([
|
|
|
120
130
|
category: s.m(categorySchema),
|
|
121
131
|
channel: s.m(channelSchema)
|
|
122
132
|
})),
|
|
133
|
+
Sury.$schema(s => ({
|
|
134
|
+
TAG: "DigestTimeChosen",
|
|
135
|
+
recipientId: s.m(DcbTag$Reventless.string),
|
|
136
|
+
hour: s.m(Sury.int32),
|
|
137
|
+
minute: s.m(Sury.int32),
|
|
138
|
+
zone: s.m(Sury.string)
|
|
139
|
+
})),
|
|
123
140
|
Sury.$schema(s => ({
|
|
124
141
|
TAG: "NotificationRequested",
|
|
125
142
|
recipientId: s.m(DcbTag$Reventless.string),
|
|
@@ -215,6 +215,17 @@ let decide = (state, command) =>
|
|
|
215
215
|
)
|
|
216
216
|
)
|
|
217
217
|
|
|
218
|
+
// Not the trait's: when a digest comes is this shop's offer, and the trait holds
|
|
219
|
+
// no time. Only a recipient the directory knows may choose one.
|
|
220
|
+
| ChooseDigestTime({recipientId, hour, minute, zone}) =>
|
|
221
|
+
if state.contacts->Array.length == 0 {
|
|
222
|
+
Error(RecipientUnknown)
|
|
223
|
+
} else if hour < 0 || hour > 23 || minute < 0 || minute > 59 {
|
|
224
|
+
Error(DigestTimeInvalid)
|
|
225
|
+
} else {
|
|
226
|
+
Ok([DigestTimeChosen({recipientId, hour, minute, zone})])
|
|
227
|
+
}
|
|
228
|
+
|
|
218
229
|
// Reported by the send slice once the provider has settled. No rule to state —
|
|
219
230
|
// the outcome is whatever the provider said — so these stay here rather than
|
|
220
231
|
// being pushed through a trait that would only pass them along.
|
|
@@ -209,6 +209,32 @@ function decide(state, command) {
|
|
|
209
209
|
category: categoryKey(command.category),
|
|
210
210
|
channel: channelKey(command.channel)
|
|
211
211
|
});
|
|
212
|
+
case "ChooseDigestTime" :
|
|
213
|
+
if (state.contacts.length === 0) {
|
|
214
|
+
return {
|
|
215
|
+
TAG: "Error",
|
|
216
|
+
_0: "RecipientUnknown"
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
let minute = command.minute;
|
|
220
|
+
let hour = command.hour;
|
|
221
|
+
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
|
222
|
+
return {
|
|
223
|
+
TAG: "Error",
|
|
224
|
+
_0: "DigestTimeInvalid"
|
|
225
|
+
};
|
|
226
|
+
} else {
|
|
227
|
+
return {
|
|
228
|
+
TAG: "Ok",
|
|
229
|
+
_0: [{
|
|
230
|
+
TAG: "DigestTimeChosen",
|
|
231
|
+
recipientId: command.recipientId,
|
|
232
|
+
hour: hour,
|
|
233
|
+
minute: minute,
|
|
234
|
+
zone: command.zone
|
|
235
|
+
}]
|
|
236
|
+
};
|
|
237
|
+
}
|
|
212
238
|
case "RequestNotification" :
|
|
213
239
|
let origin = command.origin;
|
|
214
240
|
let body = command.body;
|
|
@@ -35,6 +35,8 @@ let targetName = "ShipOrder";
|
|
|
35
35
|
|
|
36
36
|
let moduleUrl = "@reventlessdev/online-shop-hybrid-ordering/src/Order/Automation/AutoShipOrder.res.mjs";
|
|
37
37
|
|
|
38
|
+
let schedule;
|
|
39
|
+
|
|
38
40
|
export {
|
|
39
41
|
name,
|
|
40
42
|
Id,
|
|
@@ -47,5 +49,6 @@ export {
|
|
|
47
49
|
authorizationOf,
|
|
48
50
|
commandTransition,
|
|
49
51
|
traits,
|
|
52
|
+
schedule,
|
|
50
53
|
}
|
|
51
54
|
/* todoItemSchema Not a pure module */
|