Files
plp/apps/worker/src/main.ts
T
2026-09-15 13:56:56 +08:00

110 lines
2.8 KiB
TypeScript

import { pathToFileURL } from "node:url";
import { PrismaClient } from "@prisma/client";
import { ModerationWorker } from "./moderation-worker.js";
import { LeaseReaper } from "./lease-reaper.processor.js";
type SignalSource = {
once(signal: "SIGTERM" | "SIGINT", listener: () => void): unknown;
removeListener(signal: "SIGTERM" | "SIGINT", listener: () => void): unknown;
};
type RunWorkerOptions = {
worker: { runOnce(): Promise<boolean> };
connect: () => Promise<unknown>;
disconnect: () => Promise<unknown>;
once: boolean;
sleep?: () => Promise<unknown>;
signals?: SignalSource;
};
type Worker = { runOnce(): Promise<boolean> };
export function createCombinedWorker(
reaper: Worker,
moderation: Worker,
): Worker {
return {
async runOnce() {
const reaped = await reaper.runOnce();
const moderated = await moderation.runOnce();
return reaped || moderated;
},
};
}
export async function runWorker(options: RunWorkerOptions) {
const signals = options.signals ?? process;
const sleep =
options.sleep ??
(() => new Promise((resolve) => setTimeout(resolve, 1000)));
let stopping = false;
const stop = () => {
stopping = true;
};
if (!options.once) {
signals.once("SIGTERM", stop);
signals.once("SIGINT", stop);
}
try {
await options.connect();
do {
const handled = await options.worker.runOnce();
if (options.once || stopping) break;
if (!handled) await sleep();
} while (!stopping);
} finally {
if (!options.once) {
signals.removeListener("SIGTERM", stop);
signals.removeListener("SIGINT", stop);
}
await options.disconnect();
}
}
export async function main() {
const prisma = new PrismaClient();
const moderation = new ModerationWorker(prisma);
const reaper = new LeaseReaper(prisma);
const worker = createCombinedWorker(reaper, moderation);
await runWorker({
worker,
connect: () => prisma.$connect(),
disconnect: () => prisma.$disconnect(),
once: process.argv.includes("--once"),
});
}
export function logTopLevelError(
error: unknown,
logger: Pick<Console, "error"> = console,
) {
logger.error(
JSON.stringify({
scope: "moderation-worker",
errorClass:
error instanceof Error && error.name ? error.name : "UnknownError",
}),
);
}
type TopLevelErrorOptions = {
logger?: Pick<Console, "error">;
processState?: { exitCode?: number };
};
export function handleTopLevelError(
error: unknown,
options: TopLevelErrorOptions = {},
) {
logTopLevelError(error, options.logger);
(options.processState ?? process).exitCode = 1;
}
const isEntrypoint =
process.argv[1] !== undefined &&
import.meta.url === pathToFileURL(process.argv[1]).href;
if (isEntrypoint) {
main().catch(handleTopLevelError);
}