feat(sync): let a client start the delta feed at the present

GET /api/sync/changes?start=now answers an empty page whose cursor sits at
serverNow in every domain, in the same opaque format the feed hands out.
A client that only needs what changes from now on, such as the Apple
Health write-back, pages forward from it instead of draining the whole
history first. start and cursor together are refused with 422.

Refs #1339
This commit is contained in:
Marc-André Bombeck committed 2026-11-04 21:24:12 +01:00
1 parent 59434cd463
commit a79fc4168a
9 files changed
+351 -5

No files matched your search

+21
View File
@@ -13,6 +13,7 @@ import {
decodeCursor,
decodeCursorIssuedAt,
encodeCursor,
encodeCursorAtNow,
SYNC_DOMAINS,
type SyncCursor,
type SyncDomain,
@@ -108,6 +109,26 @@ function emptyChanges(): SyncChangesBody["changes"] {
};
}
/**
* `start=now` (#1339): an empty page with a cursor at `serverNow` in every
* domain, for a client that wants changes from now on (the Apple Health
* write-back) without paging through the history first. `hasMore` is false:
* the client is caught up as of `serverNow` by definition.
*/
export function syncChangesFromNow(serverNow: Date): SyncChangesBody {
annotate({
action: { name: "sync.changes.start_now" },
meta: { returned: 0 },
});
return {
serverNow: serverNow.toISOString(),
cursor: encodeCursorAtNow(serverNow.getTime()),
hasMore: false,
cursorExpired: false,
changes: emptyChanges(),
};
}
export async function pullSyncChanges(input: {
userId: string;
/** The opaque cursor the client echoed, if any. */