Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions __tests__/CronJob_tests.res
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,50 @@ describe("changing CronJobs", () => {

expect(LuxonDateTime.toMillis(nextTick))->toEqual(LuxonDateTime.toMillis(nextAssumedTick))
})

test("nextDate returns the next single date", () => {
let onTick = _ => ()

RescriptJestDateMock.advanceTo(Date.fromString("2010-01-01T12:00:00.000Z"))

let job = RescriptCron.CronJob.make(#JsDate(Date.fromString(futureDate)), onTick, ())
let time = RescriptCron.CronTime.make(#CronString("0 0 12 20 jan *"), ())

let nextAssumedTick =
LuxonDateTime.fromISO("2010-01-01T12:00:00.000Z")->LuxonDateTime.set({day: 20, hour: 12})

RescriptCron.setTime(job, time)

let nextTick = RescriptCron.nextDate(job)

expect(LuxonDateTime.toMillis(nextTick))->toEqual(LuxonDateTime.toMillis(nextAssumedTick))
})

test("isActive reflects job start and stop state", () => {
let onTick = _ => ()

RescriptJestDateMock.advanceTo(Date.fromString(pastDate))

let job = RescriptCron.CronJob.make(#JsDate(Date.fromString(futureDate)), onTick, ())

expect(RescriptCron.isActive(job))->toEqual(false)->ignore

RescriptCron.start(job)
expect(RescriptCron.isActive(job))->toEqual(true)->ignore

RescriptCron.stop(job)
expect(RescriptCron.isActive(job))->toEqual(false)
})
})

describe("validateCronExpression", () => {
test("returns valid for a correct cron expression", () => {
let result = RescriptCron.validateCronExpression("0 0 * * *")
expect(result.valid)->toEqual(true)
})

test("returns invalid for a malformed cron expression", () => {
let result = RescriptCron.validateCronExpression("not-a-cron")
expect(result.valid)->toEqual(false)
})
})
28 changes: 27 additions & 1 deletion src/RescriptCron.res
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ module CronJob = {
Nullable.t<bool>,
Nullable.t<int>,
Nullable.t<bool>,
Nullable.t<bool>,
Nullable.t<JsExn.t => unit>,
Nullable.t<string>,
Nullable.t<float>,
) => t = "CronJob"

let make = (
Expand All @@ -22,8 +26,12 @@ module CronJob = {
~timezone: option<string>=?,
~context: option<{..}>=?,
~runOnInit: option<bool>=?,
~utcOffset: option<int>=?, // Could also be string, but that complicates
~utcOffset: option<int>=?,
~unrefTimeout: option<bool>=?,
~waitForCompletion: option<bool>=?,
~errorHandler: option<JsExn.t => unit>=?,
~name: option<string>=?,
~threshold: option<float>=?,
(),
) =>
make(
Expand All @@ -36,6 +44,10 @@ module CronJob = {
Nullable.fromOption(runOnInit),
Nullable.fromOption(utcOffset),
Nullable.fromOption(unrefTimeout),
Nullable.fromOption(waitForCompletion),
Nullable.fromOption(errorHandler),
Nullable.fromOption(name),
Nullable.fromOption(threshold),
)
}

Expand Down Expand Up @@ -78,3 +90,17 @@ let nextJsDates = (~numberOfDates=1, job: CronJob.t) =>

@send
external addCallback: (CronJob.t, (unit => unit) => unit) => unit = "addCallback"

@send external nextDate: CronJob.t => LuxonDateTime.t = "nextDate"

@get external isActive: CronJob.t => bool = "isActive"

@get external isCallbackRunning: CronJob.t => bool = "isCallbackRunning"

type validateCronResult = {
valid: bool,
error?: JsExn.t,
}

@module("cron") @val
external validateCronExpression: string => validateCronResult = "validateCronExpression"
37 changes: 37 additions & 0 deletions src/RescriptCron.resi
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ module CronJob: {
@param runOnInit This will immediately fire your [onTick] function as soon as the requisite initialization has happened. This option is set to [false] by default for backwards compatibility.
@param utcOffset Set a UTC offset for your timezone instead of using the [timezone] parameter. Do not use [utcOffset] and [timezone] parameter together.
@param unrefTimeout If you have code that keeps the event loop running and want to stop the node process when that finishes regardless of the state of your cronjob, you can do so making use of this parameter. This is off by default and cron will run as if it needs to control the event loop. For more information take a look at {{: https://nodejs.org/api/timers.html#timers_timeout_unref } timers#timers_timeout_unref } from the NodeJS docs.
@param waitForCompletion If set to [true], the job will wait for the previous execution to complete before starting a new one.
@param errorHandler A function called with the error if [onTick] throws. Defaults to [None].
@param name An optional name for the job, useful for debugging.
@param threshold An optional threshold in milliseconds. If the job is overdue by more than this value it will not fire.
")
let make: (
[#CronString(string) | #JsDate(Date.t)],
Expand All @@ -27,6 +31,10 @@ module CronJob: {
~runOnInit: bool=?,
~utcOffset: int=?,
~unrefTimeout: bool=?,
~waitForCompletion: bool=?,
~errorHandler: JsExn.t => unit=?,
~name: string=?,
~threshold: float=?,
unit,
) => t
}
Expand Down Expand Up @@ -104,3 +112,32 @@ external fireOnTick: CronJob.t => unit = "fireOnTick"
)
@send
external addCallback: (CronJob.t, (unit => unit) => unit) => unit = "addCallback"

@ocaml.doc(
" [nextDate(cronJob)] returns the next {!type:LuxonDateTime.t} that will trigger an [onTick] "
)
@send
external nextDate: CronJob.t => LuxonDateTime.t = "nextDate"

@ocaml.doc(" [isActive(cronJob)] returns [true] if the job is currently running ")
@get
external isActive: CronJob.t => bool = "isActive"

@ocaml.doc(" [isCallbackRunning(cronJob)] returns [true] if an [onTick] callback is currently executing ")
@get
external isCallbackRunning: CronJob.t => bool = "isCallbackRunning"

@ocaml.doc(
" Result type returned by {!val:validateCronExpression}. [valid] is [true] if the expression is valid, [error] is set if it is not. "
)
type validateCronResult = {
valid: bool,
error?: JsExn.t,
}

@ocaml.doc(
" [validateCronExpression(expr)] validates a cron expression string. Returns a {!type:validateCronResult} with [valid] set to [true] if the expression is valid. "
)
@module("cron")
@val
external validateCronExpression: string => validateCronResult = "validateCronExpression"