crnd 0.0.1 → 0.0.4

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.
Files changed (122) hide show
  1. package/bin/crnd +88 -0
  2. package/package.json +19 -43
  3. package/drizzle/0000_init.sql +0 -35
  4. package/drizzle/0001_add_runs.sql +0 -13
  5. package/drizzle/meta/_journal.json +0 -20
  6. package/src/cli/commands/createDeleteCommand.ts +0 -93
  7. package/src/cli/commands/createDoctorCommand.ts +0 -93
  8. package/src/cli/commands/createExportCommand.ts +0 -75
  9. package/src/cli/commands/createImportCommand.ts +0 -80
  10. package/src/cli/commands/createKillCommand.ts +0 -89
  11. package/src/cli/commands/createListCommand.ts +0 -67
  12. package/src/cli/commands/createLogsCommand.ts +0 -125
  13. package/src/cli/commands/createPauseCommand.ts +0 -78
  14. package/src/cli/commands/createResetCommand.ts +0 -78
  15. package/src/cli/commands/createResumeCommand.ts +0 -78
  16. package/src/cli/commands/createRootCommand.ts +0 -50
  17. package/src/cli/commands/createRunOnceCommand.ts +0 -77
  18. package/src/cli/commands/createRunsCommand.ts +0 -106
  19. package/src/cli/commands/createScheduleCommand.ts +0 -184
  20. package/src/cli/commands/createShowCommand.ts +0 -92
  21. package/src/cli/commands/createStatusCommand.ts +0 -145
  22. package/src/cli/commands/createStopCommand.ts +0 -89
  23. package/src/cli/commands/createUpdateCommand.ts +0 -13
  24. package/src/cli/commands/daemon/createDaemonCommand.ts +0 -24
  25. package/src/cli/commands/daemon/createDaemonInstallCommand.ts +0 -144
  26. package/src/cli/commands/daemon/createDaemonServeCommand.ts +0 -14
  27. package/src/cli/commands/daemon/createDaemonStartCommand.ts +0 -73
  28. package/src/cli/commands/daemon/createDaemonStatusCommand.ts +0 -5
  29. package/src/cli/commands/daemon/createDaemonStopCommand.ts +0 -59
  30. package/src/cli/commands/daemon/createDaemonUninstallCommand.ts +0 -99
  31. package/src/cli/commands/daemon/createLaunchdPlist.ts +0 -34
  32. package/src/cli/commands/daemon/createSystemdService.ts +0 -24
  33. package/src/cli/commands/daemon/escapeXml.ts +0 -8
  34. package/src/cli/commands/daemon/getDaemonServiceArgs.ts +0 -10
  35. package/src/cli/commands/daemon/quoteWindowsArg.ts +0 -4
  36. package/src/cli/commands/getCommandArgs.ts +0 -8
  37. package/src/cli/commands/parseEnvArgs.ts +0 -28
  38. package/src/cli/getDaemonSpawnArgs.ts +0 -9
  39. package/src/cli/main.ts +0 -8
  40. package/src/daemon/autostart/ensureAutostart.ts +0 -30
  41. package/src/daemon/autostart/getAutostartPath.ts +0 -29
  42. package/src/daemon/autostart/getDaemonInstallArgs.ts +0 -10
  43. package/src/daemon/createLogger.ts +0 -13
  44. package/src/daemon/createShutdownHandler.ts +0 -29
  45. package/src/daemon/jobs/createJobsFileSync.ts +0 -113
  46. package/src/daemon/jobs/deleteJobByName.ts +0 -18
  47. package/src/daemon/jobs/upsertJob.ts +0 -85
  48. package/src/daemon/main.ts +0 -64
  49. package/src/daemon/runner/createRunOutputFds.ts +0 -18
  50. package/src/daemon/runner/getRunStatus.ts +0 -14
  51. package/src/daemon/runner/recordSkippedRun.ts +0 -27
  52. package/src/daemon/runner/recoverRunningRuns.ts +0 -36
  53. package/src/daemon/runner/runJob.ts +0 -94
  54. package/src/daemon/scheduler/createScheduler.ts +0 -45
  55. package/src/daemon/scheduler/createSchedulerState.ts +0 -8
  56. package/src/daemon/scheduler/loadJobs.ts +0 -10
  57. package/src/daemon/scheduler/runJobWithTracking.ts +0 -48
  58. package/src/daemon/scheduler/scheduleJob.ts +0 -32
  59. package/src/daemon/scheduler/unscheduleJob.ts +0 -11
  60. package/src/daemon/scheduler/updateNextRunAt.ts +0 -14
  61. package/src/daemon/server/createApp.ts +0 -76
  62. package/src/daemon/server/createAuthMiddleware.ts +0 -16
  63. package/src/daemon/server/routes/registerExportRoute.ts +0 -21
  64. package/src/daemon/server/routes/registerHealthRoute.ts +0 -16
  65. package/src/daemon/server/routes/registerImportRoute.ts +0 -22
  66. package/src/daemon/server/routes/registerJobRunsRoute.ts +0 -46
  67. package/src/daemon/server/routes/registerJobsDeleteRoute.ts +0 -37
  68. package/src/daemon/server/routes/registerJobsGetRoute.ts +0 -29
  69. package/src/daemon/server/routes/registerJobsKillRoute.ts +0 -45
  70. package/src/daemon/server/routes/registerJobsListRoute.ts +0 -13
  71. package/src/daemon/server/routes/registerJobsPauseRoute.ts +0 -53
  72. package/src/daemon/server/routes/registerJobsResetRoute.ts +0 -54
  73. package/src/daemon/server/routes/registerJobsResumeRoute.ts +0 -53
  74. package/src/daemon/server/routes/registerJobsRunRoute.ts +0 -37
  75. package/src/daemon/server/routes/registerJobsStopRoute.ts +0 -45
  76. package/src/daemon/server/routes/registerJobsUpsertRoute.ts +0 -30
  77. package/src/daemon/server/routes/registerRunGetRoute.ts +0 -25
  78. package/src/daemon/server/routes/registerRunLogsRoute.ts +0 -32
  79. package/src/daemon/server/routes/registerShutdownRoute.ts +0 -9
  80. package/src/daemon/server/startServer.ts +0 -23
  81. package/src/db/getMigrationsDir.ts +0 -5
  82. package/src/db/migrateDatabase.ts +0 -21
  83. package/src/db/openDatabase.ts +0 -12
  84. package/src/db/schema/jobs.ts +0 -26
  85. package/src/db/schema/jobsSchemas.ts +0 -10
  86. package/src/db/schema/runs.ts +0 -23
  87. package/src/db/schema/runsSchemas.ts +0 -10
  88. package/src/db/schema.ts +0 -5
  89. package/src/shared/auth/createToken.ts +0 -5
  90. package/src/shared/events/appendEvent.ts +0 -16
  91. package/src/shared/jobs/createCommandSchema.ts +0 -5
  92. package/src/shared/jobs/createEnvSchema.ts +0 -5
  93. package/src/shared/jobs/createJobInputSchema.ts +0 -31
  94. package/src/shared/jobs/createTomlJobSchema.ts +0 -31
  95. package/src/shared/jobs/formatJobRow.ts +0 -14
  96. package/src/shared/jobs/isOverlapPolicy.ts +0 -5
  97. package/src/shared/jobs/parseCommand.ts +0 -6
  98. package/src/shared/jobs/parseEnv.ts +0 -10
  99. package/src/shared/jobs/parseJobsToml.ts +0 -36
  100. package/src/shared/jobs/readJobsToml.ts +0 -17
  101. package/src/shared/jobs/serializeCommand.ts +0 -6
  102. package/src/shared/jobs/serializeEnv.ts +0 -6
  103. package/src/shared/jobs/serializeJobsToml.ts +0 -45
  104. package/src/shared/jobs/writeJobsToml.ts +0 -12
  105. package/src/shared/paths/ensureDir.ts +0 -6
  106. package/src/shared/paths/getConfigDir.ts +0 -6
  107. package/src/shared/paths/getEventsPath.ts +0 -6
  108. package/src/shared/paths/getJobRunsDir.ts +0 -7
  109. package/src/shared/paths/getJobsTomlPath.ts +0 -6
  110. package/src/shared/paths/getPaths.ts +0 -16
  111. package/src/shared/paths/getRunOutputPaths.ts +0 -10
  112. package/src/shared/paths/getRunsDir.ts +0 -7
  113. package/src/shared/paths/getStateDir.ts +0 -8
  114. package/src/shared/rpc/createRpcClient.ts +0 -20
  115. package/src/shared/runs/formatRunRow.ts +0 -6
  116. package/src/shared/state/daemonStateSchema.ts +0 -13
  117. package/src/shared/state/getDaemonStatePath.ts +0 -6
  118. package/src/shared/state/readDaemonState.ts +0 -14
  119. package/src/shared/state/removeDaemonState.ts +0 -9
  120. package/src/shared/state/writeDaemonState.ts +0 -8
  121. package/src/shared/utils/isRecord.ts +0 -5
  122. package/src/shared/version.ts +0 -5
@@ -1,85 +0,0 @@
1
- import { eq } from "drizzle-orm";
2
- import { ulid } from "ulid";
3
- import type { z } from "zod";
4
- import type openDatabase from "../../db/openDatabase";
5
- import { jobs } from "../../db/schema";
6
- import appendEvent from "../../shared/events/appendEvent";
7
- import createJobInputSchema from "../../shared/jobs/createJobInputSchema";
8
- import formatJobRow from "../../shared/jobs/formatJobRow";
9
- import serializeCommand from "../../shared/jobs/serializeCommand";
10
- import serializeEnv from "../../shared/jobs/serializeEnv";
11
- import ensureAutostart from "../autostart/ensureAutostart";
12
-
13
- type Db = ReturnType<typeof openDatabase>["orm"];
14
- type JobInput = z.infer<ReturnType<typeof createJobInputSchema>>;
15
-
16
- export default function upsertJob(db: Db, payload: unknown, id?: string) {
17
- const input: JobInput = createJobInputSchema().parse(payload);
18
- const existing = db
19
- .select()
20
- .from(jobs)
21
- .where(eq(jobs.name, input.name))
22
- .get();
23
- const now = new Date().toISOString();
24
- const scheduleType = input.schedule ? "cron" : "once";
25
- const jobId = existing?.id ?? id ?? ulid();
26
-
27
- const insertValues = {
28
- id: jobId,
29
- name: input.name,
30
- description: input.description ?? null,
31
- command: serializeCommand(input.command),
32
- cwd: input.cwd ?? null,
33
- env: input.env ? serializeEnv(input.env) : null,
34
- scheduleType,
35
- cron: input.schedule ?? null,
36
- runAt: input.runAt ?? null,
37
- timezone: input.timezone ?? null,
38
- overlapPolicy: input.overlapPolicy ?? "skip",
39
- timeoutMs: input.timeoutMs ?? null,
40
- paused: input.paused ?? false,
41
- createdAt: now,
42
- updatedAt: now,
43
- lastRunAt: existing?.lastRunAt ?? null,
44
- nextRunAt: existing?.nextRunAt ?? null,
45
- };
46
-
47
- const updateValues = {
48
- description: insertValues.description,
49
- command: insertValues.command,
50
- cwd: insertValues.cwd,
51
- env: insertValues.env,
52
- scheduleType: insertValues.scheduleType,
53
- cron: insertValues.cron,
54
- runAt: insertValues.runAt,
55
- timezone: insertValues.timezone,
56
- overlapPolicy: insertValues.overlapPolicy,
57
- timeoutMs: insertValues.timeoutMs,
58
- paused: insertValues.paused,
59
- updatedAt: now,
60
- };
61
-
62
- db.insert(jobs)
63
- .values(insertValues)
64
- .onConflictDoUpdate({
65
- target: jobs.name,
66
- set: updateValues,
67
- })
68
- .run();
69
-
70
- const saved = db.select().from(jobs).where(eq(jobs.name, input.name)).get();
71
- if (!saved) {
72
- throw new Error("job_not_saved");
73
- }
74
-
75
- const job = formatJobRow(saved);
76
- const created = !existing;
77
- appendEvent(created ? "job_created" : "job_updated", {
78
- jobId: job.id,
79
- name: job.name,
80
- });
81
- if (created) {
82
- ensureAutostart();
83
- }
84
- return { job, created };
85
- }
@@ -1,64 +0,0 @@
1
- import migrateDatabase from "../db/migrateDatabase";
2
- import openDatabase from "../db/openDatabase";
3
- import createToken from "../shared/auth/createToken";
4
- import appendEvent from "../shared/events/appendEvent";
5
- import writeDaemonState from "../shared/state/writeDaemonState";
6
- import getVersion from "../shared/version";
7
- import createLogger from "./createLogger";
8
- import createShutdownHandler from "./createShutdownHandler";
9
- import createJobsFileSync from "./jobs/createJobsFileSync";
10
- import recoverRunningRuns from "./runner/recoverRunningRuns";
11
- import createScheduler from "./scheduler/createScheduler";
12
- import createApp from "./server/createApp";
13
- import startServer from "./server/startServer";
14
-
15
- export default function startDaemon() {
16
- const logger = createLogger();
17
- const startedAt = new Date().toISOString();
18
- const token = createToken();
19
- const pid = process.pid;
20
- const { orm } = openDatabase();
21
- const migrationResult = migrateDatabase(orm);
22
- if (!migrationResult.migrated) {
23
- logger.warn({
24
- event: "migrations_skipped",
25
- reason: migrationResult.reason,
26
- });
27
- }
28
- recoverRunningRuns(orm);
29
- const scheduler = createScheduler(orm);
30
- const jobsFileSync = createJobsFileSync(orm, scheduler, logger);
31
- jobsFileSync.init();
32
- scheduler.start();
33
- let shutdown = () => {};
34
- const app = createApp(
35
- { token, startedAt, pid },
36
- orm,
37
- scheduler,
38
- jobsFileSync,
39
- () => shutdown(),
40
- );
41
- const server = startServer(app);
42
- const port = server.port;
43
- if (typeof port !== "number") {
44
- throw new Error("daemon_port_unavailable");
45
- }
46
-
47
- writeDaemonState({
48
- port,
49
- token,
50
- pid,
51
- startedAt,
52
- version: getVersion(),
53
- });
54
-
55
- appendEvent("daemon_started", { pid });
56
-
57
- logger.info("daemon_started");
58
-
59
- shutdown = createShutdownHandler(server, logger, scheduler, jobsFileSync);
60
- process.on("SIGINT", shutdown);
61
- process.on("SIGTERM", shutdown);
62
-
63
- return { server, shutdown: () => shutdown() };
64
- }
@@ -1,18 +0,0 @@
1
- import { closeSync, openSync } from "node:fs";
2
-
3
- export default function createRunOutputFds(
4
- stdoutPath: string,
5
- stderrPath: string,
6
- ) {
7
- const stdoutFd = openSync(stdoutPath, "a");
8
- const stderrFd = openSync(stderrPath, "a");
9
-
10
- return {
11
- stdoutFd,
12
- stderrFd,
13
- close() {
14
- closeSync(stdoutFd);
15
- closeSync(stderrFd);
16
- },
17
- };
18
- }
@@ -1,14 +0,0 @@
1
- export default function getRunStatus(
2
- exitCode: number | null,
3
- signal: string | null,
4
- ) {
5
- if (signal) {
6
- return "killed";
7
- }
8
-
9
- if (exitCode === 0) {
10
- return "success";
11
- }
12
-
13
- return "failed";
14
- }
@@ -1,27 +0,0 @@
1
- import { ulid } from "ulid";
2
- import type openDatabase from "../../db/openDatabase";
3
- import { runs } from "../../db/schema";
4
- import appendEvent from "../../shared/events/appendEvent";
5
- import type formatJobRow from "../../shared/jobs/formatJobRow";
6
-
7
- type Db = ReturnType<typeof openDatabase>["orm"];
8
- type Job = ReturnType<typeof formatJobRow>;
9
-
10
- export default function recordSkippedRun(db: Db, job: Job) {
11
- const now = new Date().toISOString();
12
- const runId = ulid();
13
-
14
- db.insert(runs)
15
- .values({
16
- id: runId,
17
- jobId: job.id,
18
- status: "skipped",
19
- startedAt: now,
20
- endedAt: now,
21
- })
22
- .run();
23
-
24
- appendEvent("run_skipped", { jobId: job.id, runId });
25
-
26
- return runId;
27
- }
@@ -1,36 +0,0 @@
1
- import { eq } from "drizzle-orm";
2
- import type openDatabase from "../../db/openDatabase";
3
- import { runs } from "../../db/schema";
4
- import appendEvent from "../../shared/events/appendEvent";
5
-
6
- type Db = ReturnType<typeof openDatabase>["orm"];
7
-
8
- export default function recoverRunningRuns(db: Db) {
9
- const running = db
10
- .select()
11
- .from(runs)
12
- .where(eq(runs.status, "running"))
13
- .all();
14
- const now = new Date().toISOString();
15
-
16
- for (const run of running) {
17
- if (!run.pid) {
18
- db.update(runs)
19
- .set({ status: "lost", endedAt: now })
20
- .where(eq(runs.id, run.id))
21
- .run();
22
- appendEvent("run_lost", { runId: run.id, jobId: run.jobId });
23
- continue;
24
- }
25
-
26
- try {
27
- process.kill(run.pid, 0);
28
- } catch {
29
- db.update(runs)
30
- .set({ status: "lost", endedAt: now })
31
- .where(eq(runs.id, run.id))
32
- .run();
33
- appendEvent("run_lost", { runId: run.id, jobId: run.jobId });
34
- }
35
- }
36
- }
@@ -1,94 +0,0 @@
1
- import { eq } from "drizzle-orm";
2
- import { ulid } from "ulid";
3
- import type openDatabase from "../../db/openDatabase";
4
- import { jobs, runs } from "../../db/schema";
5
- import appendEvent from "../../shared/events/appendEvent";
6
- import type formatJobRow from "../../shared/jobs/formatJobRow";
7
- import getRunOutputPaths from "../../shared/paths/getRunOutputPaths";
8
- import createRunOutputFds from "./createRunOutputFds";
9
- import getRunStatus from "./getRunStatus";
10
-
11
- type Db = ReturnType<typeof openDatabase>["orm"];
12
- type Job = ReturnType<typeof formatJobRow>;
13
-
14
- export default function runJob(db: Db, job: Job) {
15
- const runId = ulid();
16
- const startedAt = new Date().toISOString();
17
- const { stdoutPath, stderrPath } = getRunOutputPaths(job.id, runId);
18
- const outputs = createRunOutputFds(stdoutPath, stderrPath);
19
-
20
- db.insert(runs)
21
- .values({
22
- id: runId,
23
- jobId: job.id,
24
- status: "running",
25
- startedAt,
26
- stdoutPath,
27
- stderrPath,
28
- })
29
- .run();
30
-
31
- appendEvent("run_started", { jobId: job.id, runId });
32
-
33
- db.update(jobs)
34
- .set({ lastRunAt: startedAt, updatedAt: startedAt })
35
- .where(eq(jobs.id, job.id))
36
- .run();
37
-
38
- const env = job.env ? { ...process.env, ...job.env } : process.env;
39
-
40
- try {
41
- const proc = Bun.spawn(job.command, {
42
- cwd: job.cwd ?? undefined,
43
- env,
44
- stdout: outputs.stdoutFd,
45
- stderr: outputs.stderrFd,
46
- timeout: job.timeoutMs ?? undefined,
47
- killSignal: "SIGKILL",
48
- onExit: (_proc, exitCode, signalCode, error) => {
49
- outputs.close();
50
-
51
- const signal = signalCode ? String(signalCode) : null;
52
- const status = getRunStatus(exitCode ?? null, signal);
53
-
54
- const endedAt = new Date().toISOString();
55
-
56
- db.update(runs)
57
- .set({
58
- status,
59
- exitCode: exitCode ?? null,
60
- signal,
61
- endedAt,
62
- errorMessage: error ? error.message : null,
63
- })
64
- .where(eq(runs.id, runId))
65
- .run();
66
-
67
- const eventType =
68
- status === "killed"
69
- ? "run_killed"
70
- : status === "failed"
71
- ? "run_failed"
72
- : "run_finished";
73
- appendEvent(eventType, { jobId: job.id, runId, status });
74
- },
75
- });
76
-
77
- db.update(runs).set({ pid: proc.pid }).where(eq(runs.id, runId)).run();
78
-
79
- return { runId, exited: proc.exited };
80
- } catch (error) {
81
- outputs.close();
82
-
83
- const endedAt = new Date().toISOString();
84
- const message = error instanceof Error ? error.message : "spawn_failed";
85
- db.update(runs)
86
- .set({ status: "failed", endedAt, errorMessage: message })
87
- .where(eq(runs.id, runId))
88
- .run();
89
-
90
- appendEvent("run_failed", { jobId: job.id, runId, status: "failed" });
91
-
92
- return { runId, exited: Promise.resolve(1) };
93
- }
94
- }
@@ -1,45 +0,0 @@
1
- import type openDatabase from "../../db/openDatabase";
2
- import type formatJobRow from "../../shared/jobs/formatJobRow";
3
- import createSchedulerState from "./createSchedulerState";
4
- import loadJobs from "./loadJobs";
5
- import runJobWithTracking from "./runJobWithTracking";
6
- import scheduleJob from "./scheduleJob";
7
- import unscheduleJob from "./unscheduleJob";
8
-
9
- type Db = ReturnType<typeof openDatabase>["orm"];
10
- type Job = ReturnType<typeof formatJobRow>;
11
-
12
- export default function createScheduler(db: Db) {
13
- const state = createSchedulerState();
14
- const stop = () => {
15
- for (const jobId of state.scheduled.keys()) {
16
- unscheduleJob(state, jobId);
17
- }
18
- state.running.clear();
19
- };
20
-
21
- return {
22
- start() {
23
- stop();
24
- const jobs = loadJobs(db);
25
- for (const job of jobs) {
26
- scheduleJob(state, db, job, (current) => {
27
- runJobWithTracking(state, db, current);
28
- });
29
- }
30
- },
31
- stop,
32
- upsert(job: Job) {
33
- unscheduleJob(state, job.id);
34
- scheduleJob(state, db, job, (current) => {
35
- runJobWithTracking(state, db, current);
36
- });
37
- },
38
- remove(jobId: string) {
39
- unscheduleJob(state, jobId);
40
- },
41
- runNow(job: Job) {
42
- return runJobWithTracking(state, db, job);
43
- },
44
- };
45
- }
@@ -1,8 +0,0 @@
1
- import type { Cron } from "croner";
2
-
3
- export default function createSchedulerState() {
4
- return {
5
- scheduled: new Map<string, Cron>(),
6
- running: new Set<string>(),
7
- };
8
- }
@@ -1,10 +0,0 @@
1
- import type openDatabase from "../../db/openDatabase";
2
- import { jobs } from "../../db/schema";
3
- import formatJobRow from "../../shared/jobs/formatJobRow";
4
-
5
- type Db = ReturnType<typeof openDatabase>["orm"];
6
-
7
- export default function loadJobs(db: Db) {
8
- const rows = db.select().from(jobs).all();
9
- return rows.map(formatJobRow);
10
- }
@@ -1,48 +0,0 @@
1
- import type openDatabase from "../../db/openDatabase";
2
- import type formatJobRow from "../../shared/jobs/formatJobRow";
3
- import recordSkippedRun from "../runner/recordSkippedRun";
4
- import runJob from "../runner/runJob";
5
- import type createSchedulerState from "./createSchedulerState";
6
- import updateNextRunAt from "./updateNextRunAt";
7
-
8
- type Db = ReturnType<typeof openDatabase>["orm"];
9
- type Job = ReturnType<typeof formatJobRow>;
10
- type SchedulerState = ReturnType<typeof createSchedulerState>;
11
-
12
- export default function runJobWithTracking(
13
- state: SchedulerState,
14
- db: Db,
15
- job: Job,
16
- ) {
17
- if (state.running.has(job.id) && job.overlapPolicy === "skip") {
18
- const runId = recordSkippedRun(db, job);
19
- const cron = state.scheduled.get(job.id);
20
- if (cron) {
21
- updateNextRunAt(db, job.id, cron.nextRun());
22
- }
23
- return runId;
24
- }
25
-
26
- state.running.add(job.id);
27
- const result = runJob(db, job);
28
-
29
- result.exited
30
- .then(() => {
31
- state.running.delete(job.id);
32
-
33
- const cron = state.scheduled.get(job.id);
34
- if (cron) {
35
- const nextRun = cron.nextRun();
36
- updateNextRunAt(db, job.id, nextRun);
37
- if (!nextRun) {
38
- cron.stop();
39
- state.scheduled.delete(job.id);
40
- }
41
- }
42
- })
43
- .catch(() => {
44
- state.running.delete(job.id);
45
- });
46
-
47
- return result.runId;
48
- }
@@ -1,32 +0,0 @@
1
- import { Cron } from "croner";
2
- import type openDatabase from "../../db/openDatabase";
3
- import type formatJobRow from "../../shared/jobs/formatJobRow";
4
- import type createSchedulerState from "./createSchedulerState";
5
- import updateNextRunAt from "./updateNextRunAt";
6
-
7
- type Db = ReturnType<typeof openDatabase>["orm"];
8
- type Job = ReturnType<typeof formatJobRow>;
9
- type SchedulerState = ReturnType<typeof createSchedulerState>;
10
-
11
- export default function scheduleJob(
12
- state: SchedulerState,
13
- db: Db,
14
- job: Job,
15
- runScheduled: (job: Job) => void,
16
- ) {
17
- if (job.paused) {
18
- return;
19
- }
20
-
21
- const pattern = job.scheduleType === "cron" ? job.cron : job.runAt;
22
- if (!pattern) {
23
- return;
24
- }
25
-
26
- const cron = new Cron(pattern, { timezone: job.timezone ?? undefined }, () =>
27
- runScheduled(job),
28
- );
29
-
30
- state.scheduled.set(job.id, cron);
31
- updateNextRunAt(db, job.id, cron.nextRun());
32
- }
@@ -1,11 +0,0 @@
1
- import type createSchedulerState from "./createSchedulerState";
2
-
3
- type SchedulerState = ReturnType<typeof createSchedulerState>;
4
-
5
- export default function unscheduleJob(state: SchedulerState, jobId: string) {
6
- const cron = state.scheduled.get(jobId);
7
- if (cron) {
8
- cron.stop();
9
- }
10
- state.scheduled.delete(jobId);
11
- }
@@ -1,14 +0,0 @@
1
- import { eq } from "drizzle-orm";
2
- import type openDatabase from "../../db/openDatabase";
3
- import { jobs } from "../../db/schema";
4
-
5
- type Db = ReturnType<typeof openDatabase>["orm"];
6
-
7
- export default function updateNextRunAt(
8
- db: Db,
9
- jobId: string,
10
- nextRun: Date | null,
11
- ) {
12
- const nextRunAt = nextRun ? nextRun.toISOString() : null;
13
- db.update(jobs).set({ nextRunAt }).where(eq(jobs.id, jobId)).run();
14
- }
@@ -1,76 +0,0 @@
1
- import { Hono } from "hono";
2
- import { z } from "zod";
3
- import type openDatabase from "../../db/openDatabase";
4
- import getVersion from "../../shared/version";
5
- import type createJobsFileSync from "../jobs/createJobsFileSync";
6
- import type createScheduler from "../scheduler/createScheduler";
7
- import createAuthMiddleware from "./createAuthMiddleware";
8
- import registerExportRoute from "./routes/registerExportRoute";
9
- import registerHealthRoute from "./routes/registerHealthRoute";
10
- import registerImportRoute from "./routes/registerImportRoute";
11
- import registerJobRunsRoute from "./routes/registerJobRunsRoute";
12
- import registerJobsDeleteRoute from "./routes/registerJobsDeleteRoute";
13
- import registerJobsGetRoute from "./routes/registerJobsGetRoute";
14
- import registerJobsKillRoute from "./routes/registerJobsKillRoute";
15
- import registerJobsListRoute from "./routes/registerJobsListRoute";
16
- import registerJobsPauseRoute from "./routes/registerJobsPauseRoute";
17
- import registerJobsResetRoute from "./routes/registerJobsResetRoute";
18
- import registerJobsResumeRoute from "./routes/registerJobsResumeRoute";
19
- import registerJobsRunRoute from "./routes/registerJobsRunRoute";
20
- import registerJobsStopRoute from "./routes/registerJobsStopRoute";
21
- import registerJobsUpsertRoute from "./routes/registerJobsUpsertRoute";
22
- import registerRunGetRoute from "./routes/registerRunGetRoute";
23
- import registerRunLogsRoute from "./routes/registerRunLogsRoute";
24
- import registerShutdownRoute from "./routes/registerShutdownRoute";
25
-
26
- const createAppOptionsSchema = z.object({
27
- token: z.string().min(1),
28
- startedAt: z.string().datetime(),
29
- pid: z.number().int().positive(),
30
- });
31
-
32
- type CreateAppOptions = z.infer<typeof createAppOptionsSchema>;
33
- type Db = ReturnType<typeof openDatabase>["orm"];
34
- type Scheduler = ReturnType<typeof createScheduler>;
35
- type JobsFileSync = ReturnType<typeof createJobsFileSync>;
36
-
37
- export default function createApp(
38
- options: CreateAppOptions,
39
- db: Db,
40
- scheduler: Scheduler,
41
- jobsFileSync: JobsFileSync,
42
- shutdown: () => void,
43
- ) {
44
- const parsed = createAppOptionsSchema.parse(options);
45
- const app = new Hono()
46
- .use("*", createAuthMiddleware(parsed.token))
47
- .route(
48
- "/",
49
- registerHealthRoute({
50
- status: "ok",
51
- startedAt: parsed.startedAt,
52
- pid: parsed.pid,
53
- version: getVersion(),
54
- }),
55
- )
56
- .route("/", registerShutdownRoute(shutdown))
57
- .route("/", registerJobsGetRoute(db))
58
- .route("/", registerJobsListRoute(db))
59
- .route("/", registerJobRunsRoute(db))
60
- .route("/", registerJobsUpsertRoute(db, scheduler, jobsFileSync))
61
- .route("/", registerJobsRunRoute(db, scheduler))
62
- .route("/", registerJobsPauseRoute(db, scheduler, jobsFileSync))
63
- .route("/", registerJobsResumeRoute(db, scheduler, jobsFileSync))
64
- .route("/", registerJobsResetRoute(db, scheduler, jobsFileSync))
65
- .route("/", registerJobsStopRoute(db))
66
- .route("/", registerJobsKillRoute(db))
67
- .route("/", registerJobsDeleteRoute(db, scheduler, jobsFileSync))
68
- .route("/", registerExportRoute(db, jobsFileSync))
69
- .route("/", registerImportRoute(jobsFileSync))
70
- .route("/", registerRunGetRoute(db))
71
- .route("/", registerRunLogsRoute(db));
72
-
73
- return app;
74
- }
75
-
76
- export type AppType = ReturnType<typeof createApp>;
@@ -1,16 +0,0 @@
1
- import type { MiddlewareHandler } from "hono";
2
-
3
- export default function createAuthMiddleware(token: string): MiddlewareHandler {
4
- return async (c, next) => {
5
- const header = c.req.header("authorization");
6
- const value = header?.startsWith("Bearer ")
7
- ? header.slice("Bearer ".length)
8
- : null;
9
-
10
- if (!value || value !== token) {
11
- return c.json({ error: "unauthorized" }, 401);
12
- }
13
-
14
- await next();
15
- };
16
- }
@@ -1,21 +0,0 @@
1
- import { Hono } from "hono";
2
- import type openDatabase from "../../../db/openDatabase";
3
- import { jobs } from "../../../db/schema";
4
- import formatJobRow from "../../../shared/jobs/formatJobRow";
5
- import serializeJobsToml from "../../../shared/jobs/serializeJobsToml";
6
- import type createJobsFileSync from "../../jobs/createJobsFileSync";
7
-
8
- type Db = ReturnType<typeof openDatabase>["orm"];
9
- type JobsFileSync = ReturnType<typeof createJobsFileSync>;
10
-
11
- export default function registerExportRoute(
12
- db: Db,
13
- jobsFileSync: JobsFileSync,
14
- ) {
15
- return new Hono().post("/export", (c) => {
16
- const rows = db.select().from(jobs).all().map(formatJobRow);
17
- jobsFileSync.writeFromDb();
18
- const toml = serializeJobsToml(rows);
19
- return c.json({ toml });
20
- });
21
- }
@@ -1,16 +0,0 @@
1
- import { Hono } from "hono";
2
- import { z } from "zod";
3
-
4
- const healthPayloadSchema = z.object({
5
- status: z.literal("ok"),
6
- startedAt: z.string(),
7
- pid: z.number().int(),
8
- version: z.string(),
9
- });
10
-
11
- type HealthPayload = z.infer<typeof healthPayloadSchema>;
12
-
13
- export default function registerHealthRoute(payload: HealthPayload) {
14
- const parsed = healthPayloadSchema.parse(payload);
15
- return new Hono().get("/health", (c) => c.json(parsed));
16
- }
@@ -1,22 +0,0 @@
1
- import { zValidator } from "@hono/zod-validator";
2
- import { Hono } from "hono";
3
- import { z } from "zod";
4
- import type createJobsFileSync from "../../jobs/createJobsFileSync";
5
-
6
- type JobsFileSync = ReturnType<typeof createJobsFileSync>;
7
-
8
- const payloadSchema = z.object({
9
- toml: z.string().min(1),
10
- });
11
-
12
- export default function registerImportRoute(jobsFileSync: JobsFileSync) {
13
- return new Hono().post("/import", zValidator("json", payloadSchema), (c) => {
14
- const payload = c.req.valid("json");
15
- const result = jobsFileSync.applyFromText(payload.toml);
16
- if (!result.ok) {
17
- return c.json({ error: "import_failed", message: result.error }, 400);
18
- }
19
-
20
- return c.json({ ok: true });
21
- });
22
- }