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

            Line data    Source code
       1              : import * as dashql from '@ankoh/dashql-core';
       2              : import * as React from 'react';
       3              : 
       4              : import { useLogger } from '../platform/logger/logger_provider.js';
       5              : import { TracedLogger, stringifyError } from '../platform/logger/logger.js';
       6              : import { createTrace } from '../platform/logger/trace_context.js';
       7              : import { decodedContentLength } from '../utils/content_length.js';
       8              : import { disposeCatalogBatchWorker, initializeCatalogBatchWorker } from '../connections/catalog/catalog_batch.js';
       9              : import { CorePanicOverlay } from './core_panic_overlay.js';
      10              : 
      11              : // Load the WebAssembly asset through the core package export.
      12              : // eslint-disable-next-line import/no-unresolved -- resolved by bundler
      13              : import coreWasmUrl from '@ankoh/dashql-core/wasm?url';
      14            1 : const DASHQL_WASM_URL = typeof coreWasmUrl === 'string' ? coreWasmUrl : new URL(coreWasmUrl as string, import.meta.url).href;
      15              : 
      16              : export interface InstantiationProgress {
      17              :     startedAt: Date;
      18              :     updatedAt: Date;
      19              :     bytesTotal: bigint;
      20              :     bytesLoaded: bigint;
      21              : }
      22              : 
      23            1 : const INSTANTIATOR_CONTEXT = React.createContext<((context: string) => Promise<dashql.DashQL>) | null>(null);
      24            1 : const PROGRESS_CONTEXT = React.createContext<InstantiationProgress | null>(null);
      25              : 
      26              : export interface DashQLCoreSetupOptions {
      27              :     setupBatchWorker: boolean;
      28              : }
      29              : 
      30              : interface Props {
      31              :     children: React.ReactElement;
      32              :     initialSetup?: DashQLCoreSetupOptions;
      33              : }
      34              : 
      35            1 : export const DashQLCoreProvider: React.FC<Props> = (props: Props) => {
      36            1 :     const logger = useLogger();
      37            1 :     const instantiation = React.useRef<Promise<dashql.DashQL> | null>(null);
      38            1 :     const initialSetup = React.useRef(props.initialSetup);
      39            1 :     const lifecycle = React.useRef<{ disposed: boolean; core: dashql.DashQL | null }>({ disposed: false, core: null });
      40            1 :     const [progress, setProgress] = React.useState<InstantiationProgress | null>(null);
      41              : 
      42            1 :     const instantiator = React.useCallback(async (context: string): Promise<dashql.DashQL> => {
      43            1 :         const owner = lifecycle.current;
      44            1 :         if (owner.disposed) throw new DOMException('Core provider was disposed', 'AbortError');
      45              :         /// Already instantiated?
      46            1 :         if (instantiation.current != null) {
      47            1 :             return await instantiation.current;
      48              :         }
      49              : 
      50              :         // Create instantiation progress
      51            1 :         const now = new Date();
      52            1 :         const internal: InstantiationProgress = {
      53            1 :             startedAt: now,
      54            1 :             updatedAt: now,
      55            1 :             bytesTotal: BigInt(0),
      56            1 :             bytesLoaded: BigInt(0),
      57            1 :         };
      58              : 
      59              :         // Fetch an url with progress tracking (url is string from ?url import or URL)
      60            1 :         const fetchWithProgress = async (url: string | URL, traced: TracedLogger) => {
      61            1 :             traced.info("Fetching core wasm", { "context": context }, "core");
      62              : 
      63              :             // Try to determine file size
      64            1 :             const request = new Request(url);
      65            1 :             const response = await fetch(request);
      66            1 :             if (!response.ok) {
      67            1 :                 throw new Error(`Failed to fetch core wasm: ${response.status} ${response.statusText}`);
      68              :             }
      69            1 :             const contentLength = decodedContentLength(response);
      70              : 
      71            1 :             const now = new Date();
      72            1 :             internal.startedAt = now;
      73            1 :             internal.updatedAt = now;
      74            1 :             internal.bytesTotal = BigInt(contentLength) || BigInt(0);
      75            1 :             internal.bytesLoaded = BigInt(0);
      76              : 
      77              :             const tracker = {
      78            1 :                 transform(chunk: Uint8Array, ctrl: TransformStreamDefaultController) {
      79            1 :                     const prevUpdate = internal.updatedAt;
      80            1 :                     internal.updatedAt = new Date();
      81            1 :                     internal.bytesLoaded += BigInt(chunk.byteLength);
      82            1 :                     if (internal.updatedAt.getTime() - prevUpdate.getTime() > 20) {
      83            1 :                         setProgress(_ => ({ ...internal }));
      84              :                     }
      85            1 :                     ctrl.enqueue(chunk);
      86            1 :                 },
      87              :             };
      88            1 :             const ts = new TransformStream(tracker);
      89            1 :             return new Response(response.body?.pipeThrough(ts), response);
      90            1 :         };
      91              : 
      92            1 :         const instantiate = async (): Promise<dashql.DashQL> => {
      93            1 :             const traced = logger.withTrace(createTrace());
      94            1 :             const initStart = performance.now();
      95            1 :             try {
      96            1 :                 traced.info("Loading core Wasm", { "context": context }, "core");
      97            1 :                 const response = await fetchWithProgress(DASHQL_WASM_URL, traced);
      98            1 :                 const wasmBinary = new Uint8Array(await response.arrayBuffer());
      99            1 :                 const instance = await dashql.DashQL.create({ wasmBinary });
     100            1 :                 owner.core = instance;
     101            1 :                 if (owner.disposed) {
     102            1 :                     disposeCatalogBatchWorker(instance);
     103            1 :                     throw new DOMException('Core provider was disposed', 'AbortError');
     104            1 :                 }
     105            1 :                 if (initialSetup.current?.setupBatchWorker) {
     106            1 :                     try {
     107            1 :                         await initializeCatalogBatchWorker(instance);
     108            1 :                     } catch (error) {
     109            1 :                         if (owner.disposed) throw new DOMException('Core provider was disposed', 'AbortError');
     110            1 :                         disposeCatalogBatchWorker(instance);
     111            1 :                         throw error;
     112            1 :                     }
     113              :                 }
     114            1 :                 if (owner.disposed) throw new DOMException('Core provider was disposed', 'AbortError');
     115              : 
     116            1 :                 const initEnd = performance.now();
     117            1 :                 traced.info("Instantiated core", {
     118            1 :                     "context": context,
     119            1 :                     "duration": Math.floor(initEnd - initStart).toString()
     120            1 :                 }, "core");
     121              : 
     122            1 :                 setProgress(_ => ({
     123            1 :                     ...internal,
     124            1 :                     updatedAt: new Date(),
     125            1 :                 }));
     126              : 
     127            1 :                 return instance;
     128            1 :             } catch (e: any) {
     129            1 :                 const initEnd = performance.now();
     130            1 :                 traced.error("Failed to instantiate core", {
     131            1 :                     "error": stringifyError(e),
     132            1 :                     "duration": Math.floor(initEnd - initStart).toString()
     133            1 :                 }, "core");
     134            1 :                 throw e;
     135            1 :             }
     136            1 :         };
     137              :         // Start the instantiation
     138            1 :         instantiation.current = instantiate();
     139              :         // Await the instantiation
     140            1 :         return await instantiation.current;
     141              : 
     142            1 :     }, [logger, setProgress]);
     143              : 
     144            1 :     React.useEffect(() => {
     145            1 :         if (lifecycle.current.disposed) lifecycle.current = { disposed: false, core: null };
     146            1 :         const owner = lifecycle.current;
     147            1 :         return () => {
     148            1 :             owner.disposed = true;
     149            1 :             if (owner.core) disposeCatalogBatchWorker(owner.core);
     150            1 :             instantiation.current = null;
     151            1 :         };
     152            1 :     }, []);
     153              : 
     154            1 :     return (
     155            1 :         <INSTANTIATOR_CONTEXT.Provider value={instantiator}>
     156            1 :             <PROGRESS_CONTEXT.Provider value={progress}>
     157            1 :                 <>
     158            1 :                     {props.children}
     159            1 :                     <CorePanicOverlay />
     160            1 :                 </>
     161            1 :             </PROGRESS_CONTEXT.Provider>
     162            1 :         </INSTANTIATOR_CONTEXT.Provider>
     163              :     );
     164            1 : };
     165              : 
     166              : export const useDashQLCoreSetupProgress = (): InstantiationProgress | null => React.useContext(PROGRESS_CONTEXT);
     167              : 
     168              : export type DashQLSetupFn = (context: string) => Promise<dashql.DashQL>;
     169            1 : export function useDashQLCoreSetup(): DashQLSetupFn {
     170            1 :     return React.useContext(INSTANTIATOR_CONTEXT)!;
     171            1 : };
        

Generated by: LCOV version 2.0-1