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>
60 lines
1.9 KiB
TypeScript
60 lines
1.9 KiB
TypeScript
import type { JobDefinition } from "@trails-cool/jobs";
|
|
import {
|
|
DEMO_BACKFILL_TARGET,
|
|
DEMO_DAILY_CAP,
|
|
countSyntheticRoutesRecent,
|
|
countSyntheticRoutesTotal,
|
|
ensureDemoUser,
|
|
generateOneWalk,
|
|
isDemoBotEnabled,
|
|
refreshDemoBotGauges,
|
|
shouldWalkNow,
|
|
} from "../lib/demo-bot.server.ts";
|
|
import { logger } from "../lib/logger.server.ts";
|
|
|
|
/**
|
|
* Generate job. Fires every 30 minutes via pg-boss cron. Handler steps:
|
|
*
|
|
* 1. Bail if `DEMO_BOT_ENABLED` is not "true".
|
|
* 2. On first run (no synthetic rows yet) produce a small backfill so the
|
|
* demo user's profile has content immediately.
|
|
* 3. Otherwise apply the decide-to-walk gate (local hour + p=0.09) and
|
|
* daily cap; on pass, insert one route+activity via `generateOneWalk`.
|
|
*/
|
|
export const demoBotGenerateJob: JobDefinition = {
|
|
name: "demo-bot-generate",
|
|
cron: "0,30 * * * *",
|
|
retryLimit: 1,
|
|
expireInSeconds: 120,
|
|
async handler() {
|
|
if (!isDemoBotEnabled()) return { skipped: "disabled" };
|
|
|
|
const ownerId = await ensureDemoUser();
|
|
|
|
const total = await countSyntheticRoutesTotal();
|
|
if (total === 0) {
|
|
let inserted = 0;
|
|
for (let i = 0; i < DEMO_BACKFILL_TARGET; i++) {
|
|
const id = await generateOneWalk(ownerId);
|
|
if (id) inserted++;
|
|
}
|
|
logger.info({ inserted }, "demo-bot backfill complete");
|
|
await refreshDemoBotGauges();
|
|
return { mode: "backfill", inserted };
|
|
}
|
|
|
|
const recent = await countSyntheticRoutesRecent(14);
|
|
if (recent >= DEMO_DAILY_CAP) {
|
|
logger.info({ recent, cap: DEMO_DAILY_CAP }, "demo-bot cap hit, skipping");
|
|
return { skipped: "cap", recent };
|
|
}
|
|
|
|
const now = new Date();
|
|
if (!shouldWalkNow(now)) return { skipped: "gate" };
|
|
|
|
const id = await generateOneWalk(ownerId, { now });
|
|
logger.info({ inserted: id ? 1 : 0, routeId: id }, "demo-bot walk");
|
|
await refreshDemoBotGauges();
|
|
return { mode: "single", routeId: id };
|
|
},
|
|
};
|