startBackground
uses node:url
Background work for an app: a worker for the durable job queue (functions loaded by hash, so a job runs the
code that enqueued it), and the recurring jobs (every(...) values) found in the app's namespace.
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { isSchedule } from "#codebase/tcmts/cli/pages"; import { codebaseRoles } from "#codebase/tcmts/cli/roles"; import { dim, red } from "#codebase/tcmts/cli/style"; import { dataDir, serverApiHost } from "#codebase/tcmts/cli/web"; import type { Fn } from "#codebase/tcmts/cli/web"; import { exportId, shortHash } from "#codebase/tcmts/core/hash"; import { materialize } from "#codebase/tcmts/loader"; import { pathToFileURL } from "node:url"; export async function startBackground(cb: Codebase, appName: string) { const api = await serverApiHost(cb)(); const data = dataDir(cb); // `<hash>` is a function; `<hash>#fn` is the function inside the recurring job (schedule) with that hash const load = async (ref: string): Promise<Fn> => { const [hash, part] = ref.split("#") as [string, string | undefined]; const rec = cb.store.getDef(hash); if (!rec) throw new Error(`no definition ${shortHash(hash)} in this codebase`); materialize([hash], cb.source(), cb.lay); const v = ( (await import(pathToFileURL(cb.modulePath(rec.componentHash)).href)) as Record< string, unknown > )[exportId(hash)]; const fn = part === "fn" ? (v as { fn?: unknown } | undefined)?.fn : v; if (typeof fn !== "function") throw new Error(`${cb.displayRef(`#${hash}`)} isn't a function`); return fn as Fn; }; const stopWorker = api.startWorker({ dataDir: data, load, log: (line) => console.log(` ${dim("→")} ${line}`), }); const ns = appName.includes(".") ? appName.slice(0, appName.lastIndexOf(".")) : ""; let described = ""; const refresh = async (quiet = false) => { // found from the code itself: definitions in the app's namespace whose value is made by `every(...)` const roles = codebaseRoles(cb); const candidates = cb.store .names() .filter((n) => n.kind === "term" && (!ns || n.path.startsWith(ns + "."))) .filter((n) => isSchedule(cb.store.getDef(n.hash)!, roles)); const jobs: { name: string; spec: string; fn: Fn; hash: string }[] = []; for (const n of candidates) { const rec = cb.store.getDef(n.hash)!; materialize([n.hash], cb.source(), cb.lay); const v = ( (await import(pathToFileURL(cb.modulePath(rec.componentHash)).href)) as Record< string, unknown > )[exportId(n.hash)] as { [k: symbol]: unknown; spec: string; fn: Fn } | undefined; if (v && v[Symbol.for("tcm.job")]) jobs.push({ name: n.path, spec: v.spec, fn: v.fn, hash: n.hash }); } api.setSchedules({ dataDir: data, jobs, log: (line) => console.log(red(` ${line}`)) }); const now = jobs.map((j) => `${j.name} (${j.spec})`).join(", "); if (!quiet && now !== described) console.log( ` jobs ${jobs.length ? `recurring: ${now}` : dim("no recurring jobs; enqueue() from server functions runs here too")}`, ); described = now; }; await refresh(); return { refresh, stop: stopWorker }; }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.pages.isSchedule
- tcmts.cli.roles.codebaseRoles
- tcmts.cli.style.dim
- tcmts.cli.style.red
- tcmts.cli.web.Fn
- tcmts.cli.web.dataDir
- tcmts.cli.web.serverApiHost
- tcmts.core.hash.exportId
- tcmts.core.hash.shortHash
- tcmts.loader.materialize