feat: image core — format math, sharp pipeline, sticker contour, prompts, pg-boss queue+worker

- format.ts: exact px (cm/2.54*dpi), catalog incl. The Frame/portrait, aspect ratio
- pipeline.ts: finalizeToFormat (cover/attention crop, dpi metadata), stickerContour
  (alpha dilation -> white border), heic->png, german filename slug
- prompts.ts: task prompts (clean/cutout/format) — reproduce, don't reinterpret
- process.ts: per-item chain (load -> model /v1/images -> finalize -> contour -> store)
- queue.ts + worker.ts: pg-boss (concurrency, retry, counters, 402 pause, completion)
- verified locally: 30x40@300dpi = exact 3543x4724 + dpi meta; sticker white border + alpha
This commit is contained in:
2026-07-23 11:20:55 +00:00
parent b46dbbe889
commit 577b13f680
8 changed files with 434 additions and 1 deletions
+34
View File
@@ -0,0 +1,34 @@
import PgBoss from 'pg-boss';
export const QUEUE = 'generate';
export interface GenerateJob { itemId: string; jobId: string; }
let boss: PgBoss | null = null;
export async function getBoss(): Promise<PgBoss> {
if (!boss) {
boss = new PgBoss({ connectionString: process.env.DATABASE_URL });
boss.on('error', (e) => console.error('[pg-boss]', e));
await boss.start();
await boss.createQueue(QUEUE);
}
return boss;
}
export async function enqueue(job: GenerateJob): Promise<void> {
const b = await getBoss();
await b.send(QUEUE, job, { retryLimit: 2, retryBackoff: true });
}
export async function startWorker(
concurrency: number,
handler: (job: GenerateJob) => Promise<void>,
): Promise<void> {
const b = await getBoss();
await b.work<GenerateJob>(
QUEUE,
{ batchSize: Math.max(1, concurrency), pollingIntervalSeconds: 2 },
async (jobs) => { await Promise.all(jobs.map((j) => handler(j.data))); },
);
console.log(`[queue] Worker aktiv (Parallelität ${concurrency}).`);
}