All files job.ts

31.88% Statements 22/69
100% Branches 2/2
28.57% Functions 2/7
31.88% Lines 22/69

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 1011x           1x 1x 1x             1x 2x 2x 2x 2x 2x 2x   2x   1x     2x 2x 2x 2x 2x 2x               1x                   1x                       1x                                                                          
import path from "node:path";
import type SQLite from "better-sqlite3";
import {
  type Queue,
  defineQueue as definePlainjobsQueue,
  defineWorker,
} from "plainjobs";
import { getLogger } from "./log";
import { getManifest, getManifestOrThrow } from "./manifest";
 
export type Job<T> = {
  name: string;
  run: ({ data }: { data: T }) => Promise<void>;
};
 
export function isJob<T>(job: unknown): job is Job<T> {
  return (
    typeof job === "object" &&
    job !== null &&
    "run" in job &&
    "name" in job &&
    typeof job.run === "function"
  );
}
 
export function defineJob<T>(opts: {
  name: string;
  run: ({ data }: { data: T }) => Promise<void>;
}): Job<T> {
  return {
    name: path.parse(opts.name).name,
    run: opts.run,
  };
}
 
export type Schedule = {
  name: string;
  cron: string;
  run: () => Promise<void>;
};
 
export function isSchedule(schedule: unknown): schedule is Schedule {
  return (
    typeof schedule === "object" &&
    schedule !== null &&
    "run" in schedule &&
    "name" in schedule &&
    typeof schedule.run === "function"
  );
}
 
export function defineSchedule(opts: {
  name: string;
  cron: string;
  run: () => Promise<void>;
}): Schedule {
  return {
    name: path.parse(opts.name).name,
    cron: opts.cron,
    run: opts.run,
  };
}
 
export function defineQueue(opts: {
  connection: SQLite.Database;
}): Queue {
  const logger = getLogger("queue");
  return definePlainjobsQueue({ connection: opts.connection, logger });
}
 
export async function work(
  queue: Queue,
  jobs: Record<string, Job<unknown>>,
  schedules: Record<string, Schedule>,
) {
  const log = getLogger("work");
  if (!Object.values(jobs).length) log.warn("no jobs to run");
  if (!Object.values(schedules).length) log.warn("no schedules to run");
  for (const schedule of Object.values(schedules)) {
    log.info(`scheduling ${schedule.name} to run at ${schedule.cron}`);
    queue.schedule(schedule.name, { cron: schedule.cron });
  }
  log.info(`starting worker for jobs: ${Object.keys(jobs).join(", ")}`);
  log.info(
    `starting worker for schedules: ${Object.keys(schedules).join(", ")}`,
  );
  const workables = [...Object.values(jobs), ...Object.values(schedules)];
  const workers = workables.map((w) =>
    defineWorker(w.name, w.run, { queue, logger: log }),
  );
  await Promise.all(workers.map((worker) => worker.start()));
}
 
export async function perform<T>(job: Job<T>, data?: T) {
  const log = getLogger("perform");
  log.info(`performing job ${job.name}`);
  const { queue } = await getManifestOrThrow(["queue"]);
  queue.add(job.name, { data });
  log.info(`job ${job.name} queued`);
}