feat(sync): sync support for the native app #68

Merged
eliott merged 10 commits from feat/sync-support into develop 2026-10-16 17:06:05 +02:00
Owner

Sync support for the native app

Gives offline clients a way to pull everything that changed for a user since a cursor, including deletions and global rows, and to push a batch of local changes with client-minted ids, resolved by last write wins. The REST endpoints and the web app are unchanged.

Spec: docs/superpowers/specs/2026-10-13-api-sync-support-design.md. Consumer: rollnote-app branch feat/foundation.

What is in

  • updatedAt (NOT NULL, millisecond precision) and syncedAt (server write time, the only pull cursor) on the 11 synced tables, with sync indexes; new sync_tombstone and sync_reset tables; migration 0028_sync_support with backfills.
  • Tombstones written in the same transaction as every hard delete, cascades included; import commit and data wipe record a reset.
  • GET /sync/status, GET /sync/pull/:entity (keyset on (syncedAt, id)), GET /sync/tombstones, POST /sync/push (up to 200 ops, one transaction per op, statuses applied | stale | renumbered | missing-parent | rejected | retry, shoot row locks for frame writes, clock clamp, nulling of references to tombstoned rows).
  • Better-Auth expo() plugin, rollnote:// trusted origin (plus exp:// in development), origin checks kept on in tests.
  • JSON body limit raised to 1 MB for push batches.
  • Version 0.11.0.

Verification

Lint and build clean; 152 unit and 186 integration tests (testcontainers), including a two-client convergence suite, tombstone cascades, overlap and reset cases. The native foundation's device acceptance ran against this branch.

Notes

  • Migration 0028 collides with the uncommitted 0028 in the main checkout (isGlobal NOT NULL, film_format unique); whichever merges second must be renumbered and its snapshot regenerated.
  • No tombstone prune scheduler yet; resyncBefore already covers the 180-day retention edge.

https://claude.ai/code/session_013Ew5mX388EkuWC6ytSKkdc

## Sync support for the native app Gives offline clients a way to pull everything that changed for a user since a cursor, including deletions and global rows, and to push a batch of local changes with client-minted ids, resolved by last write wins. The REST endpoints and the web app are unchanged. Spec: `docs/superpowers/specs/2026-10-13-api-sync-support-design.md`. Consumer: `rollnote-app` branch `feat/foundation`. ### What is in - `updatedAt` (NOT NULL, millisecond precision) and `syncedAt` (server write time, the only pull cursor) on the 11 synced tables, with sync indexes; new `sync_tombstone` and `sync_reset` tables; migration `0028_sync_support` with backfills. - Tombstones written in the same transaction as every hard delete, cascades included; import commit and data wipe record a reset. - `GET /sync/status`, `GET /sync/pull/:entity` (keyset on `(syncedAt, id)`), `GET /sync/tombstones`, `POST /sync/push` (up to 200 ops, one transaction per op, statuses `applied | stale | renumbered | missing-parent | rejected | retry`, shoot row locks for frame writes, clock clamp, nulling of references to tombstoned rows). - Better-Auth `expo()` plugin, `rollnote://` trusted origin (plus `exp://` in development), origin checks kept on in tests. - JSON body limit raised to 1 MB for push batches. - Version 0.11.0. ### Verification Lint and build clean; 152 unit and 186 integration tests (testcontainers), including a two-client convergence suite, tombstone cascades, overlap and reset cases. The native foundation's device acceptance ran against this branch. ### Notes - Migration 0028 collides with the uncommitted `0028` in the main checkout (isGlobal NOT NULL, film_format unique); whichever merges second must be renumbered and its snapshot regenerated. - No tombstone prune scheduler yet; `resyncBefore` already covers the 180-day retention edge. https://claude.ai/code/session_013Ew5mX388EkuWC6ytSKkdc
refactor(sync): split push and pull services for the complexity gate
All checks were successful
tests / test (pull_request) Successful in 3m15s
774583c25a
Split every function the Fallow new-only gate flagged (CRAP >= 30) into
helpers with cyclomatic complexity of at most 4, without changing
behaviour. Pure decisions (error-to-outcome mapping, insert and update
values, frame renumbering outcome, validation message, date revival,
unique candidate, resync edge, page splitting) move to sync-rules.ts with
unit tests; the DB steps become small private methods. The per-op
transaction and the shoot locking order are unchanged. The convergence
spec's pullAll helper is split into per-entity, per-row and tombstone
helpers.

Claude-Session: https://claude.ai/code/session_013Ew5mX388EkuWC6ytSKkdc
eliott merged commit f179cd9840 into develop 2026-10-16 17:06:05 +02:00
eliott deleted branch feat/sync-support 2026-10-16 17:06:05 +02:00
Sign in to join this conversation.
No reviewers
No labels
run-ci
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
RollNote/rollnote-api!68
No description provided.