LCOV - code coverage report
Current view: top level - dashql-app/src/platform/hyperdb - hyperdb_provider_web.ts Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 118 118
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import type { Logger } from '../logger/logger.js';
       2              : import { stringifyError } from '../logger/logger.js';
       3              : import type { EmbeddedDatabaseOptions, SetupProgress } from '../database/embedded_database_provider.js';
       4              : 
       5              : import { HyperDB, HyperDBEngine, type HyperDBClient, type HyperDBSettings } from './hyperdb_wasm.js';
       6              : import {
       7              :     EXPERIMENTAL_WASM_BACKEND_SETTING,
       8              :     HYPERDB_WASM_ENGINE_SETTINGS,
       9              :     isUnknownHyperDBSettingError,
      10              : } from './hyperdb_settings.js';
      11              : import { cleanupStaleHyperDBOPFSInstances, createHyperDBOPFSInstance } from './hyperdb_opfs_instance.js';
      12              : import { getHyperDBWebSupportError } from './hyperdb_web_support.js';
      13              : 
      14              : declare global {
      15              :     var __DASHQL_TEST_HYPERDB_CLIENT__: ((
      16              :         onSetupProgress?: (progress: SetupProgress) => void,
      17              :     ) => Promise<HyperDBClient>) | undefined;
      18              :     var __DASHQL_TEST_HYPERDB_CLIENT_PROMISE__: Promise<HyperDBClient> | undefined;
      19              :     var __DASHQL_TEST_HYPERDB_CLIENT_CREATIONS__: number | undefined;
      20              :     var __DASHQL_TEST_HYPERDB_UNLOAD_INSTALLED__: boolean | undefined;
      21              :     var __DASHQL_TEST_HYPERDB_FACTORY__: ((
      22              :         context: string,
      23              :         logger: Logger,
      24              :         onSetupProgress?: (progress: SetupProgress) => void,
      25              :     ) => Promise<HyperDB>) | undefined;
      26              : }
      27              : 
      28              : // eslint-disable-next-line import/no-unresolved -- package asset resolved by Vite
      29              : import engineUrl from '@dashql/hyperdb-wasm-js?url';
      30              : // eslint-disable-next-line import/no-unresolved -- package asset resolved by Vite
      31              : import workerUrl from '@dashql/hyperdb-wasm-worker?url';
      32              : // eslint-disable-next-line import/no-unresolved -- package asset resolved by Vite
      33              : import workerBridgeUrl from './hyperdb_worker_bridge.js?url&no-inline';
      34              : // eslint-disable-next-line import/no-unresolved -- package asset resolved by Bazel/Vite alias
      35              : import wasmUrl from '@dashql/hyperdb-wasm?url';
      36              : 
      37            1 : const HYPERDB_ENGINE_URL = new URL(engineUrl as string, import.meta.url);
      38            1 : const HYPERDB_WORKER_URL = new URL(workerUrl as string, import.meta.url);
      39            1 : const HYPERDB_WASM_URL = new URL(wasmUrl as string, import.meta.url);
      40              : 
      41            1 : function createEngineScript(): { url: string; revoke: () => void } {
      42            1 :     const source = `self.HYPERDB_WASM_MODULE=self.Module??{};self.HYPERDB_WASM_MODULE.locateFile=(path,prefix)=>path==='hyperdb-wasm.wasm'?${JSON.stringify(HYPERDB_WASM_URL.href)}:prefix+path;importScripts(${JSON.stringify(HYPERDB_ENGINE_URL.href)});`;
      43            1 :     const url = URL.createObjectURL(new Blob([source], { type: 'application/javascript' }));
      44            1 :     return { url, revoke: () => URL.revokeObjectURL(url) };
      45            1 : }
      46              : 
      47              : /// The worker options of a browser engine client.
      48              : /// The engine script is an object URL that must be revoked once the engine terminated.
      49            1 : export function createWebHyperDBClientOptions(): { workerUrl: URL; engineUrl: string; wasmUrl: URL; revoke: () => void } {
      50            1 :     const supportError = getHyperDBWebSupportError();
      51            1 :     if (supportError != null) throw new Error(supportError);
      52            1 :     const engineScript = createEngineScript();
      53            1 :     const workerUrl = new URL(workerBridgeUrl as string, import.meta.url);
      54            1 :     workerUrl.searchParams.set('workerUrl', HYPERDB_WORKER_URL.href);
      55            1 :     return { workerUrl, engineUrl: engineScript.url, wasmUrl: HYPERDB_WASM_URL, revoke: engineScript.revoke };
      56            1 : }
      57              : 
      58              : /// Start a HyperDB engine in a browser worker.
      59            1 : async function createWebHyperDBEngine(
      60            1 :     settings: HyperDBSettings,
      61            1 :     onSetupProgress?: (progress: SetupProgress) => void,
      62            1 : ): Promise<HyperDBEngine> {
      63            1 :     const { revoke, ...options } = createWebHyperDBClientOptions();
      64            1 :     let engine: HyperDBEngine;
      65            1 :     try {
      66            1 :         engine = await HyperDBEngine.create({ ...options, onSetupProgress, settings });
      67            1 :     } catch (error) {
      68            1 :         revoke();
      69            1 :         throw error;
      70            1 :     }
      71            1 :     const terminate = engine.terminate.bind(engine);
      72            1 :     engine.terminate = async () => {
      73            1 :         try {
      74            1 :             await terminate();
      75            1 :         } finally {
      76            1 :             revoke();
      77            1 :         }
      78            1 :     };
      79            1 :     return engine;
      80            1 : }
      81              : 
      82              : /// Start a HyperDB engine in a browser worker, on the experimental wasm backend if requested.
      83              : /// A build of hyperdb-wasm without the backend rejects its setting, the engine then starts on the default backend.
      84            1 : async function createWebHyperDBEngineWithOptions(
      85            1 :     context: string,
      86            1 :     logger: Logger,
      87            1 :     settings: HyperDBSettings,
      88            1 :     options: EmbeddedDatabaseOptions,
      89            1 :     onSetupProgress?: (progress: SetupProgress) => void,
      90            1 : ): Promise<HyperDBEngine> {
      91            1 :     if (!options.experimentalWasmBackend) {
      92            1 :         return await createWebHyperDBEngine(settings, onSetupProgress);
      93              :     }
      94            1 :     try {
      95            1 :         const engine = await createWebHyperDBEngine({ ...settings, [EXPERIMENTAL_WASM_BACKEND_SETTING]: true }, onSetupProgress);
      96            1 :         logger.info('Started HyperDB on the experimental wasm backend', { context }, 'hyperdb');
      97            1 :         return engine;
      98            1 :     } catch (error) {
      99            1 :         if (!isUnknownHyperDBSettingError(error, EXPERIMENTAL_WASM_BACKEND_SETTING)) throw error;
     100            1 :         logger.warn('HyperDB has no experimental wasm backend, starting the default backend', {
     101            1 :             context,
     102            1 :             error: stringifyError(error),
     103            1 :         }, 'hyperdb');
     104            1 :         return await createWebHyperDBEngine(settings, onSetupProgress);
     105            1 :     }
     106            1 : }
     107              : 
     108            1 : export async function setupWebHyperDB(
     109            1 :     context: string,
     110            1 :     logger: Logger,
     111            1 :     onSetupProgress?: (progress: SetupProgress) => void,
     112            1 :     options: EmbeddedDatabaseOptions = {},
     113            1 : ): Promise<HyperDB> {
     114            1 :     if (globalThis.__DASHQL_TEST_HYPERDB_FACTORY__) {
     115            1 :         return await globalThis.__DASHQL_TEST_HYPERDB_FACTORY__(context, logger, onSetupProgress);
     116              :     }
     117            1 :     const initStart = performance.now();
     118            1 :     try {
     119            1 :         logger.info('Creating HyperDB WASM engine', { context }, 'hyperdb');
     120            1 :         await cleanupStaleHyperDBOPFSInstances();
     121            1 :         const instance = await createHyperDBOPFSInstance();
     122            1 :         let engine: HyperDBEngine | null = null;
     123            1 :         try {
     124            1 :             engine = await createWebHyperDBEngineWithOptions(
     125            1 :                 context,
     126            1 :                 logger,
     127            1 :                 { ...HYPERDB_WASM_ENGINE_SETTINGS, log_file_path: instance.logDirectoryPath },
     128            1 :                 options,
     129            1 :                 onSetupProgress,
     130            1 :             );
     131            1 :             const database = await HyperDB.create(engine, {
     132            1 :                 computeDatabasePath: instance.computeDatabasePath,
     133            1 :                 defaultDatabasePath: instance.userDatabasePath,
     134            1 :                 snippetsDatabasePath: instance.snippetsDatabasePath,
     135            1 :             });
     136            1 :             const terminate = database.terminate.bind(database);
     137            1 :             database.terminate = async () => {
     138            1 :                 await terminate();
     139            1 :                 await instance.dispose();
     140            1 :             };
     141              : 
     142            1 :             logger.info('Instantiated HyperDB WASM', {
     143            1 :                 context,
     144            1 :                 duration: Math.floor(performance.now() - initStart).toString(),
     145            1 :             }, 'hyperdb');
     146            1 :             return database;
     147            1 :         } catch (error) {
     148            1 :             try {
     149            1 :                 await engine?.terminate();
     150            1 :                 await instance.dispose();
     151            1 :             } catch (cleanupError) {
     152            1 :                 logger.warn('Cleaning up failed HyperDB setup failed', {
     153            1 :                     context,
     154            1 :                     error: stringifyError(cleanupError),
     155            1 :                 }, 'hyperdb');
     156            1 :             }
     157            1 :             throw error;
     158            1 :         }
     159            1 :     } catch (error) {
     160            1 :         logger.error('Instantiating HyperDB WASM failed', {
     161            1 :             context,
     162            1 :             error: stringifyError(error),
     163            1 :             duration: Math.floor(performance.now() - initStart).toString(),
     164            1 :         }, 'hyperdb');
     165            1 :         throw error;
     166            1 :     }
     167            1 : }
        

Generated by: LCOV version 2.0-1