npm package: @marceliwac/moleculer-cron
Description: Moleculer mixin that adds cron job functionality.
Latest version: 0.0.3

<div align="center">
<h1>Moleculer Cron</h1>
<img alt="NPM License" src="https://img.shields.io/npm/v/%40marceliwac%2Fmoleculer-cron">
<img alt="NPM License" src="https://img.shields.io/npm/l/%40marceliwac%2Fmoleculer-cron">
</div>


[Moleculer](https://moleculer.services/) microservice framework mixin that adds cron job 
functionality. Under the hood, this mixin uses [cron package](https://npmjs.com/package/cron) to 
create and run jobs and wraps them into configuration objects that are used throughout the 
Moleculer's service lifecycle. 

## Installation

To install the package, run the following in your Moleculer project: 

```shell
npm install @marceliwac/moleculer-cron
```

## Basic Usage

You can integrate this mixin into the existing service by importing it at the top of the file, 
providing the mixin into the top-level service `mixins` field and specifying top-level `crons` field
which stores the array of your job configurations.

The only required configuration properties are `cronTime`, defining the cron pattern that describes
when your job should run and `onTick`, which is the function that will be run. You might however 
also want to specify the `start` parameter to automatically start the job when the service starts.

In your `onTick` function you will be able to access the service context within `this`, similarly to
the service actions and methods. Examples of more detailed configuration for both JavaScript and
TypeScript can be found in [examples](./examples).

```js
import {Cron} from '@marceliwac/moleculer-cron';
// const {Cron} = require('@marceliwac/cron');

const service = {
  name: "example",
  mixins: [Cron], // enable the Cron Mixin
  crons: [
    {
      cronTime: '* * * * *',
      onTick: async function () {
        this.logger.info(`Running job "simple job".`);
        const randomNumber = await this.broker.call('example.getRandomNumber');
        this.logger.info(`Random number is: ${randomNumber}`);
      },
      start: true,
    }
  ],
  actions: {
    getRandomNumber: {
      handler() {
        const random = Number.parseInt((Math.random() * 10).toString());
        this.logger.info(`Returning random number (${random}).`);
        return random;
      }
    }
  }
};
``` 

## API

There are many more configuration options you can use with each cron job. Below is a complete 
breakdown of the parameters that can be specified to further customise the behaviour of the jobs:

### Configuration API

```ts
// Following properties are defined on the CronMixinConfig interface and can be used as part of the
// job configuration.
interface CronMixinConfig {
  name?: string;
  cronTime: string | Date | luxon.DateTime;
  onTick: (onComplete?: (() => void | Promise<void>) | null) => void | Promise<void>;
  onInitialise?: () => void | Promise<void>;
  onStart?: () => void | Promise<void>;
  onStop?: () => void | Promise<void>;
  onComplete?: () => void | Promise<void>;
  start?: boolean | null; 
  runOnInit?: boolean | null;
  context?: object;
  timeZone?: string | null;
  utcOffset?: number | null;
  unrefTimeout?: boolean | null;
}
```

- `name` Defines the name that can be used to find the job using `getCron()` function. By default,
a UUID v4 will be provided.
- `cronTime` Specifies when the job should run using a cron pattern with additional, optional 
field for the seconds. For full specification, see [crontab specification](http://crontab.org/) and
[cron documentation](https://www.npmjs.com/package/cron#cron-patterns).
- `onTick` Function to be executed at the specified `cronTime`. This function will receive
additional context from the `context` parameter, as well as Moleculer's service context. 
- `onInitialise` Function to be executed before the cron job is created. This function will 
receive additional context from the `context` parameter, as well as Moleculer's service context.
- `onStart` Function to be executed after the cron job is created. This function will
receive additional context from the `context` parameter, as well as Moleculer's service context.
- `onStop` Function to be executed after the cron job is stopped, or after the service 
stops. This function will receive additional context from the `context` parameter, as well as 
Moleculer's service context.
- `onComplete` Similarly to `onStop`, this function will be executed after the cron job is stopped.
However, when defined, this function will also be provided as the first argument to the `onTick` 
function allowing you to manually call it with every `onTick` call. Unlike `onStop`, this
function will not receive any additional context when called when the job stops. It could however be
called with any context (including the `context` parameter, as well as the Moleculer's service 
context) when called with every `onTick` (for more details, see [examples](./examples)).
- `start` Defines whether this job should be started when the service is created. Crucially, this is
different from the `runOnInit`, as it only "enables" the job without triggering its `onTick`, 
allowing it to run on the next specified `cronTime`.
- `runOnInit` Specifies whether the `onTick` should be fired immediately following the job creation,
regardless of the `cronTime` or `start` parameter. Following this initial call, all future calls 
will respect the `cronTime` and `start` parameters and only run at specified times if the job is 
started. 
- `context` Allows to supply additional context to the job which on top of the service context,
which is always provided. Similarly to the service context, this value will also be passed to all 
the lifecycle handles (`onTick`, `onInitialise`, `onStart`, `onStop`) except `onComplete`. 
- `timeZone` Specifies the timezone that should be used for the cronTime configuration. This is 
particularly important when the `cronTime` defines a specific hour, day, month or day of the week,
or a specific range for any of those values. Use of `timeZone` and `utcOffset` is mutually 
exclusive.  
- `utcOffset` Alternative to the `timeZone` which allows for specifying the offset to the UTC 
timezone in minutes. Both positive and negative values can be used (e.g. `60` would be UTC+1, `-120`
would be UTC-2). Use of `utcOffset` and `timeZone` is mutually exclusive.
- `unrefTimeout` Specifies whether the running job should keep the process alive. This parameter is
used internally by [cron](https://www.npmjs.com/package/cron#cronjob-class) to control the event
loop behaviour. When used with [Moleculer Runner](https://moleculer.services/docs/runner.html), this
parameter won't affect the event loop behaviour because it is already controlled by the runner (it 
can be left unspecified).

> Note: When defining your crons, make sure to use the `function`s, which can inherit the context,
rather than arrow functions / lambdas (`() => {}`).

### Methods

This mixin also exposes additional methods that can be used to assist you with managing the jobs
during runtime. They can be accessed using the service context, and are therefore available across
the service, including other methods, actions and the lifecycle functions of the crons you define
(`onTick`, `onInitialise`, `onStart`, `onStop`) except `onComplete` which is called internally by 
cron and therefore does not inherit the context.

### CronJob API
Once created, the [`CronJob`](https://www.npmjs.com/package/cron#cronjob-class) will be stored 
within the `job` property of the service's top-level `crons`, allowing you to control the job 
behaviour during runtime. The `job` object stores an unmodified
[`CronJob`](https://www.npmjs.com/package/cron#cronjob-class), which means you can freely use its
API:

- `start()` Starts the job; if the job is already running, this will have no effect.
- `stop()` Stops the job; if the job is already stopped, this will have no effect.
- `setTime()` Modifies the `cronTime` parameter for the job and must be a valid `CronTime` (which 
can be obtained using the Mixin's `getCronTime()` method).
- `lastDate()` Provides the last execution date.
- `nextDate()` Provides the next date on which the `onTick` will be called.
- `nextDates(count)` Provides the array of next dates on which the `onTick` will be called, equal in 
length to the _`count`_ parameter.
- `fireOnTick()`: Allows a manual execution of the `onTick` function.
- `addCallback(callback)`: Allows addition of the additional callbacks to the `onTick` function. Any
additional callbacks will also receive the additional context from the `context` parameter, as well
as Moleculer's service context.

## Cron Patterns

In addition to the [standard crontab specification](http://crontab.org/), this mixin also utilises
the _seconds_ parameter defined in [cron](https://www.npmjs.com/package/cron#cron-patterns). 
Following patterns can be used to define the `cronTime`:  

```
Extended pattern (with seconds)
<second> <minute> <hour> <day of month> <month> <day of week>

Standard pattern
<minute> <hour> <day of month> <month> <day of week>
```

For each of the fields denoted by `<field>`, values can be defined as _wildcards_ (`*`) allowing for
any value to match the pattern, _ranges_ (1-3,5) specifying direct values, or ranges of values that
will match the pattern or _steps_ (`*/2`) providing increments in which the pattern will be matched.
For example:

```
const cronTime = '*/30 * 9-17 * 1,3,6,12 1-5';

// Field           Allowed    Example     Example description
//
// <second>        0-59       */30        Every 30 seconds
// <minute>        0-59       *           Every minute
// <hour>          0-23       9-17        Every hour between 9 and 17 (9am to 5pm)
// <day of month>  1-31       *           Every day of the month
// <month>         1-12       1,3,6,12    In January, March, June and December
// <day of week>   0-7        1-5         Between Monday and Friday.
```

For a full specification of the crontab patterns, see [crontab specification](http://crontab.org/).
You may also wish to use tools such as [crontab.guru](https://crontab.guru/) to assist you with the
pattern definition, remembering that you may also define the seconds field.

## TypeScript

Additional types are provided to facilitate integration within TypeScript projects. Below are the
types you can import and extend your service interface with:

- `CronMixinConfig` Interface of the configuration objects which need to be defined within the
  service's top-level `crons` property.
- `CronMixinCron` Interface of the object stored within service's top-level `crons` following the
  initialisation.
- `CronMixinSchema` Interface that can be used to extend the `moleculer.ServiceSchema` when using
  the mixin.
- `CronMixinService` Interface of the mixin that can be used to extend the `moleculer.Service` when
  using the mixin.

For more details on using this mixing with TypeScript, please see the 
[TypeScript example](./examples/index.ts).