import { db } from '@sim/db' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' import { billingOutboxHandlers } from '@/lib/billing/webhooks/outbox-handlers' import { processOutboxEvents } from '@/lib/core/outbox/service' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { workflowDeploymentOutboxHandlers } from '@/lib/workflows/deployment-outbox' import { reapStaleBackgroundWork } from '@/ee/workspace-forking/lib/background-work/store' const logger = createLogger('OutboxProcessorAPI') export const dynamic = 'force-dynamic' export const maxDuration = 120 const handlers = { ...billingOutboxHandlers, ...workflowDeploymentOutboxHandlers, } as const export const GET = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { const authError = verifyCronAuth(request, 'Outbox processor') if (authError) { return authError } const result = await processOutboxEvents(handlers, { batchSize: 20, maxRuntimeMs: 110_000, minRemainingMs: 95_000, }) // Reap fork background-work rows stuck `processing` past their TTL (worker crash / // restart has no in-task hook). Independent of the outbox; a failure here must not // fail the outbox run, so it's guarded separately. let reapedBackgroundWork = 0 try { reapedBackgroundWork = await reapStaleBackgroundWork(db) } catch (error) { logger.error('Background-work reap failed', { requestId, error: toError(error).message }) } logger.info('Outbox processing completed', { requestId, ...result, reapedBackgroundWork }) return NextResponse.json({ success: true, requestId, result, reapedBackgroundWork, }) } catch (error) { logger.error('Outbox processing failed', { requestId, error: toError(error).message }) return NextResponse.json( { success: false, requestId, error: toError(error).message }, { status: 500 } ) } })