@reventlessdev/reventless-spec 3.0.0-alpha.163 → 3.0.0-alpha.165
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 +33 -0
- package/package.json +1 -1
- package/schema/platform-api.graphql +1 -0
- package/src/components/AutomationSlice.res +37 -2
- package/src/components/AutomationSlice.res.mjs +2 -1
- package/src/components/Clock.res +38 -0
- package/src/components/Clock.res.mjs +28 -0
- package/src/components/Plugin.res +3 -0
- package/src/components/Plugin.res.mjs +2 -1
- package/src/generator/PluginGenerator.res +14 -2
- package/src/generator/PluginGenerator.res.mjs +18 -2
- package/src/types/Schedule.res +66 -22
- package/src/types/Schedule.res.mjs +110 -19
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,39 @@
|
|
|
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.165 (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
|
+
|
|
11
|
+
### BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* a hand-written AutomationSlice.Mapping.Make the PPX does
|
|
14
|
+
not see must define `let reschedule = _ => []`.
|
|
15
|
+
* a hand-written AutomationSlice spec the PPX does not see must
|
|
16
|
+
declare `let schedule = None`.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# 3.0.0-alpha.164 (2026-10-15)
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* **spec:** generate-plugin writes Plugin.res as the formatter prints it ([3fcfd2b](https://github.com/ReventlessDev/reventless-core/commit/3fcfd2b38632bb9550413f88f410766934b6f609))
|
|
25
|
+
* feat!: an automation's process takes ~now, and collect the event's recorded time ([8c8f3f5](https://github.com/ReventlessDev/reventless-core/commit/8c8f3f570474b1658b605b726077debc7d6a1dc7))
|
|
26
|
+
* feat!: schedules take a Schedule.time, run on EventBridge Scheduler on AWS ([2d74557](https://github.com/ReventlessDev/reventless-core/commit/2d74557a6304917ded8f2b9f4107af9298c91ac0))
|
|
27
|
+
|
|
28
|
+
### BREAKING CHANGES
|
|
29
|
+
|
|
30
|
+
* a process the PPX does not see — in an Automation module
|
|
31
|
+
written by hand, or a SliceSpec given to the deprecated Automation_GWT.Make
|
|
32
|
+
or Flow_GWT.AutomationStep — must take ~now.
|
|
33
|
+
* Schedule.rate is replaced by Schedule.time, and a
|
|
34
|
+
schedule's `rate` field is now `time`. A once-only time already past
|
|
35
|
+
never fires, locally as on AWS.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
6
39
|
# 3.0.0-alpha.163 (2026-10-12)
|
|
7
40
|
|
|
8
41
|
**Note:** Version bump only for package @reventlessdev/reventless-spec
|
package/package.json
CHANGED
|
@@ -72,6 +72,14 @@ module type Spec = {
|
|
|
72
72
|
|
|
73
73
|
/** Name of the aggregate or StateChangeSlice that receives the produced command. */
|
|
74
74
|
let targetName: string
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
The time this automation runs on, or `None` for one started by events only. A
|
|
78
|
+
declared time is the slice's schedule under `Clock.defaultKey`; each time it comes,
|
|
79
|
+
the slice's `Clock` mapping receives a `Tick`. The `@@reventless.spec` PPX injects
|
|
80
|
+
`None` when the spec declares none.
|
|
81
|
+
*/
|
|
82
|
+
let schedule: option<Schedule.time>
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
// ── Plan 04: Mixed-source mappings ───────────────────────────────────────────
|
|
@@ -111,8 +119,23 @@ type context = {
|
|
|
111
119
|
platformName: string,
|
|
112
120
|
pluginName: string,
|
|
113
121
|
sliceName: string,
|
|
122
|
+
/** When the source event was recorded, in milliseconds since 1970 (UTC): what a
|
|
123
|
+
due time is counted from. The runtime sets it for every event it delivers;
|
|
124
|
+
it is absent only in a context built by hand. */
|
|
125
|
+
recordedAt?: float,
|
|
114
126
|
}
|
|
115
127
|
|
|
128
|
+
/**
|
|
129
|
+
What an event does to one of the slice's schedules. `key` names the schedule:
|
|
130
|
+
`Clock.defaultKey` for the slice-wide one (replacing a declared time), or an id for a
|
|
131
|
+
time per entity, which the tick carries back to `collect`. `Set` replaces the key's
|
|
132
|
+
time, the last one winning; `Clear` of a key with no schedule does nothing. An
|
|
133
|
+
`After` is counted from when the event was recorded.
|
|
134
|
+
*/
|
|
135
|
+
type scheduleChange =
|
|
136
|
+
| Set({key: string, time: Schedule.time})
|
|
137
|
+
| Clear({key: string})
|
|
138
|
+
|
|
116
139
|
/**
|
|
117
140
|
A compiled per-source-to-AutomationSlice mapping.
|
|
118
141
|
|
|
@@ -144,6 +167,9 @@ module type Mapping = {
|
|
|
144
167
|
*/
|
|
145
168
|
let collect: (sourceEvent, ~sourceId: string, context) => array<(string, todoItem)>
|
|
146
169
|
let resolve: sourceEvent => option<string>
|
|
170
|
+
/** The schedules the event sets or clears. The `@@reventless.automation` PPX adds
|
|
171
|
+
`let reschedule = _ => []` to a mapping written without it. */
|
|
172
|
+
let reschedule: sourceEvent => array<scheduleChange>
|
|
147
173
|
}
|
|
148
174
|
|
|
149
175
|
/**
|
|
@@ -166,9 +192,14 @@ module type Automation = {
|
|
|
166
192
|
/**
|
|
167
193
|
Process: given a pending TODO item, produce a command.
|
|
168
194
|
The processor calls this for each pending item. Returns the target ID and command.
|
|
169
|
-
May return `None` to skip processing (e.g., wait for more data
|
|
195
|
+
May return `None` to skip processing (e.g., wait for more data, or a due time
|
|
196
|
+
not yet reached); the item stays pending and the sweep offers it again.
|
|
197
|
+
|
|
198
|
+
`~now` is the current time in milliseconds since 1970 (UTC), from Effect's
|
|
199
|
+
`Clock`, so a test can fix it. The `@@reventless.automation` PPX adds `~now as _`
|
|
200
|
+
to a `process` written without it.
|
|
170
201
|
*/
|
|
171
|
-
let process: (string, Spec.todoItem) => option<(string, Spec.command)>
|
|
202
|
+
let process: (string, Spec.todoItem, ~now: float) => option<(string, Spec.command)>
|
|
172
203
|
|
|
173
204
|
/**
|
|
174
205
|
The retry budget is spent: this item will never be processed again.
|
|
@@ -224,6 +255,9 @@ module type MappingImpl = {
|
|
|
224
255
|
*/
|
|
225
256
|
let collect: (sourceEvent, ~sourceId: string, context) => array<(string, todoItem)>
|
|
226
257
|
let resolve: sourceEvent => option<string>
|
|
258
|
+
/** The schedules the event sets or clears. The `@@reventless.automation` PPX adds
|
|
259
|
+
`let reschedule = _ => []` to a mapping written without it. */
|
|
260
|
+
let reschedule: sourceEvent => array<scheduleChange>
|
|
227
261
|
}
|
|
228
262
|
|
|
229
263
|
/**
|
|
@@ -305,6 +339,7 @@ module Mapping = {
|
|
|
305
339
|
let sourceName = Source.name
|
|
306
340
|
let collect = Impl.collect
|
|
307
341
|
let resolve = Impl.resolve
|
|
342
|
+
let reschedule = Impl.reschedule
|
|
308
343
|
}
|
|
309
344
|
}
|
|
310
345
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
The framework source a scheduled automation maps. When one of the slice's
|
|
3
|
+
schedules comes due, the platform's Scheduler fires and the slice receives
|
|
4
|
+
`Tick({key, at})`: which of its schedules fired (`default` for the declared one)
|
|
5
|
+
and the time it was scheduled for, in milliseconds since 1970 (UTC). What the tick
|
|
6
|
+
means is the slice's own: a mapping's `collect` makes the item, `process` sends the
|
|
7
|
+
command. Never written by an application.
|
|
8
|
+
|
|
9
|
+
@example
|
|
10
|
+
```rescript
|
|
11
|
+
module FromClock = Mapping.Make(
|
|
12
|
+
Reventless.Clock,
|
|
13
|
+
DemandReport,
|
|
14
|
+
{
|
|
15
|
+
open Reventless.Clock
|
|
16
|
+
let collect = (Tick({key, at}), ~sourceId as _, _ctx) =>
|
|
17
|
+
[(itemId(key, at), ({day: at}: DemandReport.todoItem))]
|
|
18
|
+
let resolve = _ => None
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
```
|
|
22
|
+
*/
|
|
23
|
+
module Id = Id.String
|
|
24
|
+
|
|
25
|
+
let name = "Clock"
|
|
26
|
+
|
|
27
|
+
@schema
|
|
28
|
+
type event = Tick({key: string, at: float})
|
|
29
|
+
|
|
30
|
+
/** The key a declared `schedule` is held under. */
|
|
31
|
+
let defaultKey = "default"
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
The to-do item id for a tick: `<key>@<scheduled time in UTC, to the minute>`, such
|
|
35
|
+
as `default@2026-10-16T06:00Z`. The same tick delivered twice names the same item.
|
|
36
|
+
*/
|
|
37
|
+
let itemId = (key, at) =>
|
|
38
|
+
`${key}@${at->Date.fromTime->Date.toISOString->String.slice(~start=0, ~end=16)}Z`
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
|
|
5
|
+
let eventSchema = Sury.$schema(s => ({
|
|
6
|
+
TAG: "Tick",
|
|
7
|
+
key: s.m(Sury.string),
|
|
8
|
+
at: s.m(Sury.number)
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
function itemId(key, at) {
|
|
12
|
+
return key + `@` + new Date(at).toISOString().slice(0, 16) + `Z`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let Id;
|
|
16
|
+
|
|
17
|
+
let name = "Clock";
|
|
18
|
+
|
|
19
|
+
let defaultKey = "default";
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
Id,
|
|
23
|
+
name,
|
|
24
|
+
eventSchema,
|
|
25
|
+
defaultKey,
|
|
26
|
+
itemId,
|
|
27
|
+
}
|
|
28
|
+
/* eventSchema Not a pure module */
|
|
@@ -301,6 +301,9 @@ type automationSliceDef = {
|
|
|
301
301
|
targetName: string,
|
|
302
302
|
/** Chapter grouping band — see `queryableDef.chapter`. */
|
|
303
303
|
chapter: option<string>,
|
|
304
|
+
/** The declared `schedule`, as the JSON of `Schedule.timeSchema`; `None` for an
|
|
305
|
+
automation started by events only, and in a structure from before the field. */
|
|
306
|
+
schedule?: string,
|
|
304
307
|
}
|
|
305
308
|
|
|
306
309
|
@schema
|
|
@@ -158,7 +158,8 @@ let automationSliceDefSchema = Sury.$schema(s => ({
|
|
|
158
158
|
consumedEventTypes: s.m(Sury.array(Sury.string)),
|
|
159
159
|
producedCommandTypes: s.m(Sury.array(Sury.string)),
|
|
160
160
|
targetName: s.m(Sury.string),
|
|
161
|
-
chapter: s.m(Sury.$option(Sury.string))
|
|
161
|
+
chapter: s.m(Sury.$option(Sury.string)),
|
|
162
|
+
schedule: s.m(Sury.$option(Sury.string))
|
|
162
163
|
}));
|
|
163
164
|
|
|
164
165
|
let outboundTranslationSliceDefSchema = Sury.$schema(s => ({
|
|
@@ -24,6 +24,18 @@ let parseArgs = (argv: array<string>): result<args, string> =>
|
|
|
24
24
|
|
|
25
25
|
let cli: CliArgs.cli<args> = {bin: "generate-plugin", usage, parse: parseArgs}
|
|
26
26
|
|
|
27
|
+
/** The generated files sit in the plugin's source tree, so they face its format
|
|
28
|
+
gate like any other source. Codegen lays text out for reading, not for the
|
|
29
|
+
formatter, so the formatter settles the layout before the file is written.
|
|
30
|
+
Raising beats falling back: unformatted output would build and then fail the
|
|
31
|
+
format gate on every regeneration. */
|
|
32
|
+
let formatted = (source: string, ~cwd: string): string =>
|
|
33
|
+
NodeChildProcess.execFileSync(
|
|
34
|
+
"pnpm",
|
|
35
|
+
["exec", "rescript", "format", "--stdin", ".res"],
|
|
36
|
+
{cwd, encoding: "utf8", input: source},
|
|
37
|
+
)
|
|
38
|
+
|
|
27
39
|
// A usage mistake exits non-zero, so `prebuild` (and CI) fail instead of
|
|
28
40
|
// continuing green with no Plugin.res generated.
|
|
29
41
|
let main = () =>
|
|
@@ -46,7 +58,7 @@ let main = () =>
|
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
let pluginPath = NodePath.join([outputDir, "Plugin.res"])
|
|
49
|
-
NodeFs.writeFileSync(pluginPath, source)
|
|
61
|
+
NodeFs.writeFileSync(pluginPath, source->formatted(~cwd=outputDir))
|
|
50
62
|
Console.log("Generated: " ++ pluginPath)
|
|
51
63
|
|
|
52
64
|
switch variant {
|
|
@@ -54,7 +66,7 @@ let main = () =>
|
|
|
54
66
|
| Config.Aws(_) =>
|
|
55
67
|
let mainSource = Codegen.renderMain(~config)
|
|
56
68
|
let mainPath = NodePath.join([outputDir, "Main.res"])
|
|
57
|
-
NodeFs.writeFileSync(mainPath, mainSource)
|
|
69
|
+
NodeFs.writeFileSync(mainPath, mainSource->formatted(~cwd=outputDir))
|
|
58
70
|
Console.log("Generated: " ++ mainPath)
|
|
59
71
|
}
|
|
60
72
|
})
|
|
@@ -7,6 +7,7 @@ import * as Config$Reventless from "./Config.res.mjs";
|
|
|
7
7
|
import * as CliArgs$Reventless from "../CliArgs.res.mjs";
|
|
8
8
|
import * as Codegen$Reventless from "./Codegen.res.mjs";
|
|
9
9
|
import * as Pairing$Reventless from "./Pairing.res.mjs";
|
|
10
|
+
import * as Nodechild_process from "node:child_process";
|
|
10
11
|
import * as Discovery$Reventless from "./Discovery.res.mjs";
|
|
11
12
|
|
|
12
13
|
let usage = `Usage: generate-plugin [--aws <Namespace>] <srcDir>
|
|
@@ -62,6 +63,20 @@ let cli = {
|
|
|
62
63
|
parse: parseArgs
|
|
63
64
|
};
|
|
64
65
|
|
|
66
|
+
function formatted(source, cwd) {
|
|
67
|
+
return Nodechild_process.execFileSync("pnpm", [
|
|
68
|
+
"exec",
|
|
69
|
+
"rescript",
|
|
70
|
+
"format",
|
|
71
|
+
"--stdin",
|
|
72
|
+
".res"
|
|
73
|
+
], {
|
|
74
|
+
cwd: cwd,
|
|
75
|
+
encoding: "utf8",
|
|
76
|
+
input: source
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
function main() {
|
|
66
81
|
return CliArgs$Reventless.run(cli, undefined, undefined, async param => {
|
|
67
82
|
let variant = param.variant;
|
|
@@ -86,14 +101,14 @@ function main() {
|
|
|
86
101
|
let outputDir;
|
|
87
102
|
outputDir = typeof variant !== "object" ? srcDir : Nodepath.join(process.cwd(), "src");
|
|
88
103
|
let pluginPath = Nodepath.join(outputDir, "Plugin.res");
|
|
89
|
-
Nodefs.writeFileSync(pluginPath, source, "utf8");
|
|
104
|
+
Nodefs.writeFileSync(pluginPath, formatted(source, outputDir), "utf8");
|
|
90
105
|
console.log("Generated: " + pluginPath);
|
|
91
106
|
if (typeof variant !== "object") {
|
|
92
107
|
return;
|
|
93
108
|
}
|
|
94
109
|
let mainSource = Codegen$Reventless.renderMain(config);
|
|
95
110
|
let mainPath = Nodepath.join(outputDir, "Main.res");
|
|
96
|
-
Nodefs.writeFileSync(mainPath, mainSource, "utf8");
|
|
111
|
+
Nodefs.writeFileSync(mainPath, formatted(mainSource, outputDir), "utf8");
|
|
97
112
|
console.log("Generated: " + mainPath);
|
|
98
113
|
});
|
|
99
114
|
}
|
|
@@ -102,6 +117,7 @@ export {
|
|
|
102
117
|
usage,
|
|
103
118
|
parseArgs,
|
|
104
119
|
cli,
|
|
120
|
+
formatted,
|
|
105
121
|
main,
|
|
106
122
|
}
|
|
107
123
|
/* node:fs Not a pure module */
|
package/src/types/Schedule.res
CHANGED
|
@@ -9,44 +9,88 @@ type hour = int
|
|
|
9
9
|
@schema
|
|
10
10
|
type minute = int
|
|
11
11
|
|
|
12
|
+
/** A fixed length, measured from the previous firing. */
|
|
13
|
+
@schema
|
|
14
|
+
type interval =
|
|
15
|
+
| Minutes(int)
|
|
16
|
+
| Hours(int)
|
|
17
|
+
| Days(int)
|
|
18
|
+
| Weeks(int)
|
|
19
|
+
|
|
20
|
+
@schema
|
|
21
|
+
type weekday = Mon | Tue | Wed | Thu | Fri | Sat | Sun
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
A wall-clock time that recurs. `Monthly` with `every: 3` fires in January, April, July
|
|
25
|
+
and October; a `day` a month does not have (the 31st) is skipped in that month.
|
|
26
|
+
*/
|
|
27
|
+
@schema
|
|
28
|
+
type calendar =
|
|
29
|
+
| Daily({hour: hour, minute: minute})
|
|
30
|
+
| Weekly({days: array<weekday>, hour: hour, minute: minute})
|
|
31
|
+
| Monthly({day: day, hour: hour, minute: minute, every?: int})
|
|
32
|
+
| Yearly({month: month, day: day, hour: hour, minute: minute})
|
|
33
|
+
|
|
34
|
+
/** A span counted once, from the moment a schedule is set; months and years by the calendar. */
|
|
35
|
+
@schema
|
|
36
|
+
type period =
|
|
37
|
+
| Minutes(int)
|
|
38
|
+
| Hours(int)
|
|
39
|
+
| Days(int)
|
|
40
|
+
| Weeks(int)
|
|
41
|
+
| Months(int)
|
|
42
|
+
| Years(int)
|
|
43
|
+
|
|
44
|
+
/** A wall-clock date and time, read in the schedule's zone. */
|
|
45
|
+
@schema
|
|
46
|
+
type localTime = {year: year, month: month, day: day, hour: hour, minute: minute}
|
|
47
|
+
|
|
48
|
+
/** `After` becomes `At` when the schedule is set. */
|
|
49
|
+
@schema
|
|
50
|
+
type moment = At(localTime) | After(period)
|
|
51
|
+
|
|
52
|
+
/** An IANA zone name such as `Europe/Berlin`; absent means UTC. */
|
|
53
|
+
@schema
|
|
54
|
+
type zone = string
|
|
55
|
+
|
|
12
56
|
/**
|
|
13
|
-
|
|
57
|
+
When a schedule fires: `Every` an interval, `Calendar` a wall-clock time that recurs,
|
|
58
|
+
or `Once` a single moment.
|
|
14
59
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- `Daily(hour, minute)` — fires every day at the given UTC time
|
|
18
|
-
- `Weekdays(hour, minute)` — fires Monday–Friday at the given UTC time
|
|
19
|
-
- `WeekdaysAndSaturday(hour, minute)` — fires Monday–Saturday at the given UTC time
|
|
60
|
+
A calendar time in the hour skipped by daylight saving does not fire that day; one in
|
|
61
|
+
the repeated hour fires once, at the first.
|
|
20
62
|
|
|
21
63
|
@example
|
|
22
64
|
```rescript
|
|
23
|
-
//
|
|
24
|
-
let
|
|
65
|
+
// every 15 minutes
|
|
66
|
+
let every = Schedule.Every(Minutes(15))
|
|
67
|
+
|
|
68
|
+
// weekdays at 07:30 in Berlin
|
|
69
|
+
let weekdays = Schedule.Calendar({
|
|
70
|
+
calendar: Weekly({days: [Mon, Tue, Wed, Thu, Fri], hour: 7, minute: 30}),
|
|
71
|
+
zone: "Europe/Berlin",
|
|
72
|
+
})
|
|
25
73
|
|
|
26
|
-
//
|
|
27
|
-
let
|
|
74
|
+
// once, ten minutes after it is set
|
|
75
|
+
let reminder = Schedule.Once({moment: After(Minutes(10))})
|
|
28
76
|
```
|
|
29
77
|
*/
|
|
30
78
|
@schema
|
|
31
|
-
type
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
| Days(int)
|
|
36
|
-
| Daily(hour, minute)
|
|
37
|
-
| Weekdays(hour, minute)
|
|
38
|
-
| WeekdaysAndSaturday(hour, minute)
|
|
79
|
+
type time =
|
|
80
|
+
| Every(interval)
|
|
81
|
+
| Calendar({calendar: calendar, zone?: zone})
|
|
82
|
+
| Once({moment: moment, zone?: zone})
|
|
39
83
|
|
|
40
84
|
/**
|
|
41
|
-
A named
|
|
85
|
+
A named schedule.
|
|
42
86
|
|
|
43
|
-
`payload` is an opaque string forwarded to the
|
|
87
|
+
`payload` is an opaque string forwarded to the target when the schedule fires.
|
|
44
88
|
|
|
45
89
|
@example
|
|
46
90
|
```rescript
|
|
47
91
|
let dailySync: Schedule.schedule = {
|
|
48
92
|
name: "DailyCatalogSync",
|
|
49
|
-
|
|
93
|
+
time: Calendar({calendar: Daily({hour: 2, minute: 0})}),
|
|
50
94
|
payload: `{"action": "sync"}`,
|
|
51
95
|
}
|
|
52
96
|
```
|
|
@@ -54,7 +98,7 @@ let dailySync: Schedule.schedule = {
|
|
|
54
98
|
@schema
|
|
55
99
|
type schedule = {
|
|
56
100
|
name: string,
|
|
57
|
-
|
|
101
|
+
time: time,
|
|
58
102
|
payload: string,
|
|
59
103
|
}
|
|
60
104
|
|
|
@@ -12,15 +12,7 @@ let hourSchema = Sury.int32;
|
|
|
12
12
|
|
|
13
13
|
let minuteSchema = Sury.int32;
|
|
14
14
|
|
|
15
|
-
let
|
|
16
|
-
Sury.$schema(s => ({
|
|
17
|
-
TAG: "Single",
|
|
18
|
-
_0: s.m(yearSchema),
|
|
19
|
-
_1: s.m(monthSchema),
|
|
20
|
-
_2: s.m(daySchema),
|
|
21
|
-
_3: s.m(hourSchema),
|
|
22
|
-
_4: s.m(minuteSchema)
|
|
23
|
-
})),
|
|
15
|
+
let intervalSchema = Sury.union([
|
|
24
16
|
Sury.$schema(s => ({
|
|
25
17
|
TAG: "Minutes",
|
|
26
18
|
_0: s.m(Sury.int32)
|
|
@@ -33,26 +25,118 @@ let rateSchema = Sury.union([
|
|
|
33
25
|
TAG: "Days",
|
|
34
26
|
_0: s.m(Sury.int32)
|
|
35
27
|
})),
|
|
28
|
+
Sury.$schema(s => ({
|
|
29
|
+
TAG: "Weeks",
|
|
30
|
+
_0: s.m(Sury.int32)
|
|
31
|
+
}))
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
let weekdaySchema = Sury.union([
|
|
35
|
+
Sury.literal("Mon"),
|
|
36
|
+
Sury.literal("Tue"),
|
|
37
|
+
Sury.literal("Wed"),
|
|
38
|
+
Sury.literal("Thu"),
|
|
39
|
+
Sury.literal("Fri"),
|
|
40
|
+
Sury.literal("Sat"),
|
|
41
|
+
Sury.literal("Sun")
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
let calendarSchema = Sury.union([
|
|
36
45
|
Sury.$schema(s => ({
|
|
37
46
|
TAG: "Daily",
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
hour: s.m(hourSchema),
|
|
48
|
+
minute: s.m(minuteSchema)
|
|
49
|
+
})),
|
|
50
|
+
Sury.$schema(s => ({
|
|
51
|
+
TAG: "Weekly",
|
|
52
|
+
days: s.m(Sury.array(weekdaySchema)),
|
|
53
|
+
hour: s.m(hourSchema),
|
|
54
|
+
minute: s.m(minuteSchema)
|
|
55
|
+
})),
|
|
56
|
+
Sury.$schema(s => ({
|
|
57
|
+
TAG: "Monthly",
|
|
58
|
+
day: s.m(daySchema),
|
|
59
|
+
hour: s.m(hourSchema),
|
|
60
|
+
minute: s.m(minuteSchema),
|
|
61
|
+
every: s.m(Sury.$option(Sury.int32))
|
|
62
|
+
})),
|
|
63
|
+
Sury.$schema(s => ({
|
|
64
|
+
TAG: "Yearly",
|
|
65
|
+
month: s.m(monthSchema),
|
|
66
|
+
day: s.m(daySchema),
|
|
67
|
+
hour: s.m(hourSchema),
|
|
68
|
+
minute: s.m(minuteSchema)
|
|
69
|
+
}))
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
let periodSchema = Sury.union([
|
|
73
|
+
Sury.$schema(s => ({
|
|
74
|
+
TAG: "Minutes",
|
|
75
|
+
_0: s.m(Sury.int32)
|
|
76
|
+
})),
|
|
77
|
+
Sury.$schema(s => ({
|
|
78
|
+
TAG: "Hours",
|
|
79
|
+
_0: s.m(Sury.int32)
|
|
80
|
+
})),
|
|
81
|
+
Sury.$schema(s => ({
|
|
82
|
+
TAG: "Days",
|
|
83
|
+
_0: s.m(Sury.int32)
|
|
84
|
+
})),
|
|
85
|
+
Sury.$schema(s => ({
|
|
86
|
+
TAG: "Weeks",
|
|
87
|
+
_0: s.m(Sury.int32)
|
|
88
|
+
})),
|
|
89
|
+
Sury.$schema(s => ({
|
|
90
|
+
TAG: "Months",
|
|
91
|
+
_0: s.m(Sury.int32)
|
|
92
|
+
})),
|
|
93
|
+
Sury.$schema(s => ({
|
|
94
|
+
TAG: "Years",
|
|
95
|
+
_0: s.m(Sury.int32)
|
|
96
|
+
}))
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
let localTimeSchema = Sury.$schema(s => ({
|
|
100
|
+
year: s.m(yearSchema),
|
|
101
|
+
month: s.m(monthSchema),
|
|
102
|
+
day: s.m(daySchema),
|
|
103
|
+
hour: s.m(hourSchema),
|
|
104
|
+
minute: s.m(minuteSchema)
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
let momentSchema = Sury.union([
|
|
108
|
+
Sury.$schema(s => ({
|
|
109
|
+
TAG: "At",
|
|
110
|
+
_0: s.m(localTimeSchema)
|
|
111
|
+
})),
|
|
112
|
+
Sury.$schema(s => ({
|
|
113
|
+
TAG: "After",
|
|
114
|
+
_0: s.m(periodSchema)
|
|
115
|
+
}))
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
let zoneSchema = Sury.string;
|
|
119
|
+
|
|
120
|
+
let timeSchema = Sury.union([
|
|
121
|
+
Sury.$schema(s => ({
|
|
122
|
+
TAG: "Every",
|
|
123
|
+
_0: s.m(intervalSchema)
|
|
40
124
|
})),
|
|
41
125
|
Sury.$schema(s => ({
|
|
42
|
-
TAG: "
|
|
43
|
-
|
|
44
|
-
|
|
126
|
+
TAG: "Calendar",
|
|
127
|
+
calendar: s.m(calendarSchema),
|
|
128
|
+
zone: s.m(Sury.$option(zoneSchema))
|
|
45
129
|
})),
|
|
46
130
|
Sury.$schema(s => ({
|
|
47
|
-
TAG: "
|
|
48
|
-
|
|
49
|
-
|
|
131
|
+
TAG: "Once",
|
|
132
|
+
moment: s.m(momentSchema),
|
|
133
|
+
zone: s.m(Sury.$option(zoneSchema))
|
|
50
134
|
}))
|
|
51
135
|
]);
|
|
52
136
|
|
|
53
137
|
let scheduleSchema = Sury.$schema(s => ({
|
|
54
138
|
name: s.m(Sury.string),
|
|
55
|
-
|
|
139
|
+
time: s.m(timeSchema),
|
|
56
140
|
payload: s.m(Sury.string)
|
|
57
141
|
}));
|
|
58
142
|
|
|
@@ -62,7 +146,14 @@ export {
|
|
|
62
146
|
daySchema,
|
|
63
147
|
hourSchema,
|
|
64
148
|
minuteSchema,
|
|
65
|
-
|
|
149
|
+
intervalSchema,
|
|
150
|
+
weekdaySchema,
|
|
151
|
+
calendarSchema,
|
|
152
|
+
periodSchema,
|
|
153
|
+
localTimeSchema,
|
|
154
|
+
momentSchema,
|
|
155
|
+
zoneSchema,
|
|
156
|
+
timeSchema,
|
|
66
157
|
scheduleSchema,
|
|
67
158
|
}
|
|
68
159
|
/* yearSchema Not a pure module */
|