// Sync service - manages offline mutation queueing // // Simplified service that coordinates with the Rust backend. // The Rust backend handles sync queue persistence and processing logic. // This service provides a thin TypeScript API for queuing mutations. // // TRACES: UR-002, UR-017, UR-025 | DR-014 import { commands } from "$lib/api/bindings"; import { auth } from "$lib/stores/auth"; // Types matching Rust structs export interface SyncQueueItem { id: number; userId: string; operation: string; itemId: string | null; payload: string | null; status: string; retryCount: number; createdAt: string | null; errorMessage: string | null; } export type SyncOperation = | "mark_played" | "mark_favorite" | "unmark_favorite" | "update_progress" | "report_playback_start" | "report_playback_stopped" | "playlist_create" | "playlist_delete" | "playlist_rename" | "playlist_add_items" | "playlist_remove_items" | "playlist_reorder_item"; /** * Simplified sync service - handles offline mutation queueing * * The Rust backend maintains the sync queue in SQLite and is responsible * for processing queued items. This service provides a TypeScript API * for queueing and managing sync operations. */ class SyncService { /** * Start the sync service (lifecycle managed by Rust backend) */ start(): void { console.log("[SyncService] Started"); } /** * Stop the sync service (lifecycle managed by Rust backend) */ stop(): void { console.log("[SyncService] Stopped"); } /** * Queue a mutation for sync to server * * TRACES: UR-017, UR-025 | DR-014 */ async queueMutation( operation: SyncOperation, itemId: string, payload?: Record ): Promise { const userId = auth.getUserId(); if (!userId) { throw new Error("Not authenticated"); } const id = await commands.syncQueueMutation( userId, operation, itemId, payload ? JSON.stringify(payload) : null ); console.log(`[SyncService] Queued ${operation} for item ${itemId}, id: ${id}`); return id; } // NOTE: `queueFavorite` is gone. Favourites are drained by Rust on the // `connectivity:reconnected` signal (DR-120) — the local write already sets // `pending_sync`, and a second queue here would push the same change twice. // See src-tauri/src/commands/favorites.rs. /** * Queue playback progress update * Also updates local state immediately */ async queuePlaybackProgress( itemId: string, positionMs: number ): Promise { // Update local state first await commands.storageUpdatePlaybackProgress(auth.getUserId() ?? "", itemId, positionMs); return this.queueMutation("update_progress", itemId, { positionMs }); } /** * Queue mark as played * Also updates local state immediately */ async queueMarkPlayed(itemId: string): Promise { // Update local state first await commands.storageMarkPlayed(auth.getUserId() ?? "", itemId); return this.queueMutation("mark_played", itemId); } /** * Get count of pending sync operations */ async getPendingCount(): Promise { const userId = auth.getUserId(); if (!userId) { return 0; } return commands.syncGetPendingCount(userId); } /** * Get pending sync items (for debugging/monitoring) */ async getPending(limit?: number): Promise { const userId = auth.getUserId(); if (!userId) { return []; } return commands.syncGetPending(userId, limit ?? null); } /** * Clean up completed operations older than specified days */ async cleanup(daysOld: number = 7): Promise { const deleted = await commands.syncCleanupCompleted(daysOld); console.log(`[SyncService] Cleaned up ${deleted} old completed operations`); return deleted; } // ===== Playlist sync operations ===== async queuePlaylistCreate(playlistId: string, name: string, itemIds: string[]): Promise { return this.queueMutation("playlist_create", playlistId, { name, itemIds }); } async queuePlaylistDelete(playlistId: string): Promise { return this.queueMutation("playlist_delete", playlistId); } async queuePlaylistRename(playlistId: string, name: string): Promise { return this.queueMutation("playlist_rename", playlistId, { name }); } async queuePlaylistAddItems(playlistId: string, itemIds: string[]): Promise { return this.queueMutation("playlist_add_items", playlistId, { itemIds }); } async queuePlaylistRemoveItems(playlistId: string, entryIds: string[]): Promise { return this.queueMutation("playlist_remove_items", playlistId, { entryIds }); } async queuePlaylistReorderItem(playlistId: string, itemId: string, newIndex: number): Promise { return this.queueMutation("playlist_reorder_item", playlistId, { itemId, newIndex }); } /** * Clear all sync operations for the current user (called during logout) * * TRACES: UR-017 | DR-014 */ async clearUser(): Promise { const userId = auth.getUserId(); if (userId) { await commands.syncClearUser(userId); console.log("[SyncService] Cleared sync queue for user"); } } } // Export singleton instance export const syncService = new SyncService();