mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
fix(sync): place a start=now cursor a minute behind the present
updatedAt is stamped when a write is built, not when it commits, so a transaction in flight when the cursor was handed out could commit a row stamped before it, and a watermark at serverNow skipped that row for good. The watermarks now sit one minute back; client upserts are idempotent, so the overlap is harmless. The token's issue time stays serverNow.
This commit is contained in:
1 parent
126bab8c00
commit
01884e91fa
8 files changed
+79
-26
No files matched your search
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* `GET /api/sync/changes?start=now` (#1339) — a cursor at the present.
|
||||
*
|
||||
* The answer is an empty page whose cursor sits at `serverNow` in every
|
||||
* domain, in the feed's own opaque format, and nothing is read: the point is
|
||||
* The answer is an empty page whose cursor sits a minute before `serverNow`
|
||||
* in every domain (a write stamped before it and committed after it is not
|
||||
* skipped), in the feed's own opaque format, and nothing is read: the point is
|
||||
* to skip the history walk, so a read here would be the bug. The database is
|
||||
* deliberately not mocked and not reachable: any read would fail the request,
|
||||
* so the 200 below is itself the proof that none happened.
|
||||
@@ -54,7 +55,7 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe("GET /api/sync/changes?start=now", () => {
|
||||
it("answers an empty page with a cursor at serverNow in every domain", async () => {
|
||||
it("answers an empty page with a cursor a minute before serverNow in every domain", async () => {
|
||||
const before = Date.now();
|
||||
const res = await GET(req("?start=now"));
|
||||
const after = Date.now();
|
||||
@@ -80,7 +81,10 @@ describe("GET /api/sync/changes?start=now", () => {
|
||||
const cursor = decodeCursor(data.cursor);
|
||||
expect(cursor).not.toBeNull();
|
||||
for (const domain of SYNC_DOMAINS) {
|
||||
expect(cursor![domain], domain).toEqual({ updatedAtMs: nowMs, id: "0" });
|
||||
expect(cursor![domain], domain).toEqual({
|
||||
updatedAtMs: nowMs - 60_000,
|
||||
id: "0",
|
||||
});
|
||||
}
|
||||
expect(decodeCursorIssuedAt(data.cursor)).toBe(nowMs);
|
||||
});
|
||||
|
||||
@@ -43,8 +43,9 @@
|
||||
* cycle tracking off gets both blocks empty (never a 403 — see the
|
||||
* gate note in the handler) and their cursor watermarks untouched.
|
||||
*
|
||||
* `start=now` (#1339) answers an empty page whose cursor sits at
|
||||
* `serverNow` in every domain, in the same opaque format, so a client that
|
||||
* `start=now` (#1339) answers an empty page whose cursor sits a minute
|
||||
* before `serverNow` in every domain (`START_NOW_OVERLAP_MS`), in the same
|
||||
* opaque format, so a client that
|
||||
* only wants what changes from now on (the Apple Health write-back) pages
|
||||
* forward from there instead of draining the whole history first.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user