// Copied/adapted from novelpad-desktop apps/workflow-worker/src/main.ts @ 62c56b87 /** * Outreach worker — DBOS executor process. * * Runs the grant-match outreach engine's scheduled workflows (nightly grant * ingestion, hourly expiry sweep, and future scoring/sync jobs) against the * outreach Postgres schema. Kept as its own process (mirroring novelpad's * workflow-worker split) so long-running ingestion/scoring steps don't share * a runtime with any future HTTP-facing app in this monorepo. * * Boot order is load-bearing (see ADR 0007 in novelpad-desktop): * 1. Import the workflow modules for side-effect registration * (`DBOS.registerWorkflow` / `DBOS.registerScheduled`) BEFORE * `DBOS.launch()` — DBOS only dispatches scheduled/queued jobs for * workflows that were registered before launch. * 2. Build the pg Pool + Drizzle db. * 3. `DBOS.setConfig(...)`. * 4. `DBOS.launch()` — register-mark closes; the scheduler starts firing. */ import { DBOS } from '@dbos-inc/dbos-sdk'; import { DrizzleDataSource } from '@dbos-inc/drizzle-datasource'; import { schema } from '@novelpad/outreach-core'; import { drizzle } from 'drizzle-orm/node-postgres'; import pg from 'pg'; // Importing these modules registers `ingestGrants` (nightly) and // `expireGrants` (hourly) as DBOS workflows + scheduled functions as a side // effect of module evaluation. Must happen before `DBOS.launch()` below. Each // module also exports a `set*Deps` injector — DBOS serializes // scheduled-function args, so the `db` handle can't be passed through the // scheduler; it's threaded in via this module-scope registry instead (same // pattern as novelpad-desktop's `setStartDeps`). import { setExpireGrantsDeps } from './workflows/expire-grants.js'; import { setIngestGrantsDeps } from './workflows/ingest-grants.js'; if (process.env.DATABASE_URL == null) { throw new Error('outreach-worker: DATABASE_URL is required'); } const { Pool } = pg; const dbosClientConfig = { connectionString: process.env.DATABASE_URL, application_name: 'helmdocs-outreach-worker', } satisfies pg.ClientConfig; const pool = new Pool({ ...dbosClientConfig, max: Number(process.env.PG_POOL_MAX ?? 20), }); export const db = drizzle(pool, { schema }); async function main() { // DBOS system schema lives in this same database; initialize it with the // same connection settings as the worker pool. await DrizzleDataSource.initializeDBOSSchema(dbosClientConfig); // Inject the shared `db` handle into each scheduled workflow's deps // registry before launch, so the first scheduled tick (which may fire // immediately on an `ExactlyOncePerInterval` catch-up) always has it. setIngestGrantsDeps({ db }); setExpireGrantsDeps({ db }); DBOS.setConfig({ name: 'helmdocs-outreach-worker', systemDatabasePool: pool, runAdminServer: false, }); await DBOS.launch(); console.log('[outreach-worker] launched; scheduled workflows active'); } main().catch((err) => { console.error('[outreach-worker] fatal:', err); process.exit(1); });