@reventlessdev/reventless-core 3.0.0-alpha.294 → 3.0.0-alpha.296

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/package.json +8 -8
  3. package/src/admin/Platform_ComponentDefinitionsApi.res +3 -1
  4. package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +5 -1
  5. package/src/components/AutomationSlice/AutomationSlice.res +2 -0
  6. package/src/components/AutomationSlice/AutomationSlice_Builder.res +133 -8
  7. package/src/components/AutomationSlice/AutomationSlice_Builder.res.mjs +89 -7
  8. package/src/components/AutomationSlice/AutomationSlice_Callback.res +136 -3
  9. package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +122 -4
  10. package/src/components/AutomationSlice/AutomationSlice_Route.res +3 -1
  11. package/src/components/AutomationSlice/AutomationSlice_Route.res.mjs +2 -1
  12. package/src/components/AutomationSlice/AutomationSlice_Schedules.res +149 -0
  13. package/src/components/AutomationSlice/AutomationSlice_Schedules.res.mjs +193 -0
  14. package/src/components/Dcb/Dcb_Builder.res +28 -4
  15. package/src/components/Dcb/Dcb_Builder.res.mjs +27 -5
  16. package/src/plugin/component/Plugin_Builder.res +10 -3
  17. package/src/plugin/component/Plugin_Builder.res.mjs +8 -2
  18. package/src/plugin/component/Plugin_Structure.res +7 -0
  19. package/src/plugin/component/Plugin_Structure.res.mjs +5 -2
  20. package/src/plugin/connect/PluginExtensionPoint_Plugin.res +1 -1
  21. package/src/plugin/connect/PluginExtensionPoint_Plugin.res.mjs +1 -1
  22. package/src/util/ScheduleOps.res +1 -55
  23. package/src/util/ScheduleOps.res.mjs +10 -57
  24. package/src/util/ScheduleTime.res +266 -0
  25. package/src/util/ScheduleTime.res.mjs +451 -0
  26. package/tests/admin/AdminApiSchemaDriftTest.res +1 -0
  27. package/tests/admin/AdminApiSchemaDriftTest.res.mjs +4 -1
  28. package/tests/automationslice/AutomationSliceSchedulesTest.res +143 -0
  29. package/tests/automationslice/AutomationSliceSchedulesTest.res.mjs +247 -0
  30. package/tests/util/ScheduleTimeTest.res +256 -0
  31. package/tests/util/ScheduleTimeTest.res.mjs +532 -0
@@ -0,0 +1,266 @@
1
+ // When a `Schedule.time` fires, read the same way by the local timers and the AWS
2
+ // expression, so the two platforms agree. A calendar time in the hour skipped by
3
+ // daylight saving does not fire that day, as EventBridge Scheduler does.
4
+ open Reventless.Schedule
5
+
6
+ exception InvalidTime(string)
7
+
8
+ let utc = "UTC"
9
+ let zoneName = (zone: option<zone>) => zone->Option.getOr(utc)
10
+
11
+ let zoneOrThrow = name =>
12
+ switch DateTime.zoneMakeNamed(name) {
13
+ | Some(zone) => zone
14
+ | None => throw(InvalidTime(`unknown time zone "${name}"`))
15
+ }
16
+
17
+ let minuteMs = 60_000.
18
+
19
+ let intervalMs = (interval: interval) =>
20
+ switch interval {
21
+ | Minutes(n) => n->Int.toFloat *. minuteMs
22
+ | Hours(n) => n->Int.toFloat *. 60. *. minuteMs
23
+ | Days(n) => n->Int.toFloat *. 24. *. 60. *. minuteMs
24
+ | Weeks(n) => n->Int.toFloat *. 7. *. 24. *. 60. *. minuteMs
25
+ }
26
+
27
+ let weekdayIndex = (day: weekday) =>
28
+ switch day {
29
+ | Sun => 0
30
+ | Mon => 1
31
+ | Tue => 2
32
+ | Wed => 3
33
+ | Thu => 4
34
+ | Fri => 5
35
+ | Sat => 6
36
+ }
37
+
38
+ let weekdayName = (day: weekday) =>
39
+ switch day {
40
+ | Mon => "MON"
41
+ | Tue => "TUE"
42
+ | Wed => "WED"
43
+ | Thu => "THU"
44
+ | Fri => "FRI"
45
+ | Sat => "SAT"
46
+ | Sun => "SUN"
47
+ }
48
+
49
+ let errors = (time: time) => {
50
+ let errs = []
51
+ let check = (ok, message) => ok ? () : errs->Array.push(message)
52
+ let positive = (n, what) => check(n >= 1, `${what} must be at least 1, got ${n->Int.toString}`)
53
+ let clock = (hour, minute) => {
54
+ check(hour >= 0 && hour <= 23, `hour must be 0 to 23, got ${hour->Int.toString}`)
55
+ check(minute >= 0 && minute <= 59, `minute must be 0 to 59, got ${minute->Int.toString}`)
56
+ }
57
+ let dayOfMonth = day =>
58
+ check(day >= 1 && day <= 31, `day must be 1 to 31, got ${day->Int.toString}`)
59
+ let monthOfYear = month =>
60
+ check(month >= 1 && month <= 12, `month must be 1 to 12, got ${month->Int.toString}`)
61
+ let zoneKnown = zone =>
62
+ zone->Option.forEach(name =>
63
+ check(DateTime.zoneMakeNamed(name)->Option.isSome, `unknown time zone "${name}"`)
64
+ )
65
+ switch time {
66
+ | Every(Minutes(n) | Hours(n) | Days(n) | Weeks(n)) => positive(n, "an interval")
67
+ | Calendar({calendar, ?zone}) =>
68
+ zoneKnown(zone)
69
+ switch calendar {
70
+ | Daily({hour, minute}) => clock(hour, minute)
71
+ | Weekly({days, hour, minute}) =>
72
+ check(days->Array.length > 0, "a weekly time needs at least one day")
73
+ clock(hour, minute)
74
+ | Monthly({day, hour, minute, ?every}) =>
75
+ dayOfMonth(day)
76
+ clock(hour, minute)
77
+ every->Option.forEach(every =>
78
+ check(every >= 1 && every <= 12, `every must be 1 to 12, got ${every->Int.toString}`)
79
+ )
80
+ | Yearly({month, day, hour, minute}) =>
81
+ monthOfYear(month)
82
+ dayOfMonth(day)
83
+ clock(hour, minute)
84
+ }
85
+ | Once({moment, ?zone}) =>
86
+ zoneKnown(zone)
87
+ switch moment {
88
+ | At({month, day, hour, minute}) =>
89
+ monthOfYear(month)
90
+ dayOfMonth(day)
91
+ clock(hour, minute)
92
+ | After(Minutes(n) | Hours(n) | Days(n) | Weeks(n) | Months(n) | Years(n)) =>
93
+ positive(n, "a period")
94
+ }
95
+ }
96
+ errs
97
+ }
98
+
99
+ /** Throws `InvalidTime` naming every problem of `time`, for a schedule about to be set. */
100
+ let validate = time =>
101
+ switch time->errors {
102
+ | [] => ()
103
+ | errs => throw(InvalidTime(errs->Array.join("; ")))
104
+ }
105
+
106
+ let localTimeOf = (millis, ~zone): localTime => {
107
+ let parts = DateTime.makeZonedFromMillis(millis, ~zone)->DateTime.toParts
108
+ {
109
+ year: parts.year,
110
+ month: parts.month,
111
+ day: parts.day,
112
+ hour: parts.hours,
113
+ minute: parts.minutes,
114
+ }
115
+ }
116
+
117
+ /**
118
+ The moment a period after `from` falls on, in `zone`. Minutes and hours are elapsed time;
119
+ days and longer keep the wall-clock time, and a month from 31 January is the last day
120
+ of February.
121
+ */
122
+ let afterPeriod = (period: period, ~from, ~zone) => {
123
+ let start = DateTime.makeZonedFromMillis(from, ~zone)
124
+ switch period {
125
+ | Minutes(n) => from +. n->Int.toFloat *. minuteMs
126
+ | Hours(n) => from +. n->Int.toFloat *. 60. *. minuteMs
127
+ | Days(n) => start->DateTime.add({days: n})->DateTime.toEpochMillis
128
+ | Weeks(n) => start->DateTime.add({weeks: n})->DateTime.toEpochMillis
129
+ | Months(n) => start->DateTime.add({months: n})->DateTime.toEpochMillis
130
+ | Years(n) => start->DateTime.add({years: n})->DateTime.toEpochMillis
131
+ }
132
+ }
133
+
134
+ /** `time` with an `After` turned into the `At` it falls on when counted from `from`. */
135
+ let resolveOnce = (time, ~from) =>
136
+ switch time {
137
+ | Once({moment: After(period), ?zone}) =>
138
+ let tz = zoneOrThrow(zone->zoneName)
139
+ Once({moment: At(afterPeriod(period, ~from, ~zone=tz)->localTimeOf(~zone=tz)), ?zone})
140
+ | time => time
141
+ }
142
+
143
+ /** The instant an `At` falls on; a time in the skipped hour moves forward by the gap. */
144
+ let instantOf = ({year, month, day, hour, minute}: localTime, ~zone) =>
145
+ switch DateTime.makeZonedFromParts({year, month, day, hours: hour, minutes: minute}, ~zone) {
146
+ | Some(dateTime) => dateTime->DateTime.toEpochMillis
147
+ | None =>
148
+ throw(
149
+ InvalidTime(
150
+ `no such time: ${year->Int.toString}-${month->Int.toString}-${day->Int.toString}`,
151
+ ),
152
+ )
153
+ }
154
+
155
+ let wallClockOf = (calendar: calendar) =>
156
+ switch calendar {
157
+ | Daily({hour, minute})
158
+ | Weekly({hour, minute})
159
+ | Monthly({hour, minute})
160
+ | Yearly({hour, minute}) => (hour, minute)
161
+ }
162
+
163
+ let monthsEvery = every =>
164
+ every <= 1 ? [] : Array.fromInitializer(~length=11 / every + 1, i => 1 + i * every)
165
+
166
+ let cronOf = (calendar: calendar, ~zone): Cron.t => {
167
+ let (hour, minute) = calendar->wallClockOf
168
+ let at = (~days=[], ~months=[], ~weekdays=[]) =>
169
+ Cron.make({minutes: [minute], hours: [hour], days, months, weekdays, tz: zone})
170
+ switch calendar {
171
+ | Daily(_) => at()
172
+ | Weekly({days}) => at(~weekdays=days->Array.map(weekdayIndex))
173
+ | Monthly({day, ?every}) => at(~days=[day], ~months=every->Option.getOr(1)->monthsEvery)
174
+ | Yearly({month, day}) => at(~days=[day], ~months=[month])
175
+ }
176
+ }
177
+
178
+ /**
179
+ The first firing of `cron` strictly after `after` whose wall-clock time is
180
+ `hour:minute`: a time the skipped hour moved is passed over.
181
+ */
182
+ let nextOnWallClock = (cron, ~after, ~hour, ~minute, ~zone) => {
183
+ let rec loop = (from, tries) => {
184
+ let next = cron->Cron.next(Date.fromTime(from))->Date.getTime
185
+ let parts = DateTime.makeZonedFromMillis(next, ~zone)->DateTime.toParts
186
+ (parts.hours == hour && parts.minutes == minute) || tries >= 8 ? next : loop(next, tries + 1)
187
+ }
188
+ loop(after, 0)
189
+ }
190
+
191
+ /**
192
+ The first firing of `time` strictly after `after`, or `None` when a once-only time is
193
+ already past. For `Every`, `after` is the previous firing, or the moment it was set.
194
+ */
195
+ let nextFiring = (time, ~after) =>
196
+ switch time->resolveOnce(~from=after) {
197
+ | Every(interval) => Some(after +. interval->intervalMs)
198
+ | Calendar({calendar, ?zone}) =>
199
+ let tz = zoneOrThrow(zone->zoneName)
200
+ let (hour, minute) = calendar->wallClockOf
201
+ Some(calendar->cronOf(~zone=tz)->nextOnWallClock(~after, ~hour, ~minute, ~zone=tz))
202
+ | Once({moment: At(localTime), ?zone}) =>
203
+ let at = localTime->instantOf(~zone=zoneOrThrow(zone->zoneName))
204
+ at > after ? Some(at) : None
205
+ | Once({moment: After(_)}) => None
206
+ }
207
+
208
+ /** What EventBridge Scheduler is given for a time. */
209
+ type awsSchedule = {
210
+ expression: string,
211
+ timezone: option<string>,
212
+ startDate: option<float>,
213
+ deleteAfterCompletion: bool,
214
+ }
215
+
216
+ let pad2 = n => n < 10 ? `0${n->Int.toString}` : n->Int.toString
217
+
218
+ let rateOf = (n, unit) => `rate(${n->Int.toString} ${unit}${n == 1 ? "" : "s"})`
219
+
220
+ let cronExpressionOf = (calendar: calendar) => {
221
+ let (hour, minute) = calendar->wallClockOf
222
+ let fields = switch calendar {
223
+ | Daily(_) => "* * ? *"
224
+ | Weekly({days}) => `? * ${days->Array.map(weekdayName)->Array.join(",")} *`
225
+ | Monthly({day, ?every}) =>
226
+ let month = switch every {
227
+ | Some(every) if every > 1 => `1/${every->Int.toString}`
228
+ | _ => "*"
229
+ }
230
+ `${day->Int.toString} ${month} ? *`
231
+ | Yearly({month, day}) => `${day->Int.toString} ${month->Int.toString} ? *`
232
+ }
233
+ `cron(${minute->Int.toString} ${hour->Int.toString} ${fields})`
234
+ }
235
+
236
+ /**
237
+ The EventBridge Scheduler form of `time`, set at `setAt`. An `Every` starts one interval
238
+ later, as the local timer does; without a start date AWS fires at once.
239
+ */
240
+ let toAws = (time, ~setAt) =>
241
+ switch time->resolveOnce(~from=setAt) {
242
+ | Every(interval) => {
243
+ expression: switch interval {
244
+ | Minutes(n) => rateOf(n, "minute")
245
+ | Hours(n) => rateOf(n, "hour")
246
+ | Days(n) => rateOf(n, "day")
247
+ | Weeks(n) => rateOf(n * 7, "day")
248
+ },
249
+ timezone: None,
250
+ startDate: Some(setAt +. interval->intervalMs),
251
+ deleteAfterCompletion: false,
252
+ }
253
+ | Calendar({calendar, ?zone}) => {
254
+ expression: calendar->cronExpressionOf,
255
+ timezone: Some(zone->zoneName),
256
+ startDate: None,
257
+ deleteAfterCompletion: false,
258
+ }
259
+ | Once({moment: At({year, month, day, hour, minute}), ?zone}) => {
260
+ expression: `at(${year->Int.toString}-${month->pad2}-${day->pad2}T${hour->pad2}:${minute->pad2}:00)`,
261
+ timezone: Some(zone->zoneName),
262
+ startDate: None,
263
+ deleteAfterCompletion: true,
264
+ }
265
+ | Once({moment: After(_)}) => throw(InvalidTime("unresolved once-only time"))
266
+ }
@@ -0,0 +1,451 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as DateTime from "@reventlessdev/rescript-effect/src/DateTime.res.mjs";
4
+ import * as Cron from "effect/Cron";
5
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
+ import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
7
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
+ import * as DateTime$1 from "effect/DateTime";
9
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
10
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
11
+
12
+ let InvalidTime = /* @__PURE__ */Primitive_exceptions.create("ScheduleTime-ReventlessCore.InvalidTime");
13
+
14
+ let utc = "UTC";
15
+
16
+ function zoneName(zone) {
17
+ return Stdlib_Option.getOr(zone, utc);
18
+ }
19
+
20
+ function zoneOrThrow(name) {
21
+ let zone = DateTime.zoneMakeNamed(name);
22
+ if (zone !== undefined) {
23
+ return Primitive_option.valFromOption(zone);
24
+ }
25
+ throw {
26
+ RE_EXN_ID: InvalidTime,
27
+ _1: `unknown time zone "` + name + `"`,
28
+ Error: new Error()
29
+ };
30
+ }
31
+
32
+ function intervalMs(interval) {
33
+ switch (interval.TAG) {
34
+ case "Minutes" :
35
+ return interval._0 * 60000;
36
+ case "Hours" :
37
+ return interval._0 * 60 * 60000;
38
+ case "Days" :
39
+ return interval._0 * 24 * 60 * 60000;
40
+ case "Weeks" :
41
+ return interval._0 * 7 * 24 * 60 * 60000;
42
+ }
43
+ }
44
+
45
+ function weekdayIndex(day) {
46
+ switch (day) {
47
+ case "Mon" :
48
+ return 1;
49
+ case "Tue" :
50
+ return 2;
51
+ case "Wed" :
52
+ return 3;
53
+ case "Thu" :
54
+ return 4;
55
+ case "Fri" :
56
+ return 5;
57
+ case "Sat" :
58
+ return 6;
59
+ case "Sun" :
60
+ return 0;
61
+ }
62
+ }
63
+
64
+ function weekdayName(day) {
65
+ switch (day) {
66
+ case "Mon" :
67
+ return "MON";
68
+ case "Tue" :
69
+ return "TUE";
70
+ case "Wed" :
71
+ return "WED";
72
+ case "Thu" :
73
+ return "THU";
74
+ case "Fri" :
75
+ return "FRI";
76
+ case "Sat" :
77
+ return "SAT";
78
+ case "Sun" :
79
+ return "SUN";
80
+ }
81
+ }
82
+
83
+ function errors(time) {
84
+ let errs = [];
85
+ let check = (ok, message) => {
86
+ if (ok) {
87
+ return;
88
+ } else {
89
+ errs.push(message);
90
+ return;
91
+ }
92
+ };
93
+ let positive = (n, what) => check(n >= 1, what + ` must be at least 1, got ` + n.toString());
94
+ let clock = (hour, minute) => {
95
+ check(hour >= 0 && hour <= 23, `hour must be 0 to 23, got ` + hour.toString());
96
+ check(minute >= 0 && minute <= 59, `minute must be 0 to 59, got ` + minute.toString());
97
+ };
98
+ let dayOfMonth = day => check(day >= 1 && day <= 31, `day must be 1 to 31, got ` + day.toString());
99
+ let monthOfYear = month => check(month >= 1 && month <= 12, `month must be 1 to 12, got ` + month.toString());
100
+ let zoneKnown = zone => Stdlib_Option.forEach(zone, name => check(Stdlib_Option.isSome(DateTime.zoneMakeNamed(name)), `unknown time zone "` + name + `"`));
101
+ switch (time.TAG) {
102
+ case "Every" :
103
+ positive(time._0._0, "an interval");
104
+ break;
105
+ case "Calendar" :
106
+ let calendar = time.calendar;
107
+ zoneKnown(time.zone);
108
+ switch (calendar.TAG) {
109
+ case "Daily" :
110
+ clock(calendar.hour, calendar.minute);
111
+ break;
112
+ case "Weekly" :
113
+ check(calendar.days.length !== 0, "a weekly time needs at least one day");
114
+ clock(calendar.hour, calendar.minute);
115
+ break;
116
+ case "Monthly" :
117
+ dayOfMonth(calendar.day);
118
+ clock(calendar.hour, calendar.minute);
119
+ Stdlib_Option.forEach(calendar.every, every => check(every >= 1 && every <= 12, `every must be 1 to 12, got ` + every.toString()));
120
+ break;
121
+ case "Yearly" :
122
+ monthOfYear(calendar.month);
123
+ dayOfMonth(calendar.day);
124
+ clock(calendar.hour, calendar.minute);
125
+ break;
126
+ }
127
+ break;
128
+ case "Once" :
129
+ let moment = time.moment;
130
+ zoneKnown(time.zone);
131
+ if (moment.TAG === "At") {
132
+ let match = moment._0;
133
+ monthOfYear(match.month);
134
+ dayOfMonth(match.day);
135
+ clock(match.hour, match.minute);
136
+ } else {
137
+ positive(moment._0._0, "a period");
138
+ }
139
+ break;
140
+ }
141
+ return errs;
142
+ }
143
+
144
+ function validate(time) {
145
+ let errs = errors(time);
146
+ if (errs.length === 0) {
147
+ return;
148
+ }
149
+ throw {
150
+ RE_EXN_ID: InvalidTime,
151
+ _1: errs.join("; "),
152
+ Error: new Error()
153
+ };
154
+ }
155
+
156
+ function localTimeOf(millis, zone) {
157
+ let parts = DateTime$1.toParts(DateTime.makeZonedFromMillis(millis, zone));
158
+ return {
159
+ year: parts.year,
160
+ month: parts.month,
161
+ day: parts.day,
162
+ hour: parts.hours,
163
+ minute: parts.minutes
164
+ };
165
+ }
166
+
167
+ function afterPeriod(period, from, zone) {
168
+ let start = DateTime.makeZonedFromMillis(from, zone);
169
+ switch (period.TAG) {
170
+ case "Minutes" :
171
+ return from + period._0 * 60000;
172
+ case "Hours" :
173
+ return from + period._0 * 60 * 60000;
174
+ case "Days" :
175
+ return DateTime$1.toEpochMillis(DateTime$1.add(start, {
176
+ days: period._0
177
+ }));
178
+ case "Weeks" :
179
+ return DateTime$1.toEpochMillis(DateTime$1.add(start, {
180
+ weeks: period._0
181
+ }));
182
+ case "Months" :
183
+ return DateTime$1.toEpochMillis(DateTime$1.add(start, {
184
+ months: period._0
185
+ }));
186
+ case "Years" :
187
+ return DateTime$1.toEpochMillis(DateTime$1.add(start, {
188
+ years: period._0
189
+ }));
190
+ }
191
+ }
192
+
193
+ function resolveOnce(time, from) {
194
+ switch (time.TAG) {
195
+ case "Every" :
196
+ case "Calendar" :
197
+ return time;
198
+ case "Once" :
199
+ let period = time.moment;
200
+ if (period.TAG === "At") {
201
+ return time;
202
+ }
203
+ let zone = time.zone;
204
+ let tz = zoneOrThrow(Stdlib_Option.getOr(zone, utc));
205
+ return {
206
+ TAG: "Once",
207
+ moment: {
208
+ TAG: "At",
209
+ _0: localTimeOf(afterPeriod(period._0, from, tz), tz)
210
+ },
211
+ zone: zone
212
+ };
213
+ }
214
+ }
215
+
216
+ function instantOf(param, zone) {
217
+ let day = param.day;
218
+ let month = param.month;
219
+ let year = param.year;
220
+ let dateTime = DateTime.makeZonedFromParts({
221
+ year: year,
222
+ month: month,
223
+ day: day,
224
+ hours: param.hour,
225
+ minutes: param.minute
226
+ }, zone);
227
+ if (dateTime !== undefined) {
228
+ return DateTime$1.toEpochMillis(Primitive_option.valFromOption(dateTime));
229
+ }
230
+ throw {
231
+ RE_EXN_ID: InvalidTime,
232
+ _1: `no such time: ` + year.toString() + `-` + month.toString() + `-` + day.toString(),
233
+ Error: new Error()
234
+ };
235
+ }
236
+
237
+ function wallClockOf(calendar) {
238
+ switch (calendar.TAG) {
239
+ case "Daily" :
240
+ return [
241
+ calendar.hour,
242
+ calendar.minute
243
+ ];
244
+ case "Weekly" :
245
+ case "Monthly" :
246
+ return [
247
+ calendar.hour,
248
+ calendar.minute
249
+ ];
250
+ case "Yearly" :
251
+ return [
252
+ calendar.hour,
253
+ calendar.minute
254
+ ];
255
+ }
256
+ }
257
+
258
+ function monthsEvery(every) {
259
+ if (every <= 1) {
260
+ return [];
261
+ } else {
262
+ return Stdlib_Array.fromInitializer(Primitive_int.div(11, every) + 1 | 0, i => 1 + (i * every | 0) | 0);
263
+ }
264
+ }
265
+
266
+ function cronOf(calendar, zone) {
267
+ let match = wallClockOf(calendar);
268
+ let minute = match[1];
269
+ let hour = match[0];
270
+ let at = (daysOpt, monthsOpt, weekdaysOpt) => {
271
+ let days = daysOpt !== undefined ? daysOpt : [];
272
+ let months = monthsOpt !== undefined ? monthsOpt : [];
273
+ let weekdays = weekdaysOpt !== undefined ? weekdaysOpt : [];
274
+ return Cron.make({
275
+ minutes: [minute],
276
+ hours: [hour],
277
+ days: days,
278
+ months: months,
279
+ weekdays: weekdays,
280
+ tz: zone
281
+ });
282
+ };
283
+ switch (calendar.TAG) {
284
+ case "Daily" :
285
+ return at(undefined, undefined, undefined);
286
+ case "Weekly" :
287
+ return at(undefined, undefined, calendar.days.map(weekdayIndex));
288
+ case "Monthly" :
289
+ return at([calendar.day], monthsEvery(Stdlib_Option.getOr(calendar.every, 1)), undefined);
290
+ case "Yearly" :
291
+ return at([calendar.day], [calendar.month], undefined);
292
+ }
293
+ }
294
+
295
+ function nextOnWallClock(cron, after, hour, minute, zone) {
296
+ let _from = after;
297
+ let _tries = 0;
298
+ while (true) {
299
+ let tries = _tries;
300
+ let from = _from;
301
+ let next = Cron.next(cron, new Date(from)).getTime();
302
+ let parts = DateTime$1.toParts(DateTime.makeZonedFromMillis(next, zone));
303
+ if (parts.hours === hour && parts.minutes === minute || tries >= 8) {
304
+ return next;
305
+ }
306
+ _tries = tries + 1 | 0;
307
+ _from = next;
308
+ continue;
309
+ };
310
+ }
311
+
312
+ function nextFiring(time, after) {
313
+ let interval = resolveOnce(time, after);
314
+ switch (interval.TAG) {
315
+ case "Every" :
316
+ return after + intervalMs(interval._0);
317
+ case "Calendar" :
318
+ let calendar = interval.calendar;
319
+ let tz = zoneOrThrow(Stdlib_Option.getOr(interval.zone, utc));
320
+ let match = wallClockOf(calendar);
321
+ return nextOnWallClock(cronOf(calendar, tz), after, match[0], match[1], tz);
322
+ case "Once" :
323
+ let localTime = interval.moment;
324
+ if (localTime.TAG !== "At") {
325
+ return;
326
+ }
327
+ let at = instantOf(localTime._0, zoneOrThrow(Stdlib_Option.getOr(interval.zone, utc)));
328
+ if (at > after) {
329
+ return at;
330
+ } else {
331
+ return;
332
+ }
333
+ }
334
+ }
335
+
336
+ function pad2(n) {
337
+ if (n < 10) {
338
+ return `0` + n.toString();
339
+ } else {
340
+ return n.toString();
341
+ }
342
+ }
343
+
344
+ function rateOf(n, unit) {
345
+ return `rate(` + n.toString() + ` ` + unit + (
346
+ n === 1 ? "" : "s"
347
+ ) + `)`;
348
+ }
349
+
350
+ function cronExpressionOf(calendar) {
351
+ let match = wallClockOf(calendar);
352
+ let fields;
353
+ switch (calendar.TAG) {
354
+ case "Daily" :
355
+ fields = "* * ? *";
356
+ break;
357
+ case "Weekly" :
358
+ fields = `? * ` + calendar.days.map(weekdayName).join(",") + ` *`;
359
+ break;
360
+ case "Monthly" :
361
+ let every = calendar.every;
362
+ let month = every !== undefined && every > 1 ? `1/` + every.toString() : "*";
363
+ fields = calendar.day.toString() + ` ` + month + ` ? *`;
364
+ break;
365
+ case "Yearly" :
366
+ fields = calendar.day.toString() + ` ` + calendar.month.toString() + ` ? *`;
367
+ break;
368
+ }
369
+ return `cron(` + match[1].toString() + ` ` + match[0].toString() + ` ` + fields + `)`;
370
+ }
371
+
372
+ function toAws(time, setAt) {
373
+ let interval = resolveOnce(time, setAt);
374
+ switch (interval.TAG) {
375
+ case "Every" :
376
+ let interval$1 = interval._0;
377
+ let tmp;
378
+ switch (interval$1.TAG) {
379
+ case "Minutes" :
380
+ tmp = rateOf(interval$1._0, "minute");
381
+ break;
382
+ case "Hours" :
383
+ tmp = rateOf(interval$1._0, "hour");
384
+ break;
385
+ case "Days" :
386
+ tmp = rateOf(interval$1._0, "day");
387
+ break;
388
+ case "Weeks" :
389
+ tmp = rateOf(interval$1._0 * 7 | 0, "day");
390
+ break;
391
+ }
392
+ return {
393
+ expression: tmp,
394
+ timezone: undefined,
395
+ startDate: setAt + intervalMs(interval$1),
396
+ deleteAfterCompletion: false
397
+ };
398
+ case "Calendar" :
399
+ return {
400
+ expression: cronExpressionOf(interval.calendar),
401
+ timezone: Stdlib_Option.getOr(interval.zone, utc),
402
+ startDate: undefined,
403
+ deleteAfterCompletion: false
404
+ };
405
+ case "Once" :
406
+ let match = interval.moment;
407
+ if (match.TAG === "At") {
408
+ let match$1 = match._0;
409
+ return {
410
+ expression: `at(` + match$1.year.toString() + `-` + pad2(match$1.month) + `-` + pad2(match$1.day) + `T` + pad2(match$1.hour) + `:` + pad2(match$1.minute) + `:00)`,
411
+ timezone: Stdlib_Option.getOr(interval.zone, utc),
412
+ startDate: undefined,
413
+ deleteAfterCompletion: true
414
+ };
415
+ }
416
+ throw {
417
+ RE_EXN_ID: InvalidTime,
418
+ _1: "unresolved once-only time",
419
+ Error: new Error()
420
+ };
421
+ }
422
+ }
423
+
424
+ let minuteMs = 60000;
425
+
426
+ export {
427
+ InvalidTime,
428
+ utc,
429
+ zoneName,
430
+ zoneOrThrow,
431
+ minuteMs,
432
+ intervalMs,
433
+ weekdayIndex,
434
+ weekdayName,
435
+ errors,
436
+ validate,
437
+ localTimeOf,
438
+ afterPeriod,
439
+ resolveOnce,
440
+ instantOf,
441
+ wallClockOf,
442
+ monthsEvery,
443
+ cronOf,
444
+ nextOnWallClock,
445
+ nextFiring,
446
+ pad2,
447
+ rateOf,
448
+ cronExpressionOf,
449
+ toAws,
450
+ }
451
+ /* DateTime Not a pure module */