All files / src/lib/powersync status.svelte.ts

100% Statements 21/21
100% Branches 9/9
100% Functions 7/7
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92                    10x     10x         10x       10x             14x 14x 3x                             1x 1x                                               6x 1x     5x   4x 4x 4x 2x 2x 1x   1x         5x    
// Connection status reactive store using Svelte 5 runes.
// Tracks the PowerSync connection state: offline → syncing → online.
 
type ConnectionStatus = 'online' | 'syncing' | 'offline';
 
type StatusListener = (status: ConnectionStatus) => void;
 
// Internal subscriber list (for programmatic subscription outside Svelte components)
// Using plain Set, not SvelteSet — this is a module-level listener registry, not reactive UI state.
// eslint-disable-next-line svelte/prefer-svelte-reactivity
const listeners = new Set<StatusListener>();
 
// Reactive state object — readable from anywhere, updated via setConnectionStatus
export const connectionStatus = {
  // Svelte 5 $state cannot be exported directly as a top-level rune
  // outside a .svelte file. We expose a plain object and use a module-level
  // variable with a getter/setter pattern that consumers can $state-wrap locally.
  get value(): ConnectionStatus {
    return _status;
  },
};
 
let _status: ConnectionStatus = 'offline';
 
/**
 * Update the connection status. Called by the PowerSync status subscription
 * in the application layout and by tests.
 */
export function setConnectionStatus(status: ConnectionStatus): void {
  _status = status;
  for (const listener of listeners) {
    listener(status);
  }
}
 
/**
 * Subscribe to connection status changes.
 * Returns an unsubscribe function.
 *
 * Use inside Svelte components via $effect:
 *   $effect(() => {
 *     const unsub = subscribeToStatus((s) => (myStatus = s));
 *     return unsub;
 *   });
 */
export function subscribeToStatus(listener: StatusListener): () => void {
  listeners.add(listener);
  return () => listeners.delete(listener);
}
 
/**
 * Wire up PowerSync's built-in status observable to our reactive store.
 * Call this once after the database is connected (e.g. in the root layout).
 *
 * Uses the real PowerSync SDK API: `db.registerListener({ statusChanged })`.
 * The `statusChanged` callback receives a `SyncStatus` object with:
 *   - `connected: boolean`
 *   - `dataFlowStatus.downloading: boolean`
 *   - `dataFlowStatus.uploading: boolean`
 *
 * @param db - The PowerSyncDatabase instance (or any object with `registerListener`)
 * @returns A cleanup function to unregister the listener
 */
export function connectStatusToDB(db: {
  registerListener?: (listener: {
    statusChanged?: (status: {
      connected: boolean;
      dataFlowStatus: { downloading?: boolean; uploading?: boolean };
    }) => void;
  }) => () => void;
}): () => void {
  if (!db.registerListener) {
    return () => {};
  }
 
  const unregister = db.registerListener({
    statusChanged(status) {
      const { connected } = status;
      const { downloading, uploading } = status.dataFlowStatus;
      if (connected && (downloading || uploading)) {
        setConnectionStatus('syncing');
      } else if (connected) {
        setConnectionStatus('online');
      } else {
        setConnectionStatus('offline');
      }
    },
  });
 
  return unregister;
}