LCOV - code coverage report
Current view: top level - dashql-app/src/session - session_connection_replace.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 96.9 % 32 31
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : 
       3              : import { useDeleteConnection } from '../connections/connection_deletion.js';
       4              : import { useConnectionReferences, useConnectionRegistryStore } from '../connections/connection_registry.js';
       5              : import { useLogger } from '../platform/logger/logger_provider.js';
       6              : import { useStorageWriter } from '../storage/storage_provider.js';
       7              : import { REPLACE_CONNECTION } from './scripts/session_scripts.js';
       8              : import { useConnectionScriptsDispatch } from './scripts/session_scripts_registry.js';
       9              : 
      10            1 : const LOG_CTX = 'connection_replace';
      11              : 
      12              : /// The outcome of a replacement
      13              : export interface ConnectionReplacement {
      14              :     /// Was the replaced connection removed?
      15              :     removed: boolean;
      16              : }
      17              : 
      18              : /// Replace a connection with another one
      19              : export type ConnectionReplace = (sourceId: string, targetId: string) => Promise<ConnectionReplacement>;
      20              : 
      21              : /// Replace a connection with another one in every session.
      22              : ///
      23              : /// The replacement runs in steps that can be repeated, an interrupted replacement continues with the next one:
      24              : ///  1. The scripts and the pending connections of the sessions move to the other connection.
      25              : ///  2. The session files are written right away, they name the other connection then. A write that
      26              : ///     fails stays pending and is written again by the next replacement.
      27              : ///  3. The connection is removed once it is dead: no session uses it and every session file was written.
      28              : ///     It is shut down and deleted with its folder, see useDeleteConnection.
      29              : ///
      30              : /// A connection that is not removed stays in the registry and in the manifest, a session that still names
      31              : /// it after a restart moves with the next replacement.
      32            1 : export function useReplaceConnection(): ConnectionReplace {
      33            1 :     const registryStore = useConnectionRegistryStore();
      34            1 :     const references = useConnectionReferences();
      35            1 :     const scriptsDispatch = useConnectionScriptsDispatch();
      36            1 :     const storage = useStorageWriter();
      37            1 :     const deleteConnection = useDeleteConnection();
      38            1 :     const logger = useLogger();
      39              : 
      40            1 :     return React.useCallback(async (sourceId: string, targetId: string) => {
      41            1 :         const target = registryStore.current.connections.get(targetId);
      42            1 :         if (target == null || sourceId === targetId || !registryStore.current.connections.has(sourceId)) {
      43            0 :             return { removed: false };
      44              :         }
      45            1 :         const moving = references.sessionsOf(sourceId).length;
      46            1 :         await scriptsDispatch(sourceId, { type: REPLACE_CONNECTION, value: { from: sourceId, into: target } });
      47              : 
      48              :         // The sessions of the target include the sessions that moved before and whose files were not written then
      49            1 :         const sessionIds = new Set([...references.sessionsOf(sourceId), ...references.sessionsOf(targetId)]);
      50            1 :         const written = await Promise.all([...sessionIds].map(async sessionId => [sessionId, await storage.flushSession(sessionId)] as const));
      51            1 :         const unwritten = written.filter(([, ok]) => !ok).map(([sessionId]) => sessionId);
      52            1 :         const remaining = references.sessionsOf(sourceId);
      53            1 :         if (unwritten.length > 0 || remaining.length > 0) {
      54            1 :             logger.warn('kept the replaced connection', {
      55            1 :                 connectionId: sourceId,
      56            1 :                 target: targetId,
      57            1 :                 unwritten: unwritten.join(','),
      58            1 :                 remaining: remaining.join(','),
      59            1 :             }, LOG_CTX);
      60            1 :             return { removed: false };
      61            1 :         }
      62            1 :         await deleteConnection(sourceId);
      63            1 :         logger.info('replaced connection', { connectionId: sourceId, target: targetId, sessions: moving.toString() }, LOG_CTX);
      64            1 :         return { removed: !registryStore.current.connections.has(sourceId) };
      65            1 :     }, [deleteConnection, logger, references, registryStore, scriptsDispatch, storage]);
      66            1 : }
        

Generated by: LCOV version 2.0-1