trails/packages/jobs/src/worker.ts
Ullrich Schäfer c2e8461f9c
Upgrade pg-boss from 10 to 12
Supersedes dependabot #264 — this one involves code changes that PR
couldn't make.

## Breaking changes in our usage surface

### v12: default export removed → named `PgBoss` export

Affects every file that imports the SDK:
- `packages/jobs/src/boss.ts`: `import PgBoss` → `import { PgBoss }`
- `packages/jobs/src/worker.ts`: same for the type import
- `packages/jobs/src/types.ts`: `PgBoss.Job<T>` → `Job<T>` (types.ts
  re-exports `Job` at the package root in v12)

### v11: queue names restricted to `[A-Za-z0-9_.-]`

Colon `:` is no longer allowed. Renamed the two journal queues that
had it:
- `demo-bot:generate` → `demo-bot-generate`
- `demo-bot:prune`    → `demo-bot-prune`

The planner's `expire-sessions` was already valid.

### v12: minimum Node 22.12

Not a code change for us — journal + planner Dockerfiles are on
`node:25-slim`, CI runners on 24.

## Regression guard

Added `assertValidJobName()` in `@trails-cool/jobs`, called by
`startWorker()` before any side effects. Pg-boss v11+ silently accepts
an invalid name then rejects the underlying SQL call later — we fail
loudly at boot instead. Unit tests cover the character-class rule
and exercise the exact old-name (`demo-bot:generate`) as a
regression fence.

## Prod rollout note

Pg-boss v11 dropped the auto-migration path from v10. On deploy, the
live `pgboss` schema from v10 won't migrate cleanly. Simplest path:
`DROP SCHEMA pgboss CASCADE` before the first v12 worker starts —
our jobs are all cron-scheduled and will re-register themselves on
boot, so there's nothing durable to preserve in the queue.

## Verified

- `pnpm typecheck` / `pnpm lint` / `pnpm test` — all clean
- `pnpm exec playwright test --workers=2` — 50/50 passed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:01:11 +02:00

51 lines
1.5 KiB
TypeScript

import type { PgBoss } from "pg-boss";
import type { JobDefinition } from "./types.ts";
/**
* Characters pg-boss v11+ accepts for queue and schedule keys. Stricter
* than v10, which silently accepted `:` (and therefore let our
* `demo-bot:generate` etc. names through on older pg-boss). On the
* upgrade path we renamed them, and this guard keeps us from regressing.
*/
const VALID_NAME = /^[A-Za-z0-9_.-]+$/;
export function assertValidJobName(name: string): void {
if (!VALID_NAME.test(name)) {
throw new Error(
`Invalid pg-boss queue name "${name}": only letters, numbers, hyphens, underscores, and periods are allowed.`,
);
}
}
export async function startWorker(
boss: PgBoss,
jobs: JobDefinition[],
): Promise<void> {
// Validate every job name before any side effects so a bad name fails
// loudly at worker boot instead of silently producing an unreachable
// queue somewhere downstream.
for (const job of jobs) assertValidJobName(job.name);
await boss.start();
for (const job of jobs) {
await boss.createQueue(job.name);
if (job.cron) {
await boss.schedule(job.name, job.cron, undefined, {
retryLimit: job.retryLimit,
expireInSeconds: job.expireInSeconds,
});
}
await boss.work(job.name, job.handler);
}
const onShutdown = async () => {
await boss.stop({ graceful: true, timeout: 10_000 });
process.exit(0);
};
process.on("SIGTERM", onShutdown);
process.on("SIGINT", onShutdown);
}