refactor: add cron job core package (#704)
* refactor: add cron job core package * docs: add comments to cron validation types * chore(deps): move node-cron dependencies from tasks app to cron-jobs-core package * fix: runOnInit is not running on start and rather on job creation * fix: format issues * fix: build fails when using top level await in cjs * chore: update turbo gen package json typescript version to 5.5.2 * fix: format issue * fix: deepsource issues * chore: update turbo gen package json eslint version to 9.5.0 * chore: fix frozen lockfile and format
This commit is contained in:
@@ -3,9 +3,9 @@ import { smartHomeEntityStateJob } from "~/jobs/integrations/home-assistant";
|
||||
import { analyticsJob } from "./jobs/analytics";
|
||||
import { pingJob } from "./jobs/ping";
|
||||
import { queuesJob } from "./jobs/queue";
|
||||
import { createJobGroup } from "./lib/cron-job/group";
|
||||
import { createCronJobGroup } from "./lib/jobs";
|
||||
|
||||
export const jobs = createJobGroup({
|
||||
export const jobs = createCronJobGroup({
|
||||
// Add your jobs here:
|
||||
analytics: analyticsJob,
|
||||
iconsUpdater: iconsUpdaterJob,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { sendServerAnalyticsAsync } from "@homarr/analytics";
|
||||
import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { serverSettings } from "@homarr/db/schema/sqlite";
|
||||
|
||||
import { EVERY_WEEK } from "~/lib/cron-job/constants";
|
||||
import { createCronJob } from "~/lib/cron-job/creator";
|
||||
import { createCronJob } from "~/lib/jobs";
|
||||
import type { defaultServerSettings } from "../../../../packages/server-settings";
|
||||
|
||||
export const analyticsJob = createCronJob(EVERY_WEEK, {
|
||||
export const analyticsJob = createCronJob("analytics", EVERY_WEEK, {
|
||||
runOnStart: true,
|
||||
}).withCallback(async () => {
|
||||
const analyticSetting = await db.query.serverSettings.findFirst({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Stopwatch } from "@homarr/common";
|
||||
import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions";
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { db, inArray } from "@homarr/db";
|
||||
import { createId } from "@homarr/db/client";
|
||||
@@ -6,10 +7,9 @@ import { iconRepositories, icons } from "@homarr/db/schema/sqlite";
|
||||
import { fetchIconsAsync } from "@homarr/icons";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { EVERY_WEEK } from "~/lib/cron-job/constants";
|
||||
import { createCronJob } from "~/lib/cron-job/creator";
|
||||
import { createCronJob } from "~/lib/jobs";
|
||||
|
||||
export const iconsUpdaterJob = createCronJob(EVERY_WEEK, {
|
||||
export const iconsUpdaterJob = createCronJob("iconsUpdater", EVERY_WEEK, {
|
||||
runOnStart: true,
|
||||
}).withCallback(async () => {
|
||||
logger.info("Updating icon repository cache...");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { decryptSecret } from "@homarr/common";
|
||||
import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { items } from "@homarr/db/schema/sqlite";
|
||||
import { HomeAssistantIntegration } from "@homarr/integrations";
|
||||
@@ -8,10 +9,9 @@ import { logger } from "@homarr/log";
|
||||
import { homeAssistantEntityState } from "@homarr/redis";
|
||||
import type { WidgetComponentProps } from "@homarr/widgets";
|
||||
|
||||
import { EVERY_MINUTE } from "~/lib/cron-job/constants";
|
||||
import { createCronJob } from "~/lib/cron-job/creator";
|
||||
import { createCronJob } from "~/lib/jobs";
|
||||
|
||||
export const smartHomeEntityStateJob = createCronJob(EVERY_MINUTE).withCallback(async () => {
|
||||
export const smartHomeEntityStateJob = createCronJob("smartHomeEntityState", EVERY_MINUTE).withCallback(async () => {
|
||||
const itemsForIntegration = await db.query.items.findMany({
|
||||
where: eq(items.kind, "smartHome-entityState"),
|
||||
with: {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions";
|
||||
import { logger } from "@homarr/log";
|
||||
import { sendPingRequestAsync } from "@homarr/ping";
|
||||
import { pingChannel, pingUrlChannel } from "@homarr/redis";
|
||||
|
||||
import { EVERY_MINUTE } from "~/lib/cron-job/constants";
|
||||
import { createCronJob } from "~/lib/cron-job/creator";
|
||||
import { createCronJob } from "~/lib/jobs";
|
||||
|
||||
export const pingJob = createCronJob(EVERY_MINUTE).withCallback(async () => {
|
||||
export const pingJob = createCronJob("ping", EVERY_MINUTE).withCallback(async () => {
|
||||
const urls = await pingUrlChannel.getAllAsync();
|
||||
|
||||
for (const url of new Set(urls)) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { EVERY_MINUTE } from "../lib/cron-job/constants";
|
||||
import { createCronJob } from "../lib/cron-job/creator";
|
||||
import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions";
|
||||
|
||||
import { createCronJob } from "~/lib/jobs";
|
||||
import { queueWorkerAsync } from "../lib/queue/worker";
|
||||
|
||||
// This job processes queues, it runs every minute.
|
||||
export const queuesJob = createCronJob(EVERY_MINUTE).withCallback(async () => {
|
||||
export const queuesJob = createCronJob("queues", EVERY_MINUTE).withCallback(async () => {
|
||||
await queueWorkerAsync();
|
||||
});
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export const EVERY_5_SECONDS = "*/5 * * * * *";
|
||||
export const EVERY_MINUTE = "* * * * *";
|
||||
export const EVERY_5_MINUTES = "*/5 * * * *";
|
||||
export const EVERY_10_MINUTES = "*/10 * * * *";
|
||||
export const EVERY_HOUR = "0 * * * *";
|
||||
export const EVERY_DAY = "0 0 * * */1";
|
||||
export const EVERY_WEEK = "0 0 * * 1";
|
||||
@@ -1,37 +0,0 @@
|
||||
import cron from "node-cron";
|
||||
|
||||
import type { MaybePromise } from "@homarr/common/types";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
interface CreateCronJobOptions {
|
||||
runOnStart?: boolean;
|
||||
}
|
||||
|
||||
export const createCronJob = (cronExpression: string, options: CreateCronJobOptions = { runOnStart: false }) => {
|
||||
return {
|
||||
withCallback: (callback: () => MaybePromise<void>) => {
|
||||
const catchingCallbackAsync = async () => {
|
||||
try {
|
||||
await callback();
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`apps/tasks/src/lib/cron-job/creator.ts: The callback of a cron job failed, expression ${cronExpression}, with error:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (options.runOnStart) {
|
||||
void catchingCallbackAsync();
|
||||
}
|
||||
|
||||
const task = cron.schedule(cronExpression, () => void catchingCallbackAsync(), {
|
||||
scheduled: false,
|
||||
});
|
||||
return {
|
||||
_expression: cronExpression,
|
||||
_task: task,
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
import { objectEntries } from "@homarr/common";
|
||||
|
||||
import type { createCronJob } from "./creator";
|
||||
import { jobRegistry } from "./registry";
|
||||
|
||||
type Jobs = Record<string, ReturnType<ReturnType<typeof createCronJob>["withCallback"]>>;
|
||||
|
||||
export const createJobGroup = <TJobs extends Jobs>(jobs: TJobs) => {
|
||||
for (const [name, job] of objectEntries(jobs)) {
|
||||
if (typeof name !== "string") continue;
|
||||
jobRegistry.set(name, {
|
||||
name,
|
||||
expression: job._expression,
|
||||
active: false,
|
||||
task: job._task,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
start: (name: keyof TJobs) => {
|
||||
const job = jobRegistry.get(name as string);
|
||||
if (!job) return;
|
||||
job.active = true;
|
||||
job.task.start();
|
||||
},
|
||||
startAll: () => {
|
||||
for (const job of jobRegistry.values()) {
|
||||
job.active = true;
|
||||
job.task.start();
|
||||
}
|
||||
},
|
||||
stop: (name: keyof TJobs) => {
|
||||
const job = jobRegistry.get(name as string);
|
||||
if (!job) return;
|
||||
job.active = false;
|
||||
job.task.stop();
|
||||
},
|
||||
stopAll: () => {
|
||||
for (const job of jobRegistry.values()) {
|
||||
job.active = false;
|
||||
job.task.stop();
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import type cron from "node-cron";
|
||||
|
||||
interface Job {
|
||||
name: string;
|
||||
expression: string;
|
||||
active: boolean;
|
||||
task: cron.ScheduledTask;
|
||||
}
|
||||
export const jobRegistry = new Map<string, Job>();
|
||||
21
apps/tasks/src/lib/jobs.ts
Normal file
21
apps/tasks/src/lib/jobs.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { createCronJobFunctions } from "@homarr/cron-jobs-core";
|
||||
import type { Logger } from "@homarr/cron-jobs-core/logger";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
class WinstonCronJobLogger implements Logger {
|
||||
logDebug(message: string) {
|
||||
logger.debug(message);
|
||||
}
|
||||
|
||||
logInfo(message: string) {
|
||||
logger.info(message);
|
||||
}
|
||||
|
||||
logError(error: unknown) {
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const { createCronJob, createCronJobGroup } = createCronJobFunctions({
|
||||
logger: new WinstonCronJobLogger(),
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import "./undici-log-agent-override";
|
||||
import { jobs } from "./jobs";
|
||||
import { seedServerSettingsAsync } from "./seed-server-settings";
|
||||
|
||||
jobs.startAll();
|
||||
|
||||
void seedServerSettingsAsync();
|
||||
void (async () => {
|
||||
await jobs.startAllAsync();
|
||||
await seedServerSettingsAsync();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user