Skip to content

Complete PowerSync library persistence and synchronization - #5

Merged
Eoic merged 2 commits into
masterfrom
feature/powersync-library-sync
Sep 5, 2026
Merged

Complete PowerSync library persistence and synchronization#5
Eoic merged 2 commits into
masterfrom
feature/powersync-library-sync

Conversation

@Eoic

@Eoic Eoic commented Sep 5, 2026

Copy link
Copy Markdown
Member

Summary

Persist and synchronize the complete library: books, shelves, topics (tags), notes, annotations, bookmarks, and book memberships. Previously, non-book library records lived only in client memory and could not survive restart or propagate between devices.

  • Validate authenticated ownership and references, reject shelf cycles, and apply mixed-table upload transactions atomically.
  • Merge only supplied fields, support explicit null clearing and legacy book payloads, and retain deletion tombstones so stale offline writes cannot restore deleted entities.
  • Preserve shelf hierarchy and membership uniqueness; cascade book deletion through dependent records and the existing media cleanup path.
  • Promote independently editable book metadata into columns while retaining legacy custom metadata.
  • Publish all library tables through automatic owner-filtered streams and retain the demonstration stream.

Migration and rollout

Apply additive revisions dcd3b384e6a4 and af0fea8d6317. Deploy schema/upload support first, refresh replication grants and publication with scripts/setup_local_powersync.sh, activate the expanded PowerSync configuration, and verify a replication checkpoint before updating clients. See docs/powersync-sandbox.md for commands and validation.

No key regeneration or database reset is required. Downgrading removes the newly added domain tables and promoted columns; preserve their data before a downgrade. Keep deletion tombstones while offline clients may still upload old changes.

Validation

  • Backend suite: 295 passed, 2 skipped.
  • Ruff and changed-scope mypy passed; migration checks passed.
  • Local migrations/publication applied and replication checkpoints verified after PowerSync restart.
  • Live validation with two independent clients on one disposable account and a third client on another account passed: all domains, offline restart, field conflicts, null clearing, account isolation, and deletion against stale edits. Upload queues also recovered from the configured rate limit.

Companion client: PapyrusReader/client#23. Workspace: PapyrusReader/papyrus#1. Merge/deploy this server support before the client rollout.

Copilot AI lite review requested due to automatic review settings September 5, 2026 23:02
@Eoic Eoic self-assigned this Sep 5, 2026
@Eoic
Eoic merged commit 01058d6 into master Sep 5, 2026
2 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Deletion-time cascading updates/selects in papyrus/services/library_sync.py should be owner-scoped to prevent cross-tenant effects if inconsistent references ever exist.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR expands the PowerSync-backed persistence layer from books-only to the full owned library domain (shelves, tags, notes, annotations, bookmarks, and membership join tables), including schema/migrations, upload semantics (atomic mixed-table batches, ownership validation, tombstones), and PowerSync publication/stream configuration.

Changes:

  • Add owned library domain tables + tombstones, promote selected book metadata into first-class columns, and introduce a bookmarks follow-up revision.
  • Replace books-only upload handling with a generalized library mutation engine (validation, reference checks, cycle detection, tombstone semantics, and media cleanup).
  • Expand PowerSync streams/publication grants and add regression tests for mixed-batch behavior, migration integrity, and offline conflict/deletion semantics.
File summaries
File Description
tests/test_powersync_sync_config.py Asserts expanded streams are owner-filtered and setup script publishes the right tables.
tests/test_models.py Verifies new library tables are registered in SQLAlchemy metadata with ownership columns.
tests/test_library_migration.py Exercises the main library revision and validates backfill + metadata matches.
tests/test_bookmark_migration.py Exercises the bookmark revision and validates constraints + metadata matches.
tests/services/test_library_validation.py Validates type conversion, overflow handling, and legacy book payload normalization.
tests/api/routes/test_sync.py Updates contract tests to reflect new supported/unsupported tables.
tests/api/routes/test_library_sync.py Adds end-to-end upload regression tests for library entities and conflict semantics.
tests/api/routes/test_bookmark_sync.py Adds end-to-end upload regression tests for bookmarks and tombstone behavior.
scripts/setup_local_powersync.sh Expands grants/publication to include all new library tables.
powersync/sync-config.yaml Adds owner-filtered streams for new tables and expands the books stream projection.
papyrus/services/sync.py Switches upload service from books-only to mixed library mutation application + per-user serialization.
papyrus/services/library_validation.py New conversion/normalization helpers for validating queued payload values.
papyrus/services/library_sync.py New core mutation engine: ownership checks, reference validation, tombstones, and cascading deletes.
papyrus/schemas/sync.py Expands allowed mutation tables/fields and enforces unknown-field rejection per table.
papyrus/models/sync.py Adds promoted book metadata columns.
papyrus/models/library.py Introduces owned library ORM models and tombstones.
papyrus/models/init.py Exposes new library models via the central models import surface for Alembic metadata.
docs/powersync-sandbox.md Documents rollout order, contract details, and validation steps for library sync.
alembic/versions/dcd3b384e6a4_add_owned_library_sync_and_promoted_.py Adds library tables + promoted book columns and performs backfill.
alembic/versions/af0fea8d6317_add_owned_bookmark_sync.py Adds bookmarks table and constraints in a follow-up additive revision.
Review details

Suppressed comments (1)

papyrus/services/library_sync.py:98

  • Shelf deletion reparents children based only on parent_shelf_id. Because the FK does not enforce same-owner relationships, adding owner_user_id == user_id here avoids the possibility of affecting another user's shelves if inconsistent cross-tenant references ever exist.
    if table == "shelves":
        await session.execute(
            update(SyncShelf)
            .where(SyncShelf.parent_shelf_id == row_id)
            .values(parent_shelf_id=None, updated_at=datetime.now(UTC))
        )
  • Files reviewed: 20/20 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

if table == "books":
for child_table in ("notes", "annotations", "bookmarks"):
model = MODELS[child_table]
result = await session.execute(select(model).where(model.book_id == row_id))
@Eoic
Eoic deleted the feature/powersync-library-sync branch September 6, 2026 00:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants