@arkstack/scheduler 0.17.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.
- package/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/commands/ScheduleListCommand.d.ts +14 -0
- package/dist/commands/ScheduleListCommand.js +28 -0
- package/dist/commands/ScheduleRunCommand.d.ts +18 -0
- package/dist/commands/ScheduleRunCommand.js +33 -0
- package/dist/commands/ScheduleWorkCommand.d.ts +15 -0
- package/dist/commands/ScheduleWorkCommand.js +28 -0
- package/dist/index.d.ts +326 -0
- package/dist/index.js +3 -0
- package/dist/loader-zz_XJXwZ.js +666 -0
- package/dist/runner-DFEYebtg.js +26 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +21 -0
- package/package.json +57 -0
- package/stubs/routes/console.ts.stub +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Toneflix Technologies Limited
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @arkstack/scheduler
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@arkstack/scheduler)
|
|
4
|
+
|
|
5
|
+
Task scheduling for [Arkstack](https://arkstack.toneflix.net). Define scheduled tasks fluently in code instead of managing a crowd of cron entries — a single system cron entry drives everything.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm add @arkstack/scheduler
|
|
11
|
+
ark publish --package @arkstack/scheduler # writes src/routes/console.ts
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Full app templates include it and a `src/routes/console.ts` already.
|
|
15
|
+
|
|
16
|
+
## Define your schedule
|
|
17
|
+
|
|
18
|
+
In `src/routes/console.ts`:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { Schedule } from '@arkstack/scheduler';
|
|
22
|
+
|
|
23
|
+
Schedule.command('cache:prune').hourly();
|
|
24
|
+
|
|
25
|
+
Schedule.call(async () => {
|
|
26
|
+
await pruneTempFiles();
|
|
27
|
+
})
|
|
28
|
+
.dailyAt('01:30')
|
|
29
|
+
.withoutOverlapping();
|
|
30
|
+
|
|
31
|
+
Schedule.job(new SendDigest()).weeklyOn(1, '08:00');
|
|
32
|
+
|
|
33
|
+
Schedule.exec('backup.sh').daily().onOneServer();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Task type | Runs |
|
|
37
|
+
| ------------------------------- | ---------------------------------------------- |
|
|
38
|
+
| `Schedule.command(name, args?)` | An Arkstack CLI command (`ark <name>`) |
|
|
39
|
+
| `Schedule.call(fn)` | A callback |
|
|
40
|
+
| `Schedule.job(job)` | A queued job (dispatched via `@arkstack/jobs`) |
|
|
41
|
+
| `Schedule.exec(cmd, args?)` | A shell command |
|
|
42
|
+
|
|
43
|
+
## Run it
|
|
44
|
+
|
|
45
|
+
The scheduler evaluates due tasks once a minute. In production, add **one** cron entry:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
* * * * * cd /path/to/app && npx ark schedule:run >> /dev/null 2>&1
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
During development, run the foreground worker instead:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
ark schedule:work # evaluates the schedule every minute until stopped
|
|
55
|
+
ark schedule:list # list tasks with their next run time
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Frequencies
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
Schedule.command('x').everyMinute();
|
|
62
|
+
Schedule.command('x').everyFiveMinutes(); // */5 * * * *
|
|
63
|
+
Schedule.command('x').everyThirtyMinutes();
|
|
64
|
+
Schedule.command('x').hourly();
|
|
65
|
+
Schedule.command('x').hourlyAt(15);
|
|
66
|
+
Schedule.command('x').daily();
|
|
67
|
+
Schedule.command('x').dailyAt('13:00');
|
|
68
|
+
Schedule.command('x').twiceDaily(1, 13);
|
|
69
|
+
Schedule.command('x').weekly();
|
|
70
|
+
Schedule.command('x').weeklyOn(1, '8:00'); // Monday 08:00
|
|
71
|
+
Schedule.command('x').monthly();
|
|
72
|
+
Schedule.command('x').monthlyOn(15, '17:00');
|
|
73
|
+
Schedule.command('x').quarterly();
|
|
74
|
+
Schedule.command('x').yearly();
|
|
75
|
+
Schedule.command('x').cron('*/10 9-17 * * 1-5'); // raw cron
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Day-of-week constraints, with time layered on top:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
Schedule.command('x').weekdays().at('9:00');
|
|
82
|
+
Schedule.command('x').weekends().hourly();
|
|
83
|
+
Schedule.command('x').mondays();
|
|
84
|
+
Schedule.command('x').days([1, 4]); // Monday & Thursday
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Set the timezone the expression is evaluated in:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
Schedule.command('x').dailyAt('09:00').timezone('America/New_York');
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Constraints
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
Schedule.command('x')
|
|
97
|
+
.daily()
|
|
98
|
+
.when(async () => await featureEnabled('reports')) // run only when truthy
|
|
99
|
+
.skip(() => isHoliday()) // skip when truthy
|
|
100
|
+
.environments('production', 'staging') // limit by APP_ENV
|
|
101
|
+
.between('09:00', '17:00'); // only within a daily window
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`unlessBetween('22:00', '06:00')` runs _outside_ a window (overnight ranges handled).
|
|
105
|
+
|
|
106
|
+
## Overlaps, single-server & background
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
Schedule.command('report:build').everyFiveMinutes().withoutOverlapping();
|
|
110
|
+
Schedule.exec('backup.sh').daily().onOneServer();
|
|
111
|
+
Schedule.exec('long-import.sh').daily().runInBackground();
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`withoutOverlapping()` and `onOneServer()` coordinate through `@arkstack/cache`, so they work across processes and servers. Configure a shared cache store (`redis` or `database`) for `onOneServer()` to be effective; without a cache the scheduler falls back to a process-local lock.
|
|
115
|
+
|
|
116
|
+
## Hooks
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
Schedule.command('report:build')
|
|
120
|
+
.daily()
|
|
121
|
+
.before(() => logger.info('building report'))
|
|
122
|
+
.onSuccess(() => notifyOk())
|
|
123
|
+
.onFailure((error) => notifyFailed(error))
|
|
124
|
+
.after(() => logger.info('done'));
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## API
|
|
128
|
+
|
|
129
|
+
- `Schedule.command / call / job / exec` — register a task, returns a `ScheduledEvent` for chaining.
|
|
130
|
+
- `Schedule.events()` / `Schedule.dueEvents(date?)` — inspect registered / due events.
|
|
131
|
+
- `ScheduledEvent` — the fluent builder (frequencies, day constraints, `timezone`, `when`, `skip`, `environments`, `between`, `unlessBetween`, `withoutOverlapping`, `onOneServer`, `runInBackground`, `before`/`after`/`onSuccess`/`onFailure`, `description`, `name`).
|
|
132
|
+
- Commands: `schedule:run`, `schedule:work`, `schedule:list`.
|
|
133
|
+
|
|
134
|
+
Cron evaluation is powered by [croner](https://github.com/hexagon/croner). See the [scheduling guide](https://arkstack.toneflix.net/guide/scheduling) for the full walkthrough.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from "@h3ravel/musket";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/ScheduleListCommand.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* List the application's scheduled tasks with their cron expression and next
|
|
6
|
+
* run time.
|
|
7
|
+
*/
|
|
8
|
+
declare class ScheduleListCommand extends Command {
|
|
9
|
+
protected signature: string;
|
|
10
|
+
protected description: string;
|
|
11
|
+
handle(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { ScheduleListCommand };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { n as Schedule, t as loadSchedule } from "../loader-zz_XJXwZ.js";
|
|
2
|
+
import { Command } from "@h3ravel/musket";
|
|
3
|
+
//#region src/commands/ScheduleListCommand.ts
|
|
4
|
+
/**
|
|
5
|
+
* List the application's scheduled tasks with their cron expression and next
|
|
6
|
+
* run time.
|
|
7
|
+
*/
|
|
8
|
+
var ScheduleListCommand = class extends Command {
|
|
9
|
+
signature = "schedule:list";
|
|
10
|
+
description = "List the scheduled tasks.";
|
|
11
|
+
async handle() {
|
|
12
|
+
const loaded = await loadSchedule();
|
|
13
|
+
const events = Schedule.events();
|
|
14
|
+
if (!events.length) {
|
|
15
|
+
this.warn(loaded ? "No scheduled tasks are defined in src/routes/console.ts." : "No src/routes/console.ts found.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const now = /* @__PURE__ */ new Date();
|
|
19
|
+
for (const event of events) {
|
|
20
|
+
const next = event.nextRunAt(now);
|
|
21
|
+
const when = next ? `next: ${next.toISOString()}` : "next: —";
|
|
22
|
+
this.line(` ${event.expression.padEnd(16)} ${event.descriptionValue.padEnd(28)} ${when}`);
|
|
23
|
+
}
|
|
24
|
+
this.info(`${events.length} scheduled task(s).`);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { ScheduleListCommand };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from "@h3ravel/musket";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/ScheduleRunCommand.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Run the scheduled tasks that are due right now. Invoke this once a minute from
|
|
6
|
+
* cron:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* * * * * * cd /path/to/app && npx ark schedule:run >> /dev/null 2>&1
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
declare class ScheduleRunCommand extends Command {
|
|
13
|
+
protected signature: string;
|
|
14
|
+
protected description: string;
|
|
15
|
+
handle(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { ScheduleRunCommand };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { t as loadSchedule } from "../loader-zz_XJXwZ.js";
|
|
2
|
+
import { t as runDueEvents } from "../runner-DFEYebtg.js";
|
|
3
|
+
import { Command } from "@h3ravel/musket";
|
|
4
|
+
//#region src/commands/ScheduleRunCommand.ts
|
|
5
|
+
/**
|
|
6
|
+
* Run the scheduled tasks that are due right now. Invoke this once a minute from
|
|
7
|
+
* cron:
|
|
8
|
+
*
|
|
9
|
+
* ```
|
|
10
|
+
* * * * * * cd /path/to/app && npx ark schedule:run >> /dev/null 2>&1
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
var ScheduleRunCommand = class extends Command {
|
|
14
|
+
signature = "schedule:run";
|
|
15
|
+
description = "Run the scheduled tasks that are due.";
|
|
16
|
+
async handle() {
|
|
17
|
+
if (!await loadSchedule()) {
|
|
18
|
+
this.warn("No src/routes/console.ts found — nothing to schedule.");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const results = await runDueEvents(/* @__PURE__ */ new Date());
|
|
22
|
+
if (!results.length) {
|
|
23
|
+
this.info("No scheduled tasks are ready to run.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const result of results) if (result.ran) this.success(`Ran: ${result.description}`);
|
|
27
|
+
else if (result.error) this.error(`Failed: ${result.description}`);
|
|
28
|
+
else this.line(`Skipped (${result.skipped}): ${result.description}`);
|
|
29
|
+
this.info(`${results.filter((r) => r.ran).length} task(s) ran.`);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { ScheduleRunCommand };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from "@h3ravel/musket";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/ScheduleWorkCommand.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Run the scheduler in the foreground, evaluating due tasks at the top of every
|
|
6
|
+
* minute. Intended for local development so you don't need a system cron entry;
|
|
7
|
+
* in production use a single `* * * * *` cron running `schedule:run`.
|
|
8
|
+
*/
|
|
9
|
+
declare class ScheduleWorkCommand extends Command {
|
|
10
|
+
protected signature: string;
|
|
11
|
+
protected description: string;
|
|
12
|
+
handle(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { ScheduleWorkCommand };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { t as loadSchedule } from "../loader-zz_XJXwZ.js";
|
|
2
|
+
import { t as runDueEvents } from "../runner-DFEYebtg.js";
|
|
3
|
+
import { Command } from "@h3ravel/musket";
|
|
4
|
+
//#region src/commands/ScheduleWorkCommand.ts
|
|
5
|
+
/**
|
|
6
|
+
* Run the scheduler in the foreground, evaluating due tasks at the top of every
|
|
7
|
+
* minute. Intended for local development so you don't need a system cron entry;
|
|
8
|
+
* in production use a single `* * * * *` cron running `schedule:run`.
|
|
9
|
+
*/
|
|
10
|
+
var ScheduleWorkCommand = class extends Command {
|
|
11
|
+
signature = "schedule:work";
|
|
12
|
+
description = "Run the scheduler every minute in the foreground (for local development).";
|
|
13
|
+
async handle() {
|
|
14
|
+
if (!await loadSchedule()) {
|
|
15
|
+
this.warn("No src/routes/console.ts found — nothing to schedule.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.info("Schedule worker started; evaluating tasks every minute. Press Ctrl+C to stop.");
|
|
19
|
+
for (;;) {
|
|
20
|
+
const results = await runDueEvents(/* @__PURE__ */ new Date());
|
|
21
|
+
for (const result of results) if (result.ran) this.success(`Ran: ${result.description}`);
|
|
22
|
+
else if (result.error) this.error(`Failed: ${result.description}`);
|
|
23
|
+
await new Promise((resolve) => setTimeout(resolve, 6e4 - Date.now() % 6e4 + 1e3));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { ScheduleWorkCommand };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/** A no-arg task the scheduler runs when an event is due. */
|
|
3
|
+
type TaskCallback = () => void | Promise<void>;
|
|
4
|
+
/** A predicate gating whether a due event actually runs. */
|
|
5
|
+
type FilterCallback = () => boolean | Promise<boolean>;
|
|
6
|
+
/** A lifecycle hook (before/after/onSuccess/onFailure). */
|
|
7
|
+
type HookCallback = (error?: unknown) => void | Promise<void>;
|
|
8
|
+
/** How an event's task is produced — used for `schedule:list` output. */
|
|
9
|
+
type EventType = 'command' | 'call' | 'job' | 'exec';
|
|
10
|
+
/** The result of running one scheduled event. */
|
|
11
|
+
interface RunResult {
|
|
12
|
+
description: string;
|
|
13
|
+
expression: string;
|
|
14
|
+
ran: boolean;
|
|
15
|
+
/** Why it did not run, when `ran` is false. */
|
|
16
|
+
skipped?: 'not-due' | 'filtered' | 'overlapping' | 'one-server' | 'environment';
|
|
17
|
+
error?: unknown;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/ScheduledEvent.d.ts
|
|
21
|
+
/**
|
|
22
|
+
* A single scheduled task with a fluent builder for its frequency, constraints,
|
|
23
|
+
* overlap behaviour and lifecycle hooks. Created via the {@link Schedule} facade
|
|
24
|
+
* (`command`/`call`/`job`/`exec`) and evaluated once a minute by `schedule:run`.
|
|
25
|
+
*/
|
|
26
|
+
declare class ScheduledEvent {
|
|
27
|
+
readonly type: EventType;
|
|
28
|
+
private segments;
|
|
29
|
+
private timezoneValue?;
|
|
30
|
+
descriptionValue: string;
|
|
31
|
+
private readonly filters;
|
|
32
|
+
private readonly rejects;
|
|
33
|
+
private environmentsValue?;
|
|
34
|
+
private readonly windows;
|
|
35
|
+
private readonly beforeHooks;
|
|
36
|
+
private readonly afterHooks;
|
|
37
|
+
private readonly successHooks;
|
|
38
|
+
private readonly failureHooks;
|
|
39
|
+
private withoutOverlappingValue;
|
|
40
|
+
private overlapExpiresMinutes;
|
|
41
|
+
private onOneServerValue;
|
|
42
|
+
private runInBackgroundValue;
|
|
43
|
+
private mutexNameValue?;
|
|
44
|
+
private callTask?;
|
|
45
|
+
private jobPayload?;
|
|
46
|
+
private processCommand?;
|
|
47
|
+
private processArgs;
|
|
48
|
+
constructor(type: EventType, target: string);
|
|
49
|
+
setCall(task: TaskCallback): this;
|
|
50
|
+
setJob(job: unknown): this;
|
|
51
|
+
setProcess(command: string, args?: string[]): this;
|
|
52
|
+
/** The 5-field cron expression this event resolves to. */
|
|
53
|
+
get expression(): string;
|
|
54
|
+
private splice;
|
|
55
|
+
cron(expression: string): this;
|
|
56
|
+
everyMinute(): this;
|
|
57
|
+
everyTwoMinutes(): this;
|
|
58
|
+
everyThreeMinutes(): this;
|
|
59
|
+
everyFourMinutes(): this;
|
|
60
|
+
everyFiveMinutes(): this;
|
|
61
|
+
everyTenMinutes(): this;
|
|
62
|
+
everyFifteenMinutes(): this;
|
|
63
|
+
everyThirtyMinutes(): this;
|
|
64
|
+
hourly(): this;
|
|
65
|
+
hourlyAt(minute: number | number[]): this;
|
|
66
|
+
everyTwoHours(minute?: number): this;
|
|
67
|
+
everyOddHour(minute?: number): this;
|
|
68
|
+
daily(): this;
|
|
69
|
+
at(time: string): this;
|
|
70
|
+
dailyAt(time: string): this;
|
|
71
|
+
twiceDaily(first?: number, second?: number, minute?: number): this;
|
|
72
|
+
weekly(): this;
|
|
73
|
+
weeklyOn(day: number | number[], time?: string): this;
|
|
74
|
+
monthly(): this;
|
|
75
|
+
monthlyOn(day?: number, time?: string): this;
|
|
76
|
+
quarterly(): this;
|
|
77
|
+
yearly(): this;
|
|
78
|
+
days(day: number | number[]): this;
|
|
79
|
+
weekdays(): this;
|
|
80
|
+
weekends(): this;
|
|
81
|
+
sundays(): this;
|
|
82
|
+
mondays(): this;
|
|
83
|
+
tuesdays(): this;
|
|
84
|
+
wednesdays(): this;
|
|
85
|
+
thursdays(): this;
|
|
86
|
+
fridays(): this;
|
|
87
|
+
saturdays(): this;
|
|
88
|
+
timezone(timezone: string): this;
|
|
89
|
+
/**
|
|
90
|
+
* Only run when every registered `when` predicate is truthy.
|
|
91
|
+
*
|
|
92
|
+
* @param callback
|
|
93
|
+
* @returns
|
|
94
|
+
*/
|
|
95
|
+
when(callback: FilterCallback): this;
|
|
96
|
+
/**
|
|
97
|
+
* Skip when any registered `skip` predicate is truthy.
|
|
98
|
+
*
|
|
99
|
+
* @param callback
|
|
100
|
+
* @returns
|
|
101
|
+
*/
|
|
102
|
+
skip(callback: FilterCallback): this;
|
|
103
|
+
/**
|
|
104
|
+
* Only run in these `APP_ENV` environments.
|
|
105
|
+
*
|
|
106
|
+
* @param callback
|
|
107
|
+
* @returns
|
|
108
|
+
*/
|
|
109
|
+
environments(...environments: (string | string[])[]): this;
|
|
110
|
+
/**
|
|
111
|
+
* Only run when the current time is within `[start, end]` (HH:MM, event tz).
|
|
112
|
+
*
|
|
113
|
+
* @param start
|
|
114
|
+
* @param end
|
|
115
|
+
* @returns
|
|
116
|
+
*/
|
|
117
|
+
between(start: string, end: string): this;
|
|
118
|
+
/**
|
|
119
|
+
* Only run when the current time is outside `[start, end]`.
|
|
120
|
+
*
|
|
121
|
+
* @param callback
|
|
122
|
+
* @returns
|
|
123
|
+
*/
|
|
124
|
+
unlessBetween(start: string, end: string): this;
|
|
125
|
+
private toMinutes;
|
|
126
|
+
/**
|
|
127
|
+
* Prevent the task from overlapping itself; the lock expires after `expiresMinutes`.
|
|
128
|
+
*
|
|
129
|
+
* @param expiresMinutes
|
|
130
|
+
* @returns
|
|
131
|
+
*/
|
|
132
|
+
withoutOverlapping(expiresMinutes?: number): this;
|
|
133
|
+
/**
|
|
134
|
+
* Run on only one server per due minute (requires a shared cache store).
|
|
135
|
+
*
|
|
136
|
+
* @param callback
|
|
137
|
+
* @returns
|
|
138
|
+
*/
|
|
139
|
+
onOneServer(): this;
|
|
140
|
+
/**
|
|
141
|
+
* Run the task in a detached background process (command/exec only).
|
|
142
|
+
*
|
|
143
|
+
* @param callback
|
|
144
|
+
* @returns
|
|
145
|
+
*/
|
|
146
|
+
runInBackground(): this;
|
|
147
|
+
/**
|
|
148
|
+
* A human description shown by `schedule:list`.
|
|
149
|
+
*
|
|
150
|
+
* @param callback
|
|
151
|
+
* @returns
|
|
152
|
+
*/
|
|
153
|
+
description(description: string): this;
|
|
154
|
+
/**
|
|
155
|
+
* An explicit mutex name (otherwise derived from the expression + description).
|
|
156
|
+
*
|
|
157
|
+
* @param name
|
|
158
|
+
* @returns
|
|
159
|
+
*/
|
|
160
|
+
name(name: string): this;
|
|
161
|
+
before(hook: HookCallback): this;
|
|
162
|
+
after(hook: HookCallback): this;
|
|
163
|
+
onSuccess(hook: HookCallback): this;
|
|
164
|
+
onFailure(hook: HookCallback): this;
|
|
165
|
+
/**
|
|
166
|
+
* Whether the cron expression is due at `date`.
|
|
167
|
+
*
|
|
168
|
+
* @param date
|
|
169
|
+
* @returns
|
|
170
|
+
*/
|
|
171
|
+
isDue(date?: Date): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* The next time this event will run after `from`.
|
|
174
|
+
*
|
|
175
|
+
* @param date
|
|
176
|
+
* @returns
|
|
177
|
+
*/
|
|
178
|
+
nextRunAt(from?: Date): Date | null;
|
|
179
|
+
/**
|
|
180
|
+
* Whether environment, time-window, `when` and `skip` constraints all pass.
|
|
181
|
+
*
|
|
182
|
+
* @param date
|
|
183
|
+
* @returns
|
|
184
|
+
*/
|
|
185
|
+
filtersPass(date?: Date): Promise<boolean>;
|
|
186
|
+
/**
|
|
187
|
+
* A stable mutex key derived from the expression + description (or an explicit name).
|
|
188
|
+
*
|
|
189
|
+
* @returns
|
|
190
|
+
*/
|
|
191
|
+
mutexName(): string;
|
|
192
|
+
/**
|
|
193
|
+
* Run the task now, honouring overlap/one-server locks and lifecycle hooks.
|
|
194
|
+
* Assumes the event is already due and its filters pass.
|
|
195
|
+
*
|
|
196
|
+
* @param date The reference moment (used for the one-server per-minute key).
|
|
197
|
+
*/
|
|
198
|
+
run(date?: Date): Promise<RunResult>;
|
|
199
|
+
private callHooks;
|
|
200
|
+
private executeTask;
|
|
201
|
+
private spawnProcess;
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/Schedule.d.ts
|
|
205
|
+
/**
|
|
206
|
+
* The scheduling facade. Define tasks in `src/routes/console.ts`:
|
|
207
|
+
*
|
|
208
|
+
* ```ts
|
|
209
|
+
* import { Schedule } from '@arkstack/scheduler'
|
|
210
|
+
*
|
|
211
|
+
* Schedule.command('report:send').dailyAt('13:00')
|
|
212
|
+
* Schedule.call(() => prune()).hourly().withoutOverlapping()
|
|
213
|
+
* Schedule.job(new Heartbeat()).everyFiveMinutes()
|
|
214
|
+
* Schedule.exec('backup.sh').daily().onOneServer()
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
declare class Schedule {
|
|
218
|
+
/**
|
|
219
|
+
* Run an Arkstack CLI command (`ark <name>`) on the schedule.
|
|
220
|
+
*
|
|
221
|
+
* @param name
|
|
222
|
+
* @param args
|
|
223
|
+
* @returns
|
|
224
|
+
*/
|
|
225
|
+
static command(name: string, args?: string[]): ScheduledEvent;
|
|
226
|
+
/**
|
|
227
|
+
* Run a callback on the schedule.
|
|
228
|
+
*
|
|
229
|
+
* @param callback
|
|
230
|
+
* @returns
|
|
231
|
+
*/
|
|
232
|
+
static call(callback: TaskCallback): ScheduledEvent;
|
|
233
|
+
/**
|
|
234
|
+
* Dispatch a queued job on the schedule (requires `@arkstack/jobs`).
|
|
235
|
+
*
|
|
236
|
+
* @param callback
|
|
237
|
+
* @returns
|
|
238
|
+
*/
|
|
239
|
+
static job(job: object): ScheduledEvent;
|
|
240
|
+
/**
|
|
241
|
+
* Run a shell command on the schedule.
|
|
242
|
+
*
|
|
243
|
+
* @param callback
|
|
244
|
+
* @returns
|
|
245
|
+
*/
|
|
246
|
+
static exec(command: string, args?: string[]): ScheduledEvent;
|
|
247
|
+
/**
|
|
248
|
+
* All registered events.
|
|
249
|
+
*
|
|
250
|
+
* @param callback
|
|
251
|
+
* @returns
|
|
252
|
+
*/
|
|
253
|
+
static events(): ScheduledEvent[];
|
|
254
|
+
/**
|
|
255
|
+
* Events whose cron expression is due at `date`.
|
|
256
|
+
*
|
|
257
|
+
* @param callback
|
|
258
|
+
* @returns
|
|
259
|
+
*/
|
|
260
|
+
static dueEvents(date?: Date): ScheduledEvent[];
|
|
261
|
+
/**
|
|
262
|
+
* Remove all registered events (used in tests).
|
|
263
|
+
*
|
|
264
|
+
* @param callback
|
|
265
|
+
* @returns
|
|
266
|
+
*/
|
|
267
|
+
static clear(): void;
|
|
268
|
+
private static add;
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region src/runner.d.ts
|
|
272
|
+
/**
|
|
273
|
+
* Run every event that is due at `now` and whose filters pass, collecting a
|
|
274
|
+
* result per event (including the ones skipped by filters or locks).
|
|
275
|
+
*
|
|
276
|
+
* @param now The reference moment (defaults to now).
|
|
277
|
+
*/
|
|
278
|
+
declare const runDueEvents: (now?: Date) => Promise<RunResult[]>;
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/loader.d.ts
|
|
281
|
+
/**
|
|
282
|
+
* Load the application's schedule definitions from `src/routes/console.ts`.
|
|
283
|
+
* The TypeScript source is preferred (loaded via jiti, so no build is needed),
|
|
284
|
+
* falling back to the built `<outDir>/routes/console.js`.
|
|
285
|
+
*
|
|
286
|
+
* @returns `true` when a console route file was found and loaded.
|
|
287
|
+
*/
|
|
288
|
+
declare const loadSchedule: () => Promise<boolean>;
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/cron.d.ts
|
|
291
|
+
/**
|
|
292
|
+
* Whether a cron expression is due at the given moment (minute resolution).
|
|
293
|
+
*
|
|
294
|
+
* Uses croner to find the next run at/after one second before `date`; the
|
|
295
|
+
* expression is due when that run falls in the same minute as `date`.
|
|
296
|
+
*
|
|
297
|
+
* @param expression A 5-field cron expression.
|
|
298
|
+
* @param date The moment to test (defaults to now).
|
|
299
|
+
* @param timezone IANA timezone the expression is evaluated in.
|
|
300
|
+
*/
|
|
301
|
+
declare const isDue: (expression: string, date?: Date, timezone?: string) => boolean;
|
|
302
|
+
/**
|
|
303
|
+
* The next time a cron expression will run after `from`.
|
|
304
|
+
*
|
|
305
|
+
* @param expression A 5-field cron expression.
|
|
306
|
+
* @param from The reference moment (defaults to now).
|
|
307
|
+
* @param timezone IANA timezone the expression is evaluated in.
|
|
308
|
+
*/
|
|
309
|
+
declare const nextRun: (expression: string, from?: Date, timezone?: string) => Date | null;
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/locks.d.ts
|
|
312
|
+
/**
|
|
313
|
+
* Try to acquire a lock. Returns `true` when acquired, `false` when already held.
|
|
314
|
+
*
|
|
315
|
+
* @param key The lock key.
|
|
316
|
+
* @param ttlSeconds How long the lock is held before it auto-expires.
|
|
317
|
+
*/
|
|
318
|
+
declare const acquireLock: (key: string, ttlSeconds: number) => Promise<boolean>;
|
|
319
|
+
/**
|
|
320
|
+
* Release a previously acquired lock.
|
|
321
|
+
*
|
|
322
|
+
* @param key The lock key.
|
|
323
|
+
*/
|
|
324
|
+
declare const releaseLock: (key: string) => Promise<void>;
|
|
325
|
+
//#endregion
|
|
326
|
+
export { EventType, FilterCallback, HookCallback, RunResult, Schedule, ScheduledEvent, TaskCallback, acquireLock, isDue, loadSchedule, nextRun, releaseLock, runDueEvents };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as nextRun, i as isDue, n as Schedule, o as acquireLock, r as ScheduledEvent, s as releaseLock, t as loadSchedule } from "./loader-zz_XJXwZ.js";
|
|
2
|
+
import { t as runDueEvents } from "./runner-DFEYebtg.js";
|
|
3
|
+
export { Schedule, ScheduledEvent, acquireLock, isDue, loadSchedule, nextRun, releaseLock, runDueEvents };
|