a very good jj gui
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Sprint 3: remove dead LiveStore infrastructure (265 LOC)

-265
-85
apps/desktop/src/hooks/useTauriLiveStoreSync.ts
··· 1 - /** 2 - * Tauri → BroadcastChannel Relay 3 - * 4 - * Relays Tauri events to the worker's sync backend via BroadcastChannel. 5 - * The sync backend handles everything else (LiveStore processing, materialization). 6 - * 7 - * Flow: 8 - * 1. Rust backend commits to SQLite + emits livestore:event 9 - * 2. This relay forwards event via BroadcastChannel 10 - * 3. Worker sync backend receives and processes through LiveStore 11 - * 4. Materialized views update → React queries re-render 12 - */ 13 - 14 - import { Channel, invoke } from "@tauri-apps/api/core"; 15 - import { listen, type UnlistenFn } from "@tauri-apps/api/event"; 16 - import { useEffect } from "react"; 17 - import { 18 - TAURI_SYNC_CHANNEL, 19 - TAURI_SYNC_REQUEST, 20 - type PersistedEvent, 21 - } from "../livestore/tauri-adapter"; 22 - 23 - const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; 24 - 25 - export function useTauriLiveStoreSync(): void { 26 - useEffect(() => { 27 - if (!isTauri) { 28 - return; 29 - } 30 - 31 - let mounted = true; 32 - let unlistenEvent: UnlistenFn | null = null; 33 - 34 - // Channel to relay events to worker 35 - const syncChannel = new BroadcastChannel(TAURI_SYNC_CHANNEL); 36 - 37 - // Handle hydration requests from worker sync backend 38 - const requestChannel = new BroadcastChannel(TAURI_SYNC_REQUEST); 39 - requestChannel.onmessage = async (event) => { 40 - if (!mounted || event.data.type !== "hydrate") return; 41 - 42 - const { afterSequence, storeId } = event.data; 43 - 44 - try { 45 - const channel = new Channel<PersistedEvent | { done: true }>(); 46 - 47 - channel.onmessage = (msg) => { 48 - if (!mounted) return; 49 - if (!("done" in msg)) { 50 - syncChannel.postMessage(msg); 51 - } 52 - }; 53 - 54 - await invoke("plugin:livestore|stream_events", { 55 - storeId, 56 - channel, 57 - afterSequence, 58 - }); 59 - } catch { 60 - // Hydration failed - silently ignore 61 - } 62 - }; 63 - 64 - // Relay Tauri events to worker via BroadcastChannel 65 - async function setup() { 66 - try { 67 - unlistenEvent = await listen<PersistedEvent>("livestore:event", (event) => { 68 - if (!mounted) return; 69 - syncChannel.postMessage(event.payload); 70 - }); 71 - } catch { 72 - // Setup failed - silently ignore 73 - } 74 - } 75 - 76 - setup(); 77 - 78 - return () => { 79 - mounted = false; 80 - unlistenEvent?.(); 81 - requestChannel.close(); 82 - syncChannel.close(); 83 - }; 84 - }, []); 85 - }
-180
apps/desktop/src/livestore/tauri-adapter.ts
··· 1 - import { Channel, invoke } from "@tauri-apps/api/core"; 2 - import { listen, type UnlistenFn } from "@tauri-apps/api/event"; 3 - 4 - // ============================================================================ 5 - // Types 6 - // ============================================================================ 7 - 8 - export interface PersistedEvent { 9 - id: string; 10 - sequence: number; 11 - name: string; 12 - payload: string; 13 - timestamp: number; 14 - clientId: string; 15 - } 16 - 17 - // ============================================================================ 18 - // Channel names for cross-window sync 19 - // ============================================================================ 20 - 21 - export const TAURI_SYNC_CHANNEL = "tauri-livestore-sync"; 22 - export const TAURI_SYNC_REQUEST = "tauri-livestore-request"; 23 - 24 - interface TauriAdapterConfig { 25 - storeId?: string; 26 - batchSize?: number; 27 - flushDebounceMs?: number; 28 - } 29 - 30 - // ============================================================================ 31 - // LiveStore Event Types (simplified interface matching LiveStore's needs) 32 - // ============================================================================ 33 - 34 - interface StoredEvent { 35 - id: string; 36 - sequence: number; 37 - name: string; 38 - payload: unknown; 39 - timestamp: number; 40 - clientId: string; 41 - } 42 - 43 - interface SyncStatus { 44 - pendingEvents: number; 45 - lastPersistedSequence: number; 46 - isOnline: boolean; 47 - } 48 - 49 - // ============================================================================ 50 - // Tauri Sync Adapter 51 - // ============================================================================ 52 - 53 - /** 54 - * TauriSyncAdapter bridges LiveStore's event log to Rust SQLite. 55 - * 56 - * Key design: 57 - * - Events are the unit of sync (not queries, not tables) 58 - * - Writes are batched and debounced for efficiency 59 - * - Hydration streams events via Tauri Channel for backpressure 60 - * - No queries cross the IPC boundary 61 - */ 62 - export function createTauriAdapter(config: TauriAdapterConfig = {}) { 63 - const { storeId = "default", batchSize = 100, flushDebounceMs = 50 } = config; 64 - 65 - let pendingEvents: StoredEvent[] = []; 66 - let flushTimeout: ReturnType<typeof setTimeout> | null = null; 67 - let lastPersistedSequence = 0; 68 - let unlistenFn: UnlistenFn | null = null; 69 - const clientId = crypto.randomUUID(); 70 - 71 - const adapter = { 72 - async init(): Promise<{ lastPersistedSequence: number }> { 73 - await invoke("init_event_store", { storeId }); 74 - lastPersistedSequence = await invoke<number>("get_last_sequence", { storeId }); 75 - return { lastPersistedSequence }; 76 - }, 77 - 78 - async loadEvents(onEvent: (event: StoredEvent) => void, onComplete: () => void): Promise<void> { 79 - const channel = new Channel<PersistedEvent | { done: true }>(); 80 - 81 - channel.onmessage = (message) => { 82 - if ("done" in message) { 83 - onComplete(); 84 - } else { 85 - onEvent({ 86 - id: message.id, 87 - sequence: message.sequence, 88 - name: message.name, 89 - payload: JSON.parse(message.payload), 90 - timestamp: message.timestamp, 91 - clientId: message.clientId, 92 - }); 93 - } 94 - }; 95 - 96 - await invoke("stream_events", { storeId, channel, afterSequence: 0 }); 97 - }, 98 - 99 - onEvent(event: StoredEvent): void { 100 - pendingEvents.push(event); 101 - 102 - if (flushTimeout) clearTimeout(flushTimeout); 103 - 104 - if (pendingEvents.length >= batchSize) { 105 - adapter.flush(); 106 - } else { 107 - flushTimeout = setTimeout(() => adapter.flush(), flushDebounceMs); 108 - } 109 - }, 110 - 111 - async flush(): Promise<void> { 112 - if (pendingEvents.length === 0) return; 113 - 114 - const eventsToFlush = pendingEvents; 115 - pendingEvents = []; 116 - 117 - if (flushTimeout) { 118 - clearTimeout(flushTimeout); 119 - flushTimeout = null; 120 - } 121 - 122 - try { 123 - const serialized = eventsToFlush.map((e) => ({ 124 - id: e.id, 125 - sequence: e.sequence, 126 - name: e.name, 127 - payload: JSON.stringify(e.payload), 128 - timestamp: e.timestamp, 129 - clientId, 130 - })); 131 - 132 - await invoke("persist_events", { storeId, events: serialized }); 133 - lastPersistedSequence = eventsToFlush[eventsToFlush.length - 1].sequence; 134 - } catch (error) { 135 - // Re-add failed events to front of queue 136 - pendingEvents = [...eventsToFlush, ...pendingEvents]; 137 - throw error; 138 - } 139 - }, 140 - 141 - async subscribe(onExternalEvent: (event: StoredEvent) => void): Promise<void> { 142 - unlistenFn = await listen<PersistedEvent>("livestore:event", (event) => { 143 - // Ignore events from this client 144 - if (event.payload.clientId === clientId) return; 145 - 146 - onExternalEvent({ 147 - id: event.payload.id, 148 - sequence: event.payload.sequence, 149 - name: event.payload.name, 150 - payload: JSON.parse(event.payload.payload), 151 - timestamp: event.payload.timestamp, 152 - clientId: event.payload.clientId, 153 - }); 154 - }); 155 - }, 156 - 157 - getStatus(): SyncStatus { 158 - return { 159 - pendingEvents: pendingEvents.length, 160 - lastPersistedSequence, 161 - isOnline: true, 162 - }; 163 - }, 164 - 165 - async dispose(): Promise<void> { 166 - await adapter.flush(); 167 - if (unlistenFn) { 168 - unlistenFn(); 169 - unlistenFn = null; 170 - } 171 - }, 172 - 173 - clientId, 174 - storeId, 175 - }; 176 - 177 - return adapter; 178 - } 179 - 180 - export type TauriAdapter = ReturnType<typeof createTauriAdapter>;