-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcron.mts
More file actions
118 lines (102 loc) · 3.69 KB
/
cron.mts
File metadata and controls
118 lines (102 loc) · 3.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import Database from 'better-sqlite3';
import { CronExpressionParser } from 'cron-parser';
import cronstrue from 'cronstrue';
export interface CronRow {
name: string;
prompt: string;
cron: string;
cronHuman: string;
enabled: boolean;
last_run: string | null;
run_at: string | null;
created_at: string;
}
export class CronManager {
private db: Database.Database;
constructor(db: Database.Database) {
this.db = db;
db.exec(`
CREATE TABLE IF NOT EXISTS crons (
name TEXT PRIMARY KEY,
prompt TEXT NOT NULL,
cron TEXT NOT NULL DEFAULT '',
enabled INTEGER DEFAULT 1,
last_run TEXT,
run_at TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
`);
const cols = db.pragma('table_info(crons)') as Array<{ name: string }>;
const colNames = new Set(cols.map(c => c.name));
if (colNames.has('command') && !colNames.has('prompt')) {
db.exec('ALTER TABLE crons RENAME COLUMN command TO prompt');
}
if (!colNames.has('run_at')) {
db.exec('ALTER TABLE crons ADD COLUMN run_at TEXT');
}
}
create(name: string, prompt: string, cron: string): void {
CronExpressionParser.parse(cron);
this.db.prepare(
'INSERT OR REPLACE INTO crons (name, prompt, cron, run_at) VALUES (?, ?, ?, NULL)'
).run(name, prompt, cron);
}
createOneShot(name: string, prompt: string, delaySec: number): void {
const runAt = new Date(Date.now() + delaySec * 1000).toISOString().replace('T', ' ').slice(0, 19);
this.db.prepare(
"INSERT OR REPLACE INTO crons (name, prompt, cron, run_at) VALUES (?, ?, '', ?)"
).run(name, prompt, runAt);
}
pause(name: string): boolean {
return this.db.prepare('UPDATE crons SET enabled = 0 WHERE name = ?').run(name).changes > 0;
}
resume(name: string): boolean {
return this.db.prepare('UPDATE crons SET enabled = 1 WHERE name = ?').run(name).changes > 0;
}
delete(name: string): boolean {
return this.db.prepare('DELETE FROM crons WHERE name = ?').run(name).changes > 0;
}
setEnabled(name: string, enabled: boolean): boolean {
return this.db.prepare('UPDATE crons SET enabled = ? WHERE name = ?').run(enabled ? 1 : 0, name).changes > 0;
}
list(): CronRow[] {
const rows = this.db.prepare('SELECT * FROM crons ORDER BY created_at').all() as Array<{
name: string; prompt: string; cron: string; enabled: number; last_run: string | null; run_at: string | null; created_at: string;
}>;
return rows.map(r => ({
name: r.name,
prompt: r.prompt,
cron: r.cron,
cronHuman: r.run_at ? `once at ${r.run_at}Z` : cronstrue.toString(r.cron),
enabled: r.enabled === 1,
last_run: r.last_run,
run_at: r.run_at,
created_at: r.created_at,
}));
}
checkDue(): Array<{ name: string; prompt: string }> {
const rows = this.db.prepare(
'SELECT name, prompt, cron, last_run, run_at, created_at FROM crons WHERE enabled = 1'
).all() as Array<{ name: string; prompt: string; cron: string; last_run: string | null; run_at: string | null; created_at: string }>;
const due: Array<{ name: string; prompt: string }> = [];
const now = new Date();
for (const row of rows) {
if (row.run_at) {
// One-shot: fire when run_at has passed
if (new Date(row.run_at + 'Z') <= now) {
due.push({ name: row.name, prompt: row.prompt });
this.db.prepare('DELETE FROM crons WHERE name = ?').run(row.name);
}
continue;
}
const interval = CronExpressionParser.parse(row.cron, { currentDate: now, tz: process.env.USER_TZ });
const prev = interval.prev().toDate();
const baseline = new Date((row.last_run ?? row.created_at) + 'Z');
if (baseline < prev) {
due.push({ name: row.name, prompt: row.prompt });
this.db.prepare("UPDATE crons SET last_run = datetime('now') WHERE name = ?").run(row.name);
}
}
return due;
}
}