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>
51 lines
1.5 KiB
TypeScript
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);
|
|
}
|