Add per-resource cursors, fetch-by-id, and full backfill to WHOOP sync

Replace the single shared last-synced cursor with a per-resource cursor map
on the WHOOP connection. Recovery, sleep, workout, and cycle each advance only
their own cursor on a successful fetch+upsert, so a slow or 403-ing collection
never drags the incremental window forward for its siblings and its own
late-arriving records stay inside the overlap. Legacy connections fall back to
the shared last-synced instant, so no historical state is lost.

Add single-record fetch-by-id client routes and per-resource by-id syncs. The
update webhook now carries the resource id through the queue payload, so the
worker resolves and upserts that one record immediately rather than re-walking
the whole collection on the next overlap tick.

Anchor a full sync at a fixed deep-history point instead of returning no lower
bound, so a backfill covers the multi-year span an incremental tick would skip
and a stale cursor cannot silently narrow it.
This commit is contained in:
Marc-André Bombeck committed 2026-06-14 17:19:09 +02:00
1 parent a2260d5bb8
commit 2cb84c17e9
16 files changed
+938 -126

No files matched your search

@@ -162,8 +162,11 @@ describe("POST /api/whoop/webhook/[token]", () => {
where: { whoopUserId: "42" },
select: { userId: true },
});
// v1.16.16 (iOS #17) — the resource id rides through so the worker can do
// a targeted fetch-by-id refresh instead of re-walking the collection.
expect(send).toHaveBeenCalledWith("whoop-recovery-sync", {
userId: "user-1",
resourceId: "abc",
});
});