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 : };
|