Sync System Improvements - Implementation Summary
Overview
Enhanced the Nostr subscription sync system to properly track feed deletions and read status across devices.
Changes Made
1. Database Schema Updates (prisma/schema.prisma)
Subscription Model
- Added
updatedAtfield to track when subscriptions change - Added
deletedAtfield for soft-delete tracking (instead of hard deletes) - Added indexes for efficient querying of deleted items
ReadItem Model
- Added
syncedAtfield to track when read status was last synced to Nostr - Added index for efficient querying of unsynced items
2. Sync Library Updates (src/lib/nostr-sync.ts)
New Interfaces
- Extended
SubscriptionListwithdeleted?: string[]array - Added
ReadStatusListinterface for read status sync
New Event Kind
- Added
READ_STATUS_KIND = 30405for syncing read status
Enhanced Functions
buildSubscriptionListFromFeeds()- Now includes deleted feeds in outputmergeSubscriptionLists()- Now returnstoRemovearray for feeds deleted remotely- Added
publishReadStatus()- Publish read items to Nostr - Added
fetchReadStatus()- Fetch read items from Nostr
3. API Updates (src/server/api/routers/feed.ts)
Modified Endpoints
getFeeds- Now filters out soft-deleted subscriptions (deletedAt: null)unsubscribeFeed- Changed from hard delete to soft delete (setsdeletedAt)
New Endpoints
getSubscriptionsForSync- Returns all subscriptions including deleted ones for synccleanupDeletedSubscriptions- Hard deletes old soft-deleted records (cleanup)markReadItemsSynced- Marks read items as synced after publishinggetUnsyncedReadItems- Gets read items that haven’t been synced yet
4. Documentation Updates (SUBSCRIPTION_SYNC.md)
- Added documentation for kind 30405 (read status sync)
- Added
deletedfield to subscription list schema - Added
ReadStatusListinterface documentation
How It Works
Deletion Tracking
- When a user unsubscribes from a feed, it’s soft-deleted (deletedAt timestamp set)
- During sync, deleted feeds are included in the
deletedarray in the Nostr event - When another device syncs, it sees the deleted feeds and removes them locally
- Old soft-deleted records can be cleaned up after 90 days (configurable)
Read Status Sync
- When items are marked as read, they’re tracked in the local database
- Unsynced read items can be published to Nostr (kind 30405)
- When syncing on another device, the read status is fetched and applied
- Items are marked with
syncedAttimestamp after successful sync
Migration
A database migration has been created:
- File:
prisma/migrations/20260128144700_add_sync_tracking/migration.sql - Adds
deletedAtandupdatedAtto Subscription table - Adds
syncedAtto ReadItem table - Creates necessary indexes
To apply: Run npx prisma migrate deploy in production or npx prisma migrate dev in development
Benefits
- No More Re-adding Deleted Feeds: Deletions are now tracked and synced across devices
- Read Status Sync: Read/unread status can be synced across devices (optional feature)
- Conflict Resolution: Soft deletes allow for better conflict resolution in sync scenarios
- Data Integrity: No loss of information during sync operations
- Cleanup: Old deleted records can be purged after a grace period
Next Steps for Implementation
- Apply the database migration
- Update client-side sync logic to use new
toRemovearray from merge function - Implement UI for read status sync (optional feature)
- Add periodic cleanup job for old deleted subscriptions
- Test cross-device sync scenarios
Backward Compatibility
- Existing sync events (kind 30404) without
deletedfield will continue to work - The
deletedfield is optional and only included when there are deletions - Read status sync (kind 30405) is completely new and optional