LCOV - code coverage report
Current view: top level - dashql-app/src/session/persistence - session_relocation.ts Coverage Total Hit
Test: DashQL 915828b Lines: 87.2 % 47 41
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import type { Logger } from '../../platform/logger/logger.js';
       2              : import type { CompositeStorageBackend } from '../../storage/composite_storage_backend.js';
       3              : import { StorageWriter } from '../../storage/storage_writer.js';
       4              : 
       5            1 : const LOG_CTX = 'session_relocation';
       6              : 
       7              : /// Relocate a single OPFS session into a native directory, from the UI (native app only).
       8              : ///
       9              : /// Prompts the user for a target directory, copies just this one session's files into it, verifies
      10              : /// the copy, records `location=native` in the OPFS root manifest (the registry entry stays in
      11              : /// OPFS), deletes the OPFS copy of the files, and reloads the app to re-run the tested storage init
      12              : /// + restore path against the new per-session layout.
      13              : ///
      14              : /// The OPFS copy is never deleted before the native copy is verified and the manifest is updated,
      15              : /// so any failure leaves the session fully intact on OPFS.
      16              : ///
      17              : /// Returns false if the user cancelled the folder picker; true after a successful relocate kicks off
      18              : /// the reload (the function does not return in practice on success).
      19            1 : export async function relocateSessionToNative(
      20            1 :     sessionId: string,
      21            1 :     backend: CompositeStorageBackend,
      22            1 :     writer: StorageWriter,
      23            1 :     logger: Logger,
      24            1 : ): Promise<boolean> {
      25              :     // 1. Pick a target directory. recursive:true so the picker's auto-grant also covers nested
      26              :     //    scripts/… paths (matters even though the boot path re-grants on reload).
      27            1 :     const folder = await globalThis.dashqlElectron?.openDirectory('Select a folder for this session');
      28            1 :     if (folder == null) {
      29              :         // User cancelled.
      30            1 :         return false;
      31              :     }
      32              : 
      33            1 :     try {
      34              :         // 2. Stop the debounced writer so OPFS isn't mutated mid-copy, then drain pending writes.
      35            1 :         writer.pause();
      36            1 :         await writer.flush();
      37              : 
      38              :         // 3. Copy + verify + flip the registry + delete the OPFS copy.
      39            1 :         await backend.relocateSessionToNative(sessionId, folder);
      40            0 :         logger.info('relocated session to native storage', { sessionId, target: folder }, LOG_CTX);
      41              : 
      42              :         // 4. Reload to re-run StorageProvider init + restoreAppState against the new layout.
      43            0 :         globalThis.location.reload();
      44            0 :         return true;
      45            1 :     } catch (e: any) {
      46              :         // Re-arm the writer so the app keeps working on OPFS, then surface the failure via the toast.
      47            1 :         writer.resume();
      48            1 :         logger.error('relocating session to native storage failed', {
      49            1 :             sessionId,
      50            1 :             target: folder,
      51            1 :             error: (e as Error)?.message ?? String(e),
      52            1 :         }, LOG_CTX);
      53            1 :         throw e;
      54            1 :     }
      55            1 : }
      56              : 
      57              : /// Add a pre-existing session from a folder on disk, from the UI (native app only).
      58              : ///
      59              : /// Prompts the user for a folder that already holds a dashql session (e.g. one created by a previous
      60              : /// run, or relocated to native storage), registers it in the OPFS root manifest as a native session,
      61              : /// and reloads the app so the tested storage init + restore path brings it into the registries.
      62              : ///
      63              : /// Nothing is copied or written into the folder — the folder is the source of truth and stays put.
      64              : /// On any failure (folder holds no session, invalid metadata, already registered) nothing is changed.
      65              : ///
      66              : /// Returns false if the user cancelled the folder picker; true after a successful load kicks off the
      67              : /// reload (the function does not return in practice on success).
      68            1 : export async function addNativeSessionFromFolder(
      69            1 :     backend: CompositeStorageBackend,
      70            1 :     logger: Logger,
      71            1 : ): Promise<boolean> {
      72              :     // recursive:true so the picker's auto-grant also covers the session's nested scripts/… paths.
      73            1 :     const folder = await globalThis.dashqlElectron?.openDirectory('Open an existing session folder');
      74            1 :     if (folder == null) {
      75              :         // User cancelled.
      76            1 :         return false;
      77              :     }
      78              : 
      79            1 :     try {
      80              :         // Loading only touches the OPFS manifest (no per-session writes the writer could race),
      81              :         // so unlike relocate there's no need to pause/flush the writer here.
      82            1 :         const sessionId = await backend.loadNativeSession(folder);
      83            0 :         logger.info('added native session from folder', { sessionId, folder }, LOG_CTX);
      84              : 
      85              :         // Reload to re-run StorageProvider init + restoreAppState, which loads the new session.
      86            0 :         globalThis.location.reload();
      87            0 :         return true;
      88            1 :     } catch (e: any) {
      89            1 :         logger.error('adding native session from folder failed', {
      90            1 :             folder,
      91            1 :             error: (e as Error)?.message ?? String(e),
      92            1 :         }, LOG_CTX);
      93            1 :         throw e;
      94            1 :     }
      95            1 : }
        

Generated by: LCOV version 2.0-1