Workspaces: config (copied), outreach-core (schema + actions/queries + hard gates), outreach-ai (Gemini client + embeddings copies, profiler and mission-fit-judge agent stubs), outreach-worker (DBOS executor with nightly ingest + hourly expiry workflows), outreach-review (RR7 review queue v0). Initial drizzle migration incl. pgvector extension. Stack contract: Yarn 4.5.0 + Turbo, Node 22.16, Drizzle 0.44.6 + pgvector, DBOS 4.17.6, @google/genai on Vertex, gemini-embedding-001 @1536, React Router v7. Files copied from novelpad-desktop carry provenance headers @ 62c56b87. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 lines
3.0 KiB
TypeScript
77 lines
3.0 KiB
TypeScript
// 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);
|
|
});
|