// POST /api/offline/sync - run ONE bounded delta-sync cycle for the calling // session's account. // // The renderer drives this: once at launch (catch-up for whatever changed while // the app was closed, for which no push event was ever delivered) and on each // JMAP `StateChange` from the live push connection. There is no background worker // and no resident credential - see `lib/offline-replica/sync.ts`'s header for why // that architecture choice keeps most of the original design review's critical // findings out of scope entirely. // // A cycle is BOUNDED. `unfinishedWork: true` means "call again", and the renderer // chains with a cap; it never means an error. import { NextRequest, NextResponse } from 'next/server'; import { clampPolicy, type RetentionPolicy } from '@/lib/offline-replica/store'; import { resolveIndexSession, syncAccount } from '@/lib/offline-replica/engine'; import { gateReplicaRoute, NO_STORE, replicaErrorResponse } from '@/lib/offline-replica/route-gate'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; export async function POST(request: NextRequest) { const gated = gateReplicaRoute(); if (gated) return gated; let body: Record = {}; try { const text = await request.text(); if (text.trim()) body = JSON.parse(text) as Record; } catch { return NextResponse.json({ error: 'Malformed JSON body' }, { status: 400 }); } const rawPolicy = body.policy; const policy: RetentionPolicy | undefined = rawPolicy && typeof rawPolicy === 'object' && !Array.isArray(rawPolicy) ? clampPolicy(rawPolicy as Partial) : undefined; try { const session = await resolveIndexSession(request); const report = await syncAccount(session, { policy, forceResync: body.forceResync === true, }); return NextResponse.json({ ok: report.ok, report }, { headers: NO_STORE }); } catch (error) { return replicaErrorResponse(error, 'sync'); } }