diff --git a/build/adonis-typings/container.d.ts b/build/adonis-typings/container.d.ts
new file mode 100644
index 0000000..52c44ac
--- /dev/null
+++ b/build/adonis-typings/container.d.ts
@@ -0,0 +1,7 @@
+///
+declare module '@ioc:Adonis/Core/Application' {
+ import { SchedulerContract } from '@ioc:Verful/Scheduler';
+ interface ContainerBindings {
+ 'Verful/Scheduler': SchedulerContract;
+ }
+}
diff --git a/build/adonis-typings/container.js b/build/adonis-typings/container.js
new file mode 100644
index 0000000..e69de29
diff --git a/build/adonis-typings/index.d.ts b/build/adonis-typings/index.d.ts
new file mode 100644
index 0000000..8d1ad49
--- /dev/null
+++ b/build/adonis-typings/index.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/build/adonis-typings/index.js b/build/adonis-typings/index.js
new file mode 100644
index 0000000..e69de29
diff --git a/build/adonis-typings/scheduler.d.ts b/build/adonis-typings/scheduler.d.ts
new file mode 100644
index 0000000..28f27da
--- /dev/null
+++ b/build/adonis-typings/scheduler.d.ts
@@ -0,0 +1,62 @@
+declare module '@ioc:Verful/Scheduler' {
+ export type ScheduleHandler = () => void;
+ export type Time = `${string}:${string}`;
+ export type Condition = () => boolean | Promise;
+ export interface ManagesFrequenciesContract {
+ expression: string;
+ cron(expression: string): this;
+ everyMinute(): this;
+ everyTwoMinutes(): this;
+ everyThreeMinutes(): this;
+ everyFourMinutes(): this;
+ everyFiveMinutes(): this;
+ everyTenMinutes(): this;
+ everyFifteenMinutes(): this;
+ everyThirtyMinutes(): this;
+ hourly(): this;
+ hourlyAt(offset: number | number[]): this;
+ everyTwoHours(): this;
+ everyFourHours(): this;
+ everySixHours(): this;
+ daily(): this;
+ dailyAt(time: Time): this;
+ twiceDaily(): this;
+ twiceDailyAt(first: number, second: number, offset: number): this;
+ weekly(): this;
+ weeklyOn(daysOfWeek: number | number[] | string, time: Time): this;
+ monthly(): this;
+ monthlyOn(dayOfMonth: number, time: Time): this;
+ twiceMonthly(first: number, second: number, time: Time): this;
+ lastDayOfMonth(time: Time): this;
+ yearly(): this;
+ yearlyOn(month: number, dayOfMonth?: string | number, time?: Time): this;
+ days(days: number | number[] | string): this;
+ weekdays(): this;
+ weekends(): this;
+ sundays(): this;
+ mondays(): this;
+ tuesdays(): this;
+ wednesdays(): this;
+ thursdays(): this;
+ fridays(): this;
+ saturdays(): this;
+ }
+ export interface ScheduleContract extends ManagesFrequenciesContract {
+ command: ScheduleHandler;
+ filters: Condition[];
+ rejects: Condition[];
+ skip(condition: Condition): this;
+ when(condition: Condition): this;
+ between(start: Time, end: Time): this;
+ unlessBetween(start: Time, end: Time): this;
+ environments(environments: Array<'production' | 'development' | 'staging' | 'test'>): this;
+ }
+ export interface SchedulerContract {
+ call(handler: ScheduleHandler): ScheduleContract;
+ command(command: string | string[]): ScheduleContract;
+ exec(command: string): ScheduleContract;
+ start(): void;
+ }
+ const Scheduler: SchedulerContract;
+ export default Scheduler;
+}
diff --git a/build/adonis-typings/scheduler.js b/build/adonis-typings/scheduler.js
new file mode 100644
index 0000000..e69de29
diff --git a/build/bin/japa_types.d.ts b/build/bin/japa_types.d.ts
new file mode 100644
index 0000000..64490c7
--- /dev/null
+++ b/build/bin/japa_types.d.ts
@@ -0,0 +1,19 @@
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+import { ApplicationContract } from '@ioc:Adonis/Core/Application';
+import '@japa/runner';
+declare module '@japa/runner' {
+ interface TestContext {
+ app: ApplicationContract;
+ }
+ interface Test {
+ }
+}
diff --git a/build/bin/japa_types.js b/build/bin/japa_types.js
new file mode 100644
index 0000000..c6112f4
--- /dev/null
+++ b/build/bin/japa_types.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+require("@japa/runner");
diff --git a/build/bin/test.d.ts b/build/bin/test.d.ts
new file mode 100644
index 0000000..d2c9bc6
--- /dev/null
+++ b/build/bin/test.d.ts
@@ -0,0 +1 @@
+import 'reflect-metadata';
diff --git a/build/bin/test.js b/build/bin/test.js
new file mode 100644
index 0000000..5b675d2
--- /dev/null
+++ b/build/bin/test.js
@@ -0,0 +1,66 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+ o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+ if (mod && mod.__esModule) return mod;
+ var result = {};
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+require("reflect-metadata");
+const node_path_1 = require("node:path");
+const standalone_1 = require("@adonisjs/core/build/standalone");
+const assert_1 = require("@japa/assert");
+const runner_1 = require("@japa/runner");
+const spec_reporter_1 = require("@japa/spec-reporter");
+const dev_utils_1 = require("@poppinss/dev-utils");
+const fs = new dev_utils_1.Filesystem((0, node_path_1.resolve)(__dirname, '__app'));
+(0, runner_1.configure)({
+ ...(0, runner_1.processCliArgs)(process.argv.slice(2)),
+ files: ['tests/**/*.spec.ts'],
+ plugins: [(0, assert_1.assert)()],
+ reporters: [(0, spec_reporter_1.specReporter)()],
+ importer: (filePath) => Promise.resolve(`${filePath}`).then(s => __importStar(require(s))),
+ forceExit: true,
+ setup: [
+ async () => {
+ await fs.add('.env', '');
+ await fs.add('config/app.ts', `
+ export const profiler = { enabled: true }
+ export const appKey = 'averylong32charsrandomsecretkey',
+ export const http = {
+ cookie: {},
+ trustProxy: () => true,
+ }
+ `);
+ const app = new standalone_1.Application(fs.basePath, 'test', {
+ providers: ['@adonisjs/core', '../../providers/scheduler_provider'],
+ });
+ await app.setup();
+ await app.registerProviders();
+ await app.bootProviders();
+ return async () => {
+ await app.shutdown();
+ await fs.cleanup();
+ };
+ },
+ ],
+});
+runner_1.TestContext.getter('app', () => require('@adonisjs/core/build/services/app.js').default);
+(0, runner_1.run)();
diff --git a/build/commands/index.d.ts b/build/commands/index.d.ts
new file mode 100644
index 0000000..d451d2b
--- /dev/null
+++ b/build/commands/index.d.ts
@@ -0,0 +1,2 @@
+declare const _default: string[];
+export default _default;
diff --git a/build/commands/index.js b/build/commands/index.js
new file mode 100644
index 0000000..4af0d83
--- /dev/null
+++ b/build/commands/index.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.default = ['@verful/scheduler/build/commands/scheduler_work'];
diff --git a/build/commands/scheduler_work.d.ts b/build/commands/scheduler_work.d.ts
new file mode 100644
index 0000000..003e047
--- /dev/null
+++ b/build/commands/scheduler_work.d.ts
@@ -0,0 +1,10 @@
+import { BaseCommand } from '@adonisjs/core/build/standalone';
+export default class ProcessSchedule extends BaseCommand {
+ static commandName: string;
+ static description: string;
+ static settings: {
+ loadApp: boolean;
+ stayAlive: boolean;
+ };
+ run(): Promise;
+}
diff --git a/build/commands/scheduler_work.js b/build/commands/scheduler_work.js
new file mode 100644
index 0000000..7eb9ee4
--- /dev/null
+++ b/build/commands/scheduler_work.js
@@ -0,0 +1,15 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const standalone_1 = require("@adonisjs/core/build/standalone");
+class ProcessSchedule extends standalone_1.BaseCommand {
+ static commandName = 'scheduler:work';
+ static description = 'Process the scheduled tasks';
+ static settings = {
+ loadApp: true,
+ stayAlive: true,
+ };
+ async run() {
+ this.application.container.use('Verful/Scheduler').start();
+ }
+}
+exports.default = ProcessSchedule;
diff --git a/build/instructions.md b/build/instructions.md
new file mode 100644
index 0000000..5c3a8c7
--- /dev/null
+++ b/build/instructions.md
@@ -0,0 +1 @@
+The package has been configured successfully.
diff --git a/build/providers/scheduler_provider.d.ts b/build/providers/scheduler_provider.d.ts
new file mode 100644
index 0000000..572c3ac
--- /dev/null
+++ b/build/providers/scheduler_provider.d.ts
@@ -0,0 +1,16 @@
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+import type { ApplicationContract } from '@ioc:Adonis/Core/Application';
+export default class ScheduleProvider {
+ protected app: ApplicationContract;
+ constructor(app: ApplicationContract);
+ register(): void;
+}
diff --git a/build/providers/scheduler_provider.js b/build/providers/scheduler_provider.js
new file mode 100644
index 0000000..0d957e5
--- /dev/null
+++ b/build/providers/scheduler_provider.js
@@ -0,0 +1,15 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+class ScheduleProvider {
+ app;
+ constructor(app) {
+ this.app = app;
+ }
+ register() {
+ this.app.container.singleton('Verful/Scheduler', () => {
+ const { default: Scheduler } = require('../src/scheduler');
+ return new Scheduler(this.app);
+ });
+ }
+}
+exports.default = ScheduleProvider;
diff --git a/build/src/manages_frequencies.d.ts b/build/src/manages_frequencies.d.ts
new file mode 100644
index 0000000..3015d66
--- /dev/null
+++ b/build/src/manages_frequencies.d.ts
@@ -0,0 +1,46 @@
+///
+import { ManagesFrequenciesContract, Time } from '@ioc:Verful/Scheduler';
+export default class ManagesFrequencies implements ManagesFrequenciesContract {
+ expression: string;
+ protected currentTimezone: string;
+ timezone(timezone: string): this;
+ protected spliceIntoPosition(position: number, value: string | number): this;
+ everyMinute(): this;
+ everyTwoMinutes(): this;
+ everyThreeMinutes(): this;
+ everyFourMinutes(): this;
+ everyFiveMinutes(): this;
+ everyTenMinutes(): this;
+ everyFifteenMinutes(): this;
+ everyThirtyMinutes(): this;
+ hourly(): this;
+ hourlyAt(offset: number | number[]): this;
+ everyTwoHours(): this;
+ everyThreeHours(): this;
+ everyFourHours(): this;
+ everySixHours(): this;
+ daily(): this;
+ dailyAt(time: Time): this;
+ twiceDaily(): this;
+ twiceDailyAt(first: number, second: number, offset?: number): this;
+ weekly(): this;
+ weeklyOn(daysOfWeek: number | number[] | string, time: Time): this;
+ monthly(): this;
+ monthlyOn(dayOfMonth: number, time?: Time): this;
+ twiceMonthly(first?: number, second?: number, time?: Time): this;
+ lastDayOfMonth(time?: Time): this;
+ quarterly(): this;
+ yearly(): this;
+ yearlyOn(month: number, dayOfMonth?: string | number, time?: Time): this;
+ days(days: number | number[] | string): this;
+ weekdays(): this;
+ weekends(): this;
+ sundays(): this;
+ mondays(): this;
+ tuesdays(): this;
+ wednesdays(): this;
+ thursdays(): this;
+ fridays(): this;
+ saturdays(): this;
+ cron(expression: string): this;
+}
diff --git a/build/src/manages_frequencies.js b/build/src/manages_frequencies.js
new file mode 100644
index 0000000..226b7b4
--- /dev/null
+++ b/build/src/manages_frequencies.js
@@ -0,0 +1,152 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const luxon_1 = require("luxon");
+const Weekdays = Object.freeze({
+ MONDAY: 1,
+ TUESDAY: 2,
+ WEDNESDAY: 3,
+ THURSDAY: 4,
+ FRIDAY: 5,
+ SATURDAY: 6,
+ SUNDAY: 7,
+});
+class ManagesFrequencies {
+ expression = '* * * * *';
+ currentTimezone = 'UTC';
+ timezone(timezone) {
+ this.currentTimezone = timezone;
+ return this;
+ }
+ spliceIntoPosition(position, value) {
+ const segments = this.expression.split(' ');
+ segments[position] = String(value);
+ return this.cron(segments.join(' '));
+ }
+ everyMinute() {
+ return this.spliceIntoPosition(0, '*');
+ }
+ everyTwoMinutes() {
+ return this.spliceIntoPosition(0, '*/2');
+ }
+ everyThreeMinutes() {
+ return this.spliceIntoPosition(0, '*/3');
+ }
+ everyFourMinutes() {
+ return this.spliceIntoPosition(0, '*/4');
+ }
+ everyFiveMinutes() {
+ return this.spliceIntoPosition(0, '*/5');
+ }
+ everyTenMinutes() {
+ return this.spliceIntoPosition(0, '*/10');
+ }
+ everyFifteenMinutes() {
+ return this.spliceIntoPosition(0, '*/15');
+ }
+ everyThirtyMinutes() {
+ return this.spliceIntoPosition(0, '0,30');
+ }
+ hourly() {
+ return this.spliceIntoPosition(0, 0);
+ }
+ hourlyAt(offset) {
+ const offsetString = Array.isArray(offset) ? offset.join(',') : offset;
+ return this.spliceIntoPosition(0, offsetString);
+ }
+ everyTwoHours() {
+ return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, '*/2');
+ }
+ everyThreeHours() {
+ return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, '*/3');
+ }
+ everyFourHours() {
+ return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, '*/4');
+ }
+ everySixHours() {
+ return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, '*/6');
+ }
+ daily() {
+ return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, 0);
+ }
+ dailyAt(time) {
+ const [hour, minute] = time.split(':').map((value) => String(Number(value)));
+ return this.spliceIntoPosition(0, minute).spliceIntoPosition(1, hour);
+ }
+ twiceDaily() {
+ return this.twiceDailyAt(0, 12, 0);
+ }
+ twiceDailyAt(first, second, offset = 0) {
+ const hours = `${first},${second}`;
+ return this.spliceIntoPosition(0, offset).spliceIntoPosition(1, hours);
+ }
+ weekly() {
+ return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, 0).spliceIntoPosition(4, 0);
+ }
+ weeklyOn(daysOfWeek, time) {
+ return this.days(daysOfWeek).dailyAt(time);
+ }
+ monthly() {
+ return this.spliceIntoPosition(0, 0).spliceIntoPosition(1, 0).spliceIntoPosition(2, 1);
+ }
+ monthlyOn(dayOfMonth, time = '00:00') {
+ return this.dailyAt(time).spliceIntoPosition(2, dayOfMonth);
+ }
+ twiceMonthly(first = 1, second = 16, time = '00:00') {
+ const daysOfMonth = `${first},${second}`;
+ return this.spliceIntoPosition(2, daysOfMonth).dailyAt(time);
+ }
+ lastDayOfMonth(time = '00:00') {
+ return this.spliceIntoPosition(2, luxon_1.DateTime.now().setZone(this.currentTimezone).endOf('month').day).dailyAt(time);
+ }
+ quarterly() {
+ return this.spliceIntoPosition(0, 0)
+ .spliceIntoPosition(1, 0)
+ .spliceIntoPosition(2, 1)
+ .spliceIntoPosition(3, '1-12/3');
+ }
+ yearly() {
+ return this.spliceIntoPosition(0, 0)
+ .spliceIntoPosition(1, 0)
+ .spliceIntoPosition(2, 1)
+ .spliceIntoPosition(3, 1);
+ }
+ yearlyOn(month, dayOfMonth = 1, time = '00:00') {
+ return this.spliceIntoPosition(2, dayOfMonth).spliceIntoPosition(3, month).dailyAt(time);
+ }
+ days(days) {
+ const daysString = Array.isArray(days) ? days.join(',') : days;
+ return this.spliceIntoPosition(4, daysString);
+ }
+ weekdays() {
+ return this.days(`${Weekdays.MONDAY}-${Weekdays.FRIDAY}`);
+ }
+ weekends() {
+ return this.days(`${Weekdays.SATURDAY},${Weekdays.SUNDAY}`);
+ }
+ sundays() {
+ return this.days(Weekdays.SUNDAY);
+ }
+ mondays() {
+ return this.days(Weekdays.MONDAY);
+ }
+ tuesdays() {
+ return this.days(Weekdays.TUESDAY);
+ }
+ wednesdays() {
+ return this.days(Weekdays.WEDNESDAY);
+ }
+ thursdays() {
+ return this.days(Weekdays.THURSDAY);
+ }
+ fridays() {
+ return this.days(Weekdays.FRIDAY);
+ }
+ saturdays() {
+ return this.days(Weekdays.SATURDAY);
+ }
+ cron(expression) {
+ this.expression = expression;
+ return this;
+ }
+}
+exports.default = ManagesFrequencies;
diff --git a/build/src/schedule.d.ts b/build/src/schedule.d.ts
new file mode 100644
index 0000000..6485df0
--- /dev/null
+++ b/build/src/schedule.d.ts
@@ -0,0 +1,27 @@
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+import { ApplicationContract } from '@ioc:Adonis/Core/Application';
+import { Condition, ScheduleContract, ScheduleHandler, Time } from '@ioc:Verful/Scheduler';
+import ManagesFrequencies from './manages_frequencies';
+export default class Schedule extends ManagesFrequencies implements ScheduleContract {
+ private app;
+ command: ScheduleHandler;
+ constructor(app: ApplicationContract, command: ScheduleHandler);
+ protected inTimeInterval(startTime: Time, endTime: Time): () => boolean;
+ between(start: Time, end: Time): this;
+ unlessBetween(start: Time, end: Time): this;
+ filters: Condition[];
+ rejects: Condition[];
+ skip(condition: Condition): this;
+ when(condition: Condition): this;
+ environments(environments: Array<'production' | 'development' | 'staging' | 'test'>): this;
+}
diff --git a/build/src/schedule.js b/build/src/schedule.js
new file mode 100644
index 0000000..aa22d20
--- /dev/null
+++ b/build/src/schedule.js
@@ -0,0 +1,59 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const luxon_1 = require("luxon");
+const manages_frequencies_1 = __importDefault(require("./manages_frequencies"));
+class Schedule extends manages_frequencies_1.default {
+ app;
+ command;
+ constructor(app, command) {
+ super();
+ this.app = app;
+ this.command = command;
+ }
+ inTimeInterval(startTime, endTime) {
+ const [startHours, startMinutes] = startTime.split(':').map(Number);
+ const [endHours, endMinutes] = endTime.split(':').map(Number);
+ let [now, start, end] = [
+ luxon_1.DateTime.now().setZone(this.currentTimezone),
+ luxon_1.DateTime.now()
+ .set({ minute: startMinutes, hour: startHours, second: 0, millisecond: 0 })
+ .setZone(this.currentTimezone),
+ luxon_1.DateTime.now()
+ .set({ minute: endMinutes, hour: endHours, second: 0, millisecond: 0 })
+ .setZone(this.currentTimezone),
+ ];
+ if (end < start) {
+ if (start > now) {
+ start = start.minus({ days: 1 });
+ }
+ else {
+ end = end.plus({ days: 1 });
+ }
+ }
+ return () => now > start && now < end;
+ }
+ between(start, end) {
+ return this.when(this.inTimeInterval(start, end));
+ }
+ unlessBetween(start, end) {
+ return this.skip(this.inTimeInterval(start, end));
+ }
+ filters = [];
+ rejects = [];
+ skip(condition) {
+ this.rejects.push(condition);
+ return this;
+ }
+ when(condition) {
+ this.filters.push(condition);
+ return this;
+ }
+ environments(environments) {
+ this.when(() => environments.includes(this.app.nodeEnvironment));
+ return this;
+ }
+}
+exports.default = Schedule;
diff --git a/build/src/scheduler.d.ts b/build/src/scheduler.d.ts
new file mode 100644
index 0000000..502c560
--- /dev/null
+++ b/build/src/scheduler.d.ts
@@ -0,0 +1,22 @@
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+import { ApplicationContract } from '@ioc:Adonis/Core/Application';
+import { ScheduleContract, SchedulerContract } from '@ioc:Verful/Scheduler';
+export default class Scheduler implements SchedulerContract {
+ private app;
+ private events;
+ constructor(app: ApplicationContract);
+ call(callback: () => void): ScheduleContract;
+ command(command: string | string[]): ScheduleContract;
+ exec(command: string): ScheduleContract;
+ start(): void;
+}
diff --git a/build/src/scheduler.js b/build/src/scheduler.js
new file mode 100644
index 0000000..46a618a
--- /dev/null
+++ b/build/src/scheduler.js
@@ -0,0 +1,61 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const execa_1 = __importDefault(require("execa"));
+const node_cron_1 = __importDefault(require("node-cron"));
+const schedule_1 = __importDefault(require("./schedule"));
+class Scheduler {
+ app;
+ events = [];
+ constructor(app) {
+ this.app = app;
+ }
+ call(callback) {
+ const schedule = new schedule_1.default(this.app, callback);
+ this.events.push(schedule);
+ return schedule;
+ }
+ command(command) {
+ const commandArguments = Array.isArray(command) ? command : command.split(' ');
+ const callback = async () => {
+ try {
+ const result = await execa_1.default.node('ace', commandArguments);
+ return result.stdout;
+ }
+ catch (error) {
+ return error;
+ }
+ };
+ return this.call(callback);
+ }
+ exec(command) {
+ const callback = async () => {
+ try {
+ const result = await execa_1.default.command(command);
+ return result.stdout;
+ }
+ catch (error) {
+ return error;
+ }
+ };
+ return this.call(callback);
+ }
+ start() {
+ this.app.logger.info('Schedule processing started');
+ for (const event of this.events)
+ node_cron_1.default.schedule(event.expression, async () => {
+ for (const filter of event.filters) {
+ if (!(await filter()))
+ return;
+ }
+ for (const reject of event.rejects) {
+ if (await reject())
+ return;
+ }
+ return await event.command();
+ });
+ }
+}
+exports.default = Scheduler;
diff --git a/build/templates/tasks.txt b/build/templates/tasks.txt
new file mode 100644
index 0000000..906bd18
--- /dev/null
+++ b/build/templates/tasks.txt
@@ -0,0 +1,41 @@
+import Scheduler from '@ioc:Verful/Scheduler'
+
+/*
+|--------------------------------------------------------------------------
+| Scheduled tasks
+|--------------------------------------------------------------------------
+|
+| Scheduled tasks allow you to run recurrent tasks in the background of your
+| application. Here you can define all your scheduled tasks.
+|
+| You can define a scheduled task using the `.call` method on the Scheduler object
+| as shown in the following example
+|
+| ```
+| Scheduler.call(() => {
+| console.log('I am a scheduled task')
+| }).everyMinute()
+| ```
+|
+| The example above will print the message `I am a scheduled task` every minute.
+|
+| You can also schedule ace commands using the `.command` method on the Scheduler
+| object as shown in the following example
+|
+| ```
+| Scheduler.command('greet').everyMinute()
+| ```
+|
+| The example above will run the `greet` command every minute.
+|
+| You can also schedule shell commands with arguments using the `.exec` method on the Scheduler
+| object as shown in the following example
+|
+| ```
+| Scheduler.exec('node ace greet').everyMinute()
+| ```
+|
+| The example above will run the `node ace greet` command every minute.
+|
+| Happy scheduling!
+*/
diff --git a/build/tests/manages_frequencies.spec.d.ts b/build/tests/manages_frequencies.spec.d.ts
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/build/tests/manages_frequencies.spec.d.ts
@@ -0,0 +1 @@
+export {};
diff --git a/build/tests/manages_frequencies.spec.js b/build/tests/manages_frequencies.spec.js
new file mode 100644
index 0000000..962e205
--- /dev/null
+++ b/build/tests/manages_frequencies.spec.js
@@ -0,0 +1,232 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const runner_1 = require("@japa/runner");
+const sinon_1 = __importDefault(require("sinon"));
+const manages_frequencies_1 = __importDefault(require("../src/manages_frequencies"));
+runner_1.test.group('ManagesFrequencies', (group) => {
+ group.each.setup(() => {
+ return () => sinon_1.default.restore();
+ });
+ (0, runner_1.test)('can create instance', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ assert.instanceOf(managesFrequencies, manages_frequencies_1.default);
+ });
+ (0, runner_1.test)('can set timezone', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ const timezone = 'America/New_York';
+ managesFrequencies.timezone(timezone);
+ assert.equal(managesFrequencies['currentTimezone'], timezone);
+ });
+ (0, runner_1.test)('can set expression with cron', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ const expression = '0 0 * * *';
+ managesFrequencies.cron(expression);
+ assert.equal(managesFrequencies['expression'], expression);
+ });
+ (0, runner_1.test)('can set expression for every minute', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyMinute();
+ assert.equal(managesFrequencies['expression'], '* * * * *');
+ });
+ (0, runner_1.test)('can set expression for every two minutes', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyTwoMinutes();
+ assert.equal(managesFrequencies['expression'], '*/2 * * * *');
+ });
+ (0, runner_1.test)('can set expression for every three minutes', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyThreeMinutes();
+ assert.equal(managesFrequencies['expression'], '*/3 * * * *');
+ });
+ (0, runner_1.test)('can set expression for every four minutes', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyFourMinutes();
+ assert.equal(managesFrequencies['expression'], '*/4 * * * *');
+ });
+ (0, runner_1.test)('can set expression for every five minutes', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyFiveMinutes();
+ assert.equal(managesFrequencies['expression'], '*/5 * * * *');
+ });
+ (0, runner_1.test)('can set expression for every ten minutes', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyTenMinutes();
+ assert.equal(managesFrequencies['expression'], '*/10 * * * *');
+ });
+ (0, runner_1.test)('can set expression for every fifteen minutes', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyFifteenMinutes();
+ assert.equal(managesFrequencies['expression'], '*/15 * * * *');
+ });
+ (0, runner_1.test)('can set expression for every thirty minutes', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyThirtyMinutes();
+ assert.equal(managesFrequencies['expression'], '0,30 * * * *');
+ });
+ (0, runner_1.test)('can set expression for hourly', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.hourly();
+ assert.equal(managesFrequencies['expression'], '0 * * * *');
+ });
+ (0, runner_1.test)('can set expression for hourly at offset', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.hourlyAt(3);
+ assert.equal(managesFrequencies['expression'], '3 * * * *');
+ });
+ (0, runner_1.test)('can set expression for hourly at multiple offsets', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.hourlyAt([3, 9]);
+ assert.equal(managesFrequencies['expression'], '3,9 * * * *');
+ });
+ (0, runner_1.test)('can set expression for every two hours', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyTwoHours();
+ assert.equal(managesFrequencies['expression'], '0 */2 * * *');
+ });
+ (0, runner_1.test)('can set expression for every three hours', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyThreeHours();
+ assert.equal(managesFrequencies['expression'], '0 */3 * * *');
+ });
+ (0, runner_1.test)('can set expression for every four hours', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyFourHours();
+ assert.equal(managesFrequencies['expression'], '0 */4 * * *');
+ });
+ (0, runner_1.test)('can set expression for every six hours', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everySixHours();
+ assert.equal(managesFrequencies['expression'], '0 */6 * * *');
+ });
+ (0, runner_1.test)('can set expression for daily', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.daily();
+ assert.equal(managesFrequencies['expression'], '0 0 * * *');
+ });
+ (0, runner_1.test)('can set expression for daily at specific time', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.dailyAt('12:30');
+ assert.equal(managesFrequencies['expression'], '30 12 * * *');
+ });
+ (0, runner_1.test)('can set expression for twice daily', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.twiceDaily();
+ assert.equal(managesFrequencies['expression'], '0 0,12 * * *');
+ });
+ (0, runner_1.test)('can set expression for twice daily at specific times', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.twiceDailyAt(1, 13);
+ assert.equal(managesFrequencies['expression'], '0 1,13 * * *');
+ });
+ (0, runner_1.test)('can set expression for weekly', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.weekly();
+ assert.equal(managesFrequencies['expression'], '0 0 * * 0');
+ });
+ (0, runner_1.test)('can set expression for weekly on specific days', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.weeklyOn([2, 5], '15:00');
+ assert.equal(managesFrequencies['expression'], '0 15 * * 2,5');
+ });
+ (0, runner_1.test)('can set days of the week', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ const daysOfWeek = [1, 2, 3];
+ managesFrequencies.days(daysOfWeek);
+ assert.equal(managesFrequencies['expression'], '* * * * 1,2,3');
+ });
+ (0, runner_1.test)('can set weekdays', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.weekdays();
+ assert.equal(managesFrequencies['expression'], '* * * * 1-5');
+ });
+ (0, runner_1.test)('can set weekends', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.weekends();
+ assert.equal(managesFrequencies['expression'], '* * * * 6,7');
+ });
+ (0, runner_1.test)('can set specific day of the week', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.sundays();
+ assert.equal(managesFrequencies['expression'], '* * * * 7');
+ });
+ (0, runner_1.test)('can set expression for mondays', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.mondays();
+ assert.equal(managesFrequencies['expression'], '* * * * 1');
+ });
+ (0, runner_1.test)('can set expression for tuesdays', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.tuesdays();
+ assert.equal(managesFrequencies['expression'], '* * * * 2');
+ });
+ (0, runner_1.test)('can set expression for wednesdays', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.wednesdays();
+ assert.equal(managesFrequencies['expression'], '* * * * 3');
+ });
+ (0, runner_1.test)('can set expression for thursdays', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.thursdays();
+ assert.equal(managesFrequencies['expression'], '* * * * 4');
+ });
+ (0, runner_1.test)('can set expression for fridays', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.fridays();
+ assert.equal(managesFrequencies['expression'], '* * * * 5');
+ });
+ (0, runner_1.test)('can set expression for saturdays', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.saturdays();
+ assert.equal(managesFrequencies['expression'], '* * * * 6');
+ });
+ (0, runner_1.test)('can combine frequency methods', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.everyTwoHours().weekdays();
+ assert.equal(managesFrequencies['expression'], '0 */2 * * 1-5');
+ });
+ (0, runner_1.test)('can combine frequency methods for specific days', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.weeklyOn([2, 4], '12:00');
+ assert.equal(managesFrequencies['expression'], '0 12 * * 2,4');
+ });
+ (0, runner_1.test)('can set expression for monthly', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.monthly();
+ assert.equal(managesFrequencies['expression'], '0 0 1 * *');
+ });
+ (0, runner_1.test)('can set expression for monthly on specific day and time', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.monthlyOn(15, '14:30');
+ assert.equal(managesFrequencies['expression'], '30 14 15 * *');
+ });
+ (0, runner_1.test)('can set expression for twice monthly', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.twiceMonthly();
+ assert.equal(managesFrequencies['expression'], '0 0 1,16 * *');
+ });
+ (0, runner_1.test)('can set expression for last day of month', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.lastDayOfMonth();
+ const lastDayOfMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0);
+ const expectedExpression = `0 0 ${lastDayOfMonth.getDate()} * *`;
+ assert.equal(managesFrequencies['expression'], expectedExpression);
+ });
+ (0, runner_1.test)('can set expression for quarterly', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.quarterly();
+ assert.equal(managesFrequencies['expression'], '0 0 1 1-12/3 *');
+ });
+ (0, runner_1.test)('can set expression for yearly', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.yearly();
+ assert.equal(managesFrequencies['expression'], '0 0 1 1 *');
+ });
+ (0, runner_1.test)('can set expression for yearly on specific month and day', ({ assert }) => {
+ const managesFrequencies = new manages_frequencies_1.default();
+ managesFrequencies.yearlyOn(6, 20, '10:45');
+ assert.equal(managesFrequencies['expression'], '45 10 20 6 *');
+ });
+});
diff --git a/build/tests/schedule.spec.d.ts b/build/tests/schedule.spec.d.ts
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/build/tests/schedule.spec.d.ts
@@ -0,0 +1 @@
+export {};
diff --git a/build/tests/schedule.spec.js b/build/tests/schedule.spec.js
new file mode 100644
index 0000000..0ae183f
--- /dev/null
+++ b/build/tests/schedule.spec.js
@@ -0,0 +1,80 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const runner_1 = require("@japa/runner");
+const luxon_1 = require("luxon");
+const sinon_1 = __importDefault(require("sinon"));
+const schedule_1 = __importDefault(require("../src/schedule"));
+runner_1.test.group('Schedule', (group) => {
+ group.each.setup(() => {
+ sinon_1.default.stub(luxon_1.DateTime, 'now').returns(luxon_1.DateTime.fromObject({
+ year: 2021,
+ month: 7,
+ day: 31,
+ hour: 7,
+ minute: 30,
+ }));
+ return () => {
+ sinon_1.default.restore();
+ };
+ });
+ (0, runner_1.test)('can set between condition', ({ assert }) => {
+ const schedule = new schedule_1.default({}, () => { });
+ const start = '7:00';
+ const end = '8:00';
+ schedule.between(start, end);
+ assert.lengthOf(schedule.filters, 1);
+ assert.isFunction(schedule.filters[0]);
+ });
+ (0, runner_1.test)('can set unlessBetween condition', ({ assert }) => {
+ const schedule = new schedule_1.default({}, () => { });
+ const start = '7:00';
+ const end = '8:00';
+ schedule.unlessBetween(start, end);
+ assert.lengthOf(schedule.rejects, 1);
+ assert.isFunction(schedule.rejects[0]);
+ });
+ (0, runner_1.test)('can set between condition that wraps midnight', ({ assert }) => {
+ const schedule = new schedule_1.default({}, () => { });
+ const start = '23:00';
+ const end = '1:00';
+ schedule.between(start, end);
+ assert.lengthOf(schedule.filters, 1);
+ assert.isFunction(schedule.filters[0]);
+ });
+ (0, runner_1.test)('can set unlessBetween condition that wraps midnight', ({ assert }) => {
+ const schedule = new schedule_1.default({}, () => { });
+ const start = '23:00';
+ const end = '1:00';
+ schedule.unlessBetween(start, end);
+ assert.lengthOf(schedule.rejects, 1);
+ assert.isFunction(schedule.rejects[0]);
+ });
+ (0, runner_1.test)('time interval check is correct', ({ assert }) => {
+ const schedule = new schedule_1.default({}, () => { });
+ assert.isTrue(schedule['inTimeInterval']('7:00', '8:00')());
+ assert.isTrue(schedule['inTimeInterval']('23:00', '8:00')());
+ assert.isFalse(schedule['inTimeInterval']('6:00', '7:00')());
+ assert.isFalse(schedule['inTimeInterval']('23:00', '1:00')());
+ });
+ (0, runner_1.test)('can set skip condition', ({ assert }) => {
+ const schedule = new schedule_1.default({}, () => { });
+ schedule.skip(() => true);
+ assert.lengthOf(schedule.rejects, 1);
+ assert.isFunction(schedule.rejects[0]);
+ });
+ (0, runner_1.test)('can set when condition', ({ assert }) => {
+ const schedule = new schedule_1.default({}, () => { });
+ schedule.when(() => true);
+ assert.lengthOf(schedule.filters, 1);
+ assert.isFunction(schedule.filters[0]);
+ });
+ (0, runner_1.test)('can set environments condition', ({ assert }) => {
+ const schedule = new schedule_1.default({ nodeEnvironment: 'test' }, () => { });
+ schedule.environments(['test']);
+ assert.lengthOf(schedule.filters, 1);
+ assert.isFunction(schedule.filters[0]);
+ });
+});
diff --git a/build/tests/scheduler_provider.spec.d.ts b/build/tests/scheduler_provider.spec.d.ts
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/build/tests/scheduler_provider.spec.d.ts
@@ -0,0 +1 @@
+export {};
diff --git a/build/tests/scheduler_provider.spec.js b/build/tests/scheduler_provider.spec.js
new file mode 100644
index 0000000..693bbe3
--- /dev/null
+++ b/build/tests/scheduler_provider.spec.js
@@ -0,0 +1,12 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const runner_1 = require("@japa/runner");
+const scheduler_1 = __importDefault(require("../src/scheduler"));
+runner_1.test.group('SchedulerProvider', () => {
+ (0, runner_1.test)('Bindings registered correctly', ({ assert, app }) => {
+ assert.instanceOf(app.container.resolveBinding('Verful/Scheduler'), scheduler_1.default);
+ });
+});
diff --git a/build/tests/sheduler.spec.d.ts b/build/tests/sheduler.spec.d.ts
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/build/tests/sheduler.spec.d.ts
@@ -0,0 +1 @@
+export {};
diff --git a/build/tests/sheduler.spec.js b/build/tests/sheduler.spec.js
new file mode 100644
index 0000000..152c1f4
--- /dev/null
+++ b/build/tests/sheduler.spec.js
@@ -0,0 +1,83 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const runner_1 = require("@japa/runner");
+const execa_1 = __importDefault(require("execa"));
+const node_cron_1 = __importDefault(require("node-cron"));
+const sinon_1 = __importDefault(require("sinon"));
+const scheduler_1 = __importDefault(require("../src/scheduler"));
+runner_1.test.group('Scheduler', (group) => {
+ group.each.setup(() => {
+ return () => sinon_1.default.restore();
+ });
+ (0, runner_1.test)('calls a callback on scheduling', ({ app, assert }) => {
+ const scheduler = new scheduler_1.default(app);
+ const callback = sinon_1.default.stub();
+ scheduler.call(callback);
+ assert.lengthOf(scheduler['events'], 1);
+ });
+ (0, runner_1.test)('schedules a command to be executed', ({ app, assert }) => {
+ sinon_1.default.stub(execa_1.default, 'node');
+ const scheduler = new scheduler_1.default(app);
+ const command = 'some:command';
+ scheduler.command(command);
+ assert.lengthOf(scheduler['events'], 1);
+ });
+ (0, runner_1.test)('schedules an execution of a command', ({ app, assert }) => {
+ sinon_1.default.stub(execa_1.default, 'node');
+ const scheduler = new scheduler_1.default(app);
+ const command = 'some:command';
+ scheduler.exec(command);
+ assert.lengthOf(scheduler['events'], 1);
+ });
+ (0, runner_1.test)('the scheduler executes scheduled functions', async ({ app, assert }) => {
+ assert.plan(3);
+ const scheduler = new scheduler_1.default(app);
+ const mockFilter = sinon_1.default.stub().resolves(true);
+ const mockReject = sinon_1.default.stub().resolves(false);
+ const mockCommand = sinon_1.default.stub().resolves('Command executed');
+ scheduler.call(mockCommand).when(mockFilter).skip(mockReject).everyMinute();
+ sinon_1.default.stub(node_cron_1.default, 'schedule').callsFake((expression, cronCallback) => {
+ cronCallback().then(() => {
+ assert.isTrue(mockFilter.called);
+ assert.isTrue(mockReject.called);
+ assert.isTrue(mockCommand.called);
+ });
+ });
+ await scheduler.start();
+ });
+ (0, runner_1.test)('the scheduler executes scheduled ace commands', async ({ app, assert }) => {
+ assert.plan(3);
+ const scheduler = new scheduler_1.default(app);
+ const mockFilter = sinon_1.default.stub().resolves(true);
+ const mockReject = sinon_1.default.stub().resolves(false);
+ scheduler.command('make:user').when(mockFilter).skip(mockReject).everyMinute();
+ const execaStub = sinon_1.default.stub(execa_1.default, 'node').resolves({ stdout: 'Command executed' });
+ sinon_1.default.stub(node_cron_1.default, 'schedule').callsFake((expression, cronCallback) => {
+ cronCallback().then(() => {
+ assert.isTrue(mockFilter.called);
+ assert.isTrue(mockReject.called);
+ assert.isTrue(execaStub.called);
+ });
+ });
+ await scheduler.start();
+ });
+ (0, runner_1.test)('the scheduler executes scheduled shell commands', async ({ app, assert }) => {
+ assert.plan(3);
+ const scheduler = new scheduler_1.default(app);
+ const mockFilter = sinon_1.default.stub().resolves(true);
+ const mockReject = sinon_1.default.stub().resolves(false);
+ scheduler.exec('node ace make:user').when(mockFilter).skip(mockReject).everyMinute();
+ const execaStub = sinon_1.default.stub(execa_1.default, 'command').resolves({ stdout: 'Command executed' });
+ sinon_1.default.stub(node_cron_1.default, 'schedule').callsFake((expression, cronCallback) => {
+ cronCallback().then(() => {
+ assert.isTrue(mockFilter.called);
+ assert.isTrue(mockReject.called);
+ assert.isTrue(execaStub.called);
+ });
+ });
+ await scheduler.start();
+ });
+});