LCOV - code coverage report
Current view: top level - dashql-app/src/connections/hyper - hyper_connection_setup.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 94.7 % 95 90
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : import * as connection from '@ankoh/dashql-jsonschema/connection.js';
       3              : 
       4              : import {
       5              :     HYPER_CHANNEL_READY,
       6              :     HYPER_CHANNEL_SETUP_CANCELLED,
       7              :     HYPER_CHANNEL_SETUP_FAILED,
       8              :     HYPER_CHANNEL_SETUP_STARTED,
       9              :     HyperConnectorAction,
      10              : } from './hyper_connection_state.js';
      11              : import { Logger, stringifyError } from '../../platform/logger/logger.js';
      12              : import { HyperConnectorConfig } from '../connector_configs.js';
      13              : import { Dispatch } from '../../utils/variant.js';
      14              : import {
      15              :     HyperDatabaseChannel,
      16              :     HyperDatabaseClient,
      17              : } from './hyperdb_grpc_client.js';
      18              : import { useLogger } from '../../platform/logger/logger_provider.js';
      19              : import { useAppConfig } from '../../config/app_config.js';
      20              : import { useHyperGrpcClient, useHyperHttpClient } from './hyperdb_grpc_client_provider.js';
      21              : import { RESET_CONNECTION } from '../connection_state.js';
      22              : import { useEmbeddedDatabaseSetup, type EmbeddedDatabaseSetupFn } from '../../platform/database/embedded_database_provider.js';
      23              : import { EmbeddedDatabaseChannel, EmbeddedHyperDatabaseChannel } from '../embedded/embedded_database_channel.js';
      24              : import { connectionClientContext, type HyperLogMetrics } from './hyper_log_metrics.js';
      25              : import { useHyperLogMetrics } from './hyper_log_metrics_provider.js';
      26              : import { HyperChannelDatabase } from './hyper_channel_database.js';
      27              : import { hyperConnectionContext } from './hyper_connection_context.js';
      28              : import { getHyperLinkTabState, hyperLinkChannelParams } from './hyper_link.js';
      29              : 
      30            1 : const LOG_CTX = "hyper_setup";
      31              : 
      32            1 : function resolveClient(protocol: connection.HyperProtocol, grpcClient: HyperDatabaseClient | null, httpClient: HyperDatabaseClient | null): HyperDatabaseClient {
      33            1 :     if (protocol === 'V3_HTTP') {
      34            1 :         if (!httpClient) throw new Error("HTTP client is not available");
      35            1 :         return httpClient;
      36            1 :     }
      37            1 :     if (!grpcClient) throw new Error(`gRPC client is not available (required for protocol ${protocol})`);
      38            1 :     return grpcClient;
      39            1 : }
      40              : 
      41            1 : export async function setupHyperConnection(updateState: Dispatch<HyperConnectorAction>, logger: Logger, params: connection.HyperConnectionParams, _config: HyperConnectorConfig, grpcClient: HyperDatabaseClient | null, httpClient: HyperDatabaseClient | null, setupEmbeddedDatabase: EmbeddedDatabaseSetupFn, abortSignal: AbortSignal, logMetrics: HyperLogMetrics | null = null, connectionId: string | null = null): Promise<HyperDatabaseChannel | null> {
      42            1 :     params = structuredClone(params);
      43            1 :     delete params.draftRows;
      44            1 :     let channel: HyperDatabaseChannel | null = null;
      45            1 :     try {
      46              :         // Start the channel setup
      47            1 :         updateState({
      48            1 :             type: HYPER_CHANNEL_SETUP_STARTED,
      49            1 :             value: params,
      50            1 :         });
      51            1 :         abortSignal.throwIfAborted()
      52              : 
      53            1 :         if (params.protocol === 'WASM') {
      54            1 :             const database = await setupEmbeddedDatabase('hyper_wasm_connector');
      55              :             // The Hyper log attributes the queries of the session to the connection by its client context
      56            1 :             const embeddedConnection = await database.connect({
      57            1 :                 defaultDatabase: 'hyper',
      58            1 :                 clientContext: connectionId == null ? undefined : connectionClientContext(connectionId),
      59            1 :             });
      60            1 :             if (connectionId != null) logMetrics?.watch({ database, sessions: true }, connectionId);
      61            1 :             channel = new EmbeddedHyperDatabaseChannel(new EmbeddedDatabaseChannel(embeddedConnection));
      62            1 :         } else {
      63              :             // The Hyper of a link is reached with the token of the link, it is never stored
      64            1 :             let connectParams = params;
      65            1 :             if (params.protocol === 'V3_HTTP_LINK') {
      66            1 :                 const token = connectionId == null ? undefined : getHyperLinkTabState().tokens[connectionId];
      67            1 :                 if (token == null) throw new Error("Open the link of this Hyper again, e.g. the one that `hyperd run --ui` prints");
      68            1 :                 connectParams = hyperLinkChannelParams(params, token);
      69            1 :             }
      70            1 :             const client = resolveClient(connectParams.protocol, grpcClient, httpClient);
      71              :             // The Hyper log attributes the queries of the channel to the connection by its client context
      72            1 :             channel = await client.connect(connectParams, hyperConnectionContext(connectParams, connectionId == null ? null : connectionClientContext(connectionId)));
      73              :             // Every query of a remote channel runs in a session of its own
      74            1 :             const database = new HyperChannelDatabase(clientContext => client.connect(connectParams, hyperConnectionContext(connectParams, clientContext)));
      75            1 :             if (connectionId != null) logMetrics?.watch({ database, sessions: false }, connectionId);
      76            1 :         }
      77            1 :         abortSignal.throwIfAborted();
      78              : 
      79              :         // Mark the channel as ready
      80            1 :         updateState({
      81            1 :             type: HYPER_CHANNEL_READY,
      82            1 :             value: [channel, params],
      83            1 :         });
      84            1 :         abortSignal.throwIfAborted();
      85              : 
      86            1 :     } catch (error: any) {
      87            1 :         await channel?.close();
      88            1 :         if (error.name === 'AbortError') {
      89            1 :             logger.warn("Cancelled setup", {}, LOG_CTX);
      90            1 :             updateState({
      91            1 :                 type: HYPER_CHANNEL_SETUP_CANCELLED,
      92            1 :                 value: {
      93            1 :                     message: stringifyError(error)
      94            1 :                 },
      95            1 :             });
      96            1 :         } else if (error instanceof Error) {
      97            1 :             logger.warn("Setup failed", { "error": error?.message }, LOG_CTX);
      98            1 :             updateState({
      99            1 :                 type: HYPER_CHANNEL_SETUP_FAILED,
     100            1 :                 value: {
     101            1 :                     message: error.message,
     102            1 :                 },
     103            1 :             });
     104            1 :         }
     105            1 :         throw error;
     106            1 :     }
     107            1 :     return channel;
     108            1 : }
     109              : export interface HyperSetupApi {
     110              :     setup(dispatch: Dispatch<HyperConnectorAction>, params: connection.HyperConnectionParams, abortSignal: AbortSignal, connectionId: string): Promise<HyperDatabaseChannel | null>
     111              :     reset(dispatch: Dispatch<HyperConnectorAction>): Promise<void>
     112              : }
     113              : 
     114            1 : export const SETUP_CTX = React.createContext<HyperSetupApi | null>(null);
     115            1 : export const useHyperSetup = () => React.useContext(SETUP_CTX!);
     116              : 
     117              : interface Props {
     118              :     /// The children
     119              :     children: React.ReactElement;
     120              : }
     121              : 
     122            1 : export const HyperSetupProvider: React.FC<Props> = (props: Props) => {
     123            1 :     const logger = useLogger();
     124            1 :     const appConfig = useAppConfig();
     125            1 :     const connectorConfig = appConfig?.connectors?.hyper ?? null;
     126            1 :     const grpcClient = useHyperGrpcClient();
     127            1 :     const httpClient = useHyperHttpClient();
     128            1 :     const setupEmbeddedDatabase = useEmbeddedDatabaseSetup();
     129            1 :     const logMetrics = useHyperLogMetrics();
     130              : 
     131            1 :     const api = React.useMemo<HyperSetupApi | null>(() => {
     132            1 :         if (!connectorConfig) {
     133            1 :             return null;
     134              :         }
     135            1 :         const setup = async (dispatch: Dispatch<HyperConnectorAction>, params: connection.HyperConnectionParams, abort: AbortSignal, connectionId: string) => {
     136            1 :             return await setupHyperConnection(dispatch, logger, params, connectorConfig, grpcClient, httpClient, setupEmbeddedDatabase, abort, logMetrics, connectionId);
     137            1 :         };
     138            1 :         const reset = async (dispatch: Dispatch<HyperConnectorAction>) => {
     139            0 :             dispatch({
     140            0 :                 type: RESET_CONNECTION,
     141            0 :                 value: null,
     142            0 :             });
     143            0 :         };
     144            1 :         return { setup, reset };
     145            1 :     }, [connectorConfig, grpcClient, httpClient, logger, setupEmbeddedDatabase, logMetrics]);
     146              : 
     147            1 :     return (
     148            1 :         <SETUP_CTX.Provider value={api} > {props.children} </SETUP_CTX.Provider>
     149              :     );
     150            1 : };
        

Generated by: LCOV version 2.0-1