@athenna/cron 4.0.0

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.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import type { ScheduledTask } from '#src/types';
10
+ import { CronBuilder } from '#src/cron/CronBuilder';
11
+ export declare class CronImpl {
12
+ /**
13
+ * Creates a new instance of CronBuilder to register
14
+ * your scheduler.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * Cron.schedule().pattern('59 * * * *')
19
+ * .handler((ctx) => console.log(`my cron pattern is ${ctx.pattern}`))
20
+ * .register()
21
+ * ```
22
+ */
23
+ schedule(): CronBuilder;
24
+ /**
25
+ * Validate if CRON pattern is correct.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const valid = Cron.validate('59 * * * *')
30
+ * const invalid = Cron.validate('60 * * * *')
31
+ * ```
32
+ */
33
+ validate(pattern: string): boolean;
34
+ /**
35
+ * Register the error handler for all tasks.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * Cron.setErrorHandler((err) => console.error(err))
40
+ * ```
41
+ */
42
+ setErrorHandler(handler: any): this;
43
+ /**
44
+ * Returns a map with all tasks that has been scheduled.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const tasks = Cron.getTasks()
49
+ *
50
+ * task.map(task => task.stop())
51
+ * ```
52
+ */
53
+ getTasks(): Map<string, ScheduledTask>;
54
+ /**
55
+ * Close all scheduled tasks.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * Cron.close()
60
+ * ```
61
+ */
62
+ close(): this;
63
+ /**
64
+ * Delete all scheduled tasks.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * Cron.truncate()
69
+ * ```
70
+ */
71
+ truncate(): this;
72
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import { getTasks, validate } from 'node-cron';
10
+ import { CronBuilder } from '#src/cron/CronBuilder';
11
+ export class CronImpl {
12
+ /**
13
+ * Creates a new instance of CronBuilder to register
14
+ * your scheduler.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * Cron.schedule().pattern('59 * * * *')
19
+ * .handler((ctx) => console.log(`my cron pattern is ${ctx.pattern}`))
20
+ * .register()
21
+ * ```
22
+ */
23
+ schedule() {
24
+ return new CronBuilder();
25
+ }
26
+ /**
27
+ * Validate if CRON pattern is correct.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const valid = Cron.validate('59 * * * *')
32
+ * const invalid = Cron.validate('60 * * * *')
33
+ * ```
34
+ */
35
+ validate(pattern) {
36
+ return validate(pattern);
37
+ }
38
+ /**
39
+ * Register the error handler for all tasks.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * Cron.setErrorHandler((err) => console.error(err))
44
+ * ```
45
+ */
46
+ setErrorHandler(handler) {
47
+ CronBuilder.exceptionHandler = handler;
48
+ return this;
49
+ }
50
+ /**
51
+ * Returns a map with all tasks that has been scheduled.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const tasks = Cron.getTasks()
56
+ *
57
+ * task.map(task => task.stop())
58
+ * ```
59
+ */
60
+ getTasks() {
61
+ return getTasks();
62
+ }
63
+ /**
64
+ * Close all scheduled tasks.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * Cron.close()
69
+ * ```
70
+ */
71
+ close() {
72
+ for (const task of this.getTasks().values()) {
73
+ task.stop();
74
+ }
75
+ return this;
76
+ }
77
+ /**
78
+ * Delete all scheduled tasks.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * Cron.truncate()
83
+ * ```
84
+ */
85
+ truncate() {
86
+ const tasks = this.getTasks();
87
+ for (const task of tasks.keys()) {
88
+ tasks.delete(task);
89
+ }
90
+ return this;
91
+ }
92
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export declare const debug: import("util").DebugLogger;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import { debuglog } from 'node:util';
10
+ export const debug = debuglog('athenna:cron');
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import type { CronImpl } from '#src/cron/CronImpl';
10
+ export declare const Cron: import("@athenna/ioc").FacadeType<CronImpl>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import { Facade } from '@athenna/ioc';
10
+ export const Cron = Facade.createFor('Athenna/Core/Cron');
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export declare class CronExceptionHandler {
10
+ /**
11
+ * Error codes that should be ignored from logging.
12
+ */
13
+ get ignoreCodes(): string[];
14
+ /**
15
+ * Error statuses that should be ignored from logging.
16
+ */
17
+ get ignoreStatuses(): number[];
18
+ /**
19
+ * The exception handler of all Artisan commands.
20
+ */
21
+ handle(error: any): Promise<void>;
22
+ /**
23
+ * Return a boolean indicating if the error can be logged or not.
24
+ */
25
+ private canBeLogged;
26
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import { Log } from '@athenna/logger';
10
+ import { Config } from '@athenna/config';
11
+ import { Is, String } from '@athenna/common';
12
+ export class CronExceptionHandler {
13
+ /**
14
+ * Error codes that should be ignored from logging.
15
+ */
16
+ get ignoreCodes() {
17
+ return Config.get('cron.logger.ignoreCodes', []);
18
+ }
19
+ /**
20
+ * Error statuses that should be ignored from logging.
21
+ */
22
+ get ignoreStatuses() {
23
+ return Config.get('cron.logger.ignoreStatuses', []);
24
+ }
25
+ /**
26
+ * The exception handler of all Artisan commands.
27
+ */
28
+ async handle(error) {
29
+ error.code = String.toSnakeCase(error.code || error.name).toUpperCase();
30
+ const isException = Is.Exception(error);
31
+ const isDebugMode = Config.get('app.debug', true);
32
+ const isInternalServerError = Is.Error(error) && !isException;
33
+ if (!isException) {
34
+ error = error.toAthennaException();
35
+ }
36
+ if (isInternalServerError && !isDebugMode) {
37
+ error.name = 'Internal error';
38
+ error.code = 'E_INTERNAL_ERROR';
39
+ error.message = 'An internal error has occurred.';
40
+ delete error.stack;
41
+ }
42
+ if (!this.canBeLogged(error)) {
43
+ return;
44
+ }
45
+ Log.channelOrVanilla('exception').error(await error.prettify());
46
+ }
47
+ /**
48
+ * Return a boolean indicating if the error can be logged or not.
49
+ */
50
+ canBeLogged(error) {
51
+ if (this.ignoreCodes.includes(error.code)) {
52
+ return false;
53
+ }
54
+ if (this.ignoreStatuses.includes(error.status || error.statusCode)) {
55
+ return false;
56
+ }
57
+ return true;
58
+ }
59
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export * from '#src/types';
10
+ export * from '#src/cron/CronImpl';
11
+ export * from '#src/cron/CronBuilder';
12
+ export * from '#src/facades/Cron';
13
+ export * from '#src/annotations/Scheduler';
14
+ export * from '#src/kernels/CronKernel';
15
+ export * from '#src/handlers/CronExceptionHandler';
16
+ export * from '#src/providers/CronProvider';
package/src/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export * from '#src/types';
10
+ export * from '#src/cron/CronImpl';
11
+ export * from '#src/cron/CronBuilder';
12
+ export * from '#src/facades/Cron';
13
+ export * from '#src/annotations/Scheduler';
14
+ export * from '#src/kernels/CronKernel';
15
+ export * from '#src/handlers/CronExceptionHandler';
16
+ export * from '#src/providers/CronProvider';
@@ -0,0 +1,28 @@
1
+ export declare class CronKernel {
2
+ /**
3
+ * Register the cls-rtracer plugin in the Cron.
4
+ */
5
+ registerRTracer(trace?: boolean): Promise<void>;
6
+ /**
7
+ * Register the exception handler for all Cron tasks.
8
+ */
9
+ registerExceptionHandler(path?: string): Promise<void>;
10
+ /**
11
+ * Register all the schedulers found inside "rc.schedulers" config
12
+ * inside the service provider.
13
+ */
14
+ registerSchedulers(): Promise<void>;
15
+ /**
16
+ * Register the route file by importing the file.
17
+ */
18
+ registerRoutes(path: string): Promise<void>;
19
+ /**
20
+ * Register the schedulers using the meta information
21
+ * defined by annotations.
22
+ */
23
+ private registerUsingMeta;
24
+ /**
25
+ * Get the parent URL of the project.
26
+ */
27
+ private getParentURL;
28
+ }
@@ -0,0 +1,95 @@
1
+ import { sep } from 'node:path';
2
+ import { debug } from '#src/debug';
3
+ import { Cron } from '#src/facades/Cron';
4
+ import { CronBuilder } from '#src/cron/CronBuilder';
5
+ import { Exec, Module, Path } from '@athenna/common';
6
+ import { Annotation } from '@athenna/ioc';
7
+ import { CronExceptionHandler } from '#src/handlers/CronExceptionHandler';
8
+ const rTracerPlugin = await Module.safeImport('cls-rtracer');
9
+ export class CronKernel {
10
+ /**
11
+ * Register the cls-rtracer plugin in the Cron.
12
+ */
13
+ async registerRTracer(trace) {
14
+ if (trace === false) {
15
+ debug('Not able to register rTracer plugin. Set the trace option as true in your cron options.');
16
+ return;
17
+ }
18
+ if (trace === undefined && Config.is('cron.rTracer.enabled', false)) {
19
+ debug('Not able to register rTracer plugin. Set the cron.rTracer.enabled configuration as true.');
20
+ return;
21
+ }
22
+ if (!rTracerPlugin) {
23
+ debug('Not able to register tracer plugin. Install cls-rtracer package.');
24
+ return;
25
+ }
26
+ CronBuilder.registerRTracer(rTracerPlugin);
27
+ }
28
+ /**
29
+ * Register the exception handler for all Cron tasks.
30
+ */
31
+ async registerExceptionHandler(path) {
32
+ if (!path) {
33
+ CronBuilder.exceptionHandler = new CronExceptionHandler();
34
+ return;
35
+ }
36
+ const Handler = await Module.resolve(path, this.getParentURL());
37
+ CronBuilder.exceptionHandler = new Handler();
38
+ }
39
+ /**
40
+ * Register all the schedulers found inside "rc.schedulers" config
41
+ * inside the service provider.
42
+ */
43
+ async registerSchedulers() {
44
+ const schedulers = Config.get('rc.schedulers', []);
45
+ await Exec.concurrently(schedulers, async (path) => {
46
+ const Scheduler = await Module.resolve(path, this.getParentURL());
47
+ if (Annotation.isAnnotated(Scheduler)) {
48
+ this.registerUsingMeta(Scheduler);
49
+ return;
50
+ }
51
+ ioc.transient(`App/Cron/Schedulers/${Scheduler.name}`, Scheduler);
52
+ });
53
+ }
54
+ /**
55
+ * Register the route file by importing the file.
56
+ */
57
+ async registerRoutes(path) {
58
+ await Module.resolve(path, this.getParentURL());
59
+ }
60
+ /**
61
+ * Register the schedulers using the meta information
62
+ * defined by annotations.
63
+ */
64
+ registerUsingMeta(target) {
65
+ const meta = Annotation.getMeta(target);
66
+ const builder = Cron.schedule();
67
+ ioc[meta.type](meta.alias, target);
68
+ if (meta.name) {
69
+ builder.name(meta.name);
70
+ ioc.alias(meta.name, meta.alias);
71
+ }
72
+ if (meta.camelAlias) {
73
+ ioc.alias(meta.camelAlias, meta.alias);
74
+ }
75
+ builder
76
+ .pattern(meta.pattern)
77
+ .timezone(meta.timezone)
78
+ .scheduled(meta.scheduled)
79
+ .runOnInit(meta.runOnInit)
80
+ .recoverMissedExecutions(meta.recoverMissedExecutions)
81
+ .handler(ctx => {
82
+ const scheduler = ioc.use(meta.name) ||
83
+ ioc.use(meta.camelAlias) ||
84
+ ioc.safeUse(meta.alias);
85
+ return scheduler.handler(ctx);
86
+ });
87
+ return meta;
88
+ }
89
+ /**
90
+ * Get the parent URL of the project.
91
+ */
92
+ getParentURL() {
93
+ return Config.get('rc.parentURL', Path.toHref(Path.pwd() + sep));
94
+ }
95
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import { ServiceProvider } from '@athenna/ioc';
10
+ export declare class CronProvider extends ServiceProvider {
11
+ register(): void;
12
+ shutdown(): Promise<void>;
13
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import { CronImpl } from '#src/cron/CronImpl';
10
+ import { ServiceProvider } from '@athenna/ioc';
11
+ export class CronProvider extends ServiceProvider {
12
+ register() {
13
+ this.container.instance('Athenna/Core/Cron', new CronImpl());
14
+ }
15
+ async shutdown() {
16
+ const Cron = this.container.use('Athenna/Core/Cron');
17
+ if (!Cron) {
18
+ return;
19
+ }
20
+ Cron.close().truncate();
21
+ }
22
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export interface Context {
10
+ name?: string;
11
+ traceId?: string;
12
+ pattern?: string;
13
+ timezone?: string;
14
+ runOnInit?: boolean;
15
+ recoverMissedExecutions?: boolean;
16
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import type { Context } from '#src/types/Context';
10
+ export type CronHandler = (ctx?: Context) => any | Promise<any>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import type { ScheduledTask as NodeCronScheduledTask } from 'node-cron';
10
+ export type ScheduledTask = NodeCronScheduledTask & {
11
+ options: {
12
+ name?: string;
13
+ timezone?: string;
14
+ runOnInit?: boolean;
15
+ scheduled?: boolean;
16
+ recoverMissedExecutions?: boolean;
17
+ };
18
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export * from '#src/types/Context';
10
+ export * from '#src/types/CronHandler';
11
+ export * from '#src/types/ScheduledTask';
12
+ export * from '#src/types/schedulers/SchedulerOptions';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export * from '#src/types/Context';
10
+ export * from '#src/types/CronHandler';
11
+ export * from '#src/types/ScheduledTask';
12
+ export * from '#src/types/schedulers/SchedulerOptions';
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @athenna/cron
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ export type SchedulerOptions = {
10
+ /**
11
+ * The CRON expression that will determined when the scheduler
12
+ * will run.
13
+ *
14
+ * @example '* * * * *'
15
+ */
16
+ pattern: string;
17
+ /**
18
+ * The timezone that the CRON expression needs to respect when
19
+ * running the scheduler.
20
+ */
21
+ timezone?: string;
22
+ /**
23
+ * Defines if the scheduler should run when initiating the application,
24
+ * not matter the CRON expression, it will always run on boot.
25
+ *
26
+ * @default false
27
+ */
28
+ runOnInit?: boolean;
29
+ /**
30
+ * Defines if the scheduler should already be registered when booting
31
+ * the application. This option is not like `runOnInit`, if you set it
32
+ * as `false`, it will basically register your CRON but disabled, meaning
33
+ * you need to manually start it using `Cron` facade.
34
+ *
35
+ * @default true
36
+ */
37
+ scheduled?: boolean;
38
+ /**
39
+ * If this option is `true`, when the main thread froze for any reason,
40
+ * the elapsed time since last execution will be considered an all the
41
+ * missed executions will be triggered.
42
+ *
43
+ * @default false
44
+ */
45
+ recoverMissedExecutions?: boolean;
46
+ /**
47
+ * The name that will be used to register the scheduler node-cron
48
+ * and also inside the service container as an alias.
49
+ *
50
+ * @default YourSchedulerClassName
51
+ */
52
+ name?: string;
53
+ /**
54
+ * The alias that will be used to register the scheduler inside
55
+ * the service container.
56
+ *
57
+ * @default App/Cron/Schedulers/YourSchedulerClassName
58
+ */
59
+ alias?: string;
60
+ /**
61
+ * The camel alias that will be used as an alias of the real
62
+ * scheduler alias. Camel alias is important when you want to
63
+ * work with constructor injection. By default, Athenna doesn't
64
+ * create camel alias for schedulers.
65
+ *
66
+ * @default undefined
67
+ */
68
+ camelAlias?: string;
69
+ /**
70
+ * The registration type that will be used to register your scheduler
71
+ * inside the service container.
72
+ *
73
+ * @default 'transient'
74
+ */
75
+ type?: 'fake' | 'scoped' | 'singleton' | 'transient';
76
+ };