// Ereignisse werden erst NACH dem Commit ausgeliefert (events.ts#withDeferredEvents, von // services/context.ts#inTransaction gesetzt). Vorher lief der Benachrichtigungs- und Mailversand // noch in der offenen Transaktion und zählte gegen deren Zeitlimit — unter Last brach der // Einsatzstart deshalb sporadisch ab. // (1) innerhalb der Transaktion: noch keine Benachrichtigung // (2) nach dem Commit: Benachrichtigung vorhanden // (3) Rollback: Ereignis wird verworfen, keine Benachrichtigung // (4) ohne Transaktion: unverändert sofortige Auslieferung // // Lauf: npx tsx scripts/test-events-deferred.ts import "dotenv/config"; import { randomUUID } from "node:crypto"; import type { DomainEvent } from "../src/lib/events"; import { prisma } from "../src/server/db"; import { emitEvent } from "../src/server/events"; import { inTransaction } from "../src/server/services/context"; import { createTenant, ok, runSuite, section, type TenantFixture } from "./lib/e2e-fixture"; const SLUG = "zz-events-deferred"; const TYPE = "work_order.assigned"; async function order(A: TenantFixture) { return prisma.workOrder.create({ data: { tenantId: A.tenantId, number: `EV-${randomUUID().slice(0, 6)}`, title: "Heizung warten", customerId: A.customerId, siteId: A.siteId, assignedTeamId: A.teamId, status: "assigned", }, }); } const ev = (entityId: string): DomainEvent => ({ type: TYPE, entityType: "work_order", entityId }); const notifications = (tenantId: string, entityId: string) => prisma.notification.count({ where: { tenantId, type: TYPE, entityId } }); void runSuite("Ereignisse nach dem Commit", [SLUG], async () => { const A = await createTenant(SLUG); section("Transaktion mit Commit"); const wo1 = await order(A); const duringTransaction = await inTransaction(A.ctx.backoffice, async (tx) => { await emitEvent(tx, ev(wo1.id)); return notifications(A.tenantId, wo1.id); }); ok(duringTransaction === 0, "(1) während der Transaktion wird noch nichts zugestellt"); ok((await notifications(A.tenantId, wo1.id)) > 0, "(2) nach dem Commit ist die Benachrichtigung da"); section("Rollback"); const wo2 = await order(A); let rolledBack = false; try { await inTransaction(A.ctx.backoffice, async (tx) => { await emitEvent(tx, ev(wo2.id)); throw new Error("rollback"); }); } catch { rolledBack = true; } ok(rolledBack, "(3) Transaktion abgebrochen"); ok((await notifications(A.tenantId, wo2.id)) === 0, "(3) verworfenes Ereignis löst keine Benachrichtigung aus"); section("Ohne Transaktion"); const wo3 = await order(A); await emitEvent(A.ctx.backoffice, ev(wo3.id)); ok((await notifications(A.tenantId, wo3.id)) > 0, "(4) ohne Transaktion unverändert sofort zugestellt"); });