fix(jobs): let pg-boss create its schema only when it is missing

Postgres checks the database's CREATE privilege before it checks
whether the schema exists, so CREATE SCHEMA IF NOT EXISTS fails for a
role that may not create schemas even when the schema is there. The
worker now asks first and passes createSchema: false when it exists.
This commit is contained in:
Marc-André Bombeck committed 2026-11-03 20:32:53 +01:00
1 parent f05a112c61
commit 95525a0259
2 files changed
+24 -3

No files matched your search

+16 -2
View File
@@ -88,8 +88,22 @@ function databaseOptions(
} as const;
}
export function createWorkerBoss(connectionString: string): PgBoss {
return new PgBoss(databaseOptions(connectionString, "worker"));
/**
* The worker's pg-boss. `createSchema` says whether pg-boss's first install
* may run `CREATE SCHEMA IF NOT EXISTS pgboss`. Postgres checks the database's
* CREATE privilege before it checks whether the schema exists, so under the
* app's restricted role (`scripts/db-app-role.mjs`), which may not create
* schemas, that statement fails even though the boot already created the
* schema for it. The caller passes false when the schema is there.
*/
export function createWorkerBoss(
connectionString: string,
options: { createSchema?: boolean } = {},
): PgBoss {
return new PgBoss({
...databaseOptions(connectionString, "worker"),
...(options.createSchema === false ? { createSchema: false } : {}),
});
}
function producerOptions(connectionString: string) {
+8 -1
View File
@@ -11,6 +11,7 @@
* Usage: Run as a standalone process or call startReminderWorker() from a
* custom server setup. In dev, use: npx tsx src/lib/jobs/reminder-worker.ts
*/
import { prisma } from "@/lib/db";
import { markWorkerStarted, recordError } from "@/lib/jobs/worker-status";
import { createWorkerBoss, setGlobalBoss } from "@/lib/jobs/boss-instance";
import { reportJobsCutOffAtBoot } from "@/lib/jobs/job-observer";
@@ -47,7 +48,13 @@ export async function startReminderWorker() {
return;
}
const boss = createWorkerBoss(DATABASE_URL);
// The queue schema exists once a worker has started, and on every boot
// the restricted database role runs under (the entrypoint creates it for
// that role); only a fresh database without either needs pg-boss to
// create it.
const [{ present }] = await prisma.$queryRaw<{ present: boolean }[]>`
SELECT to_regnamespace('pgboss') IS NOT NULL AS present`;
const boss = createWorkerBoss(DATABASE_URL, { createSchema: !present });
boss.on("error", (error: unknown) => {
workerLog("error", "boss emitted error", error);