Line data Source code
1 : import * as React from 'react';
2 :
3 : import type { EmbeddedComputeDatabase } from './embedded_database.js';
4 : import type { Logger } from '../logger/logger.js';
5 : import { useLogger } from '../logger/logger_provider.js';
6 : import { useAppHandOver } from '../crosstab/app_hand_over.js';
7 :
8 1 : const SETUP_CTX = React.createContext<EmbeddedDatabaseSetupFn | null>(null);
9 :
10 : export interface SetupProgress {
11 : readonly bytesLoaded: number;
12 : readonly bytesTotal: number;
13 : }
14 :
15 : /// The startup options of the embedded database, they apply when it is instantiated
16 : export interface EmbeddedDatabaseOptions {
17 : /// Run the queries on the experimental wasm backend of HyperDB, the default backend if the engine has none
18 : experimentalWasmBackend?: boolean;
19 : }
20 :
21 1 : const DEFAULT_OPTIONS: EmbeddedDatabaseOptions = {};
22 :
23 : interface Props {
24 : children: React.ReactElement;
25 : setupDatabase?: EmbeddedDatabaseFactory;
26 : /// The startup options, null while they are not known yet, e.g. before the app settings are loaded.
27 : /// The instantiation waits for them.
28 : options?: EmbeddedDatabaseOptions | null;
29 : }
30 :
31 : type EmbeddedDatabaseFactory = (
32 : context: string,
33 : logger: Logger,
34 : onSetupProgress?: (progress: SetupProgress) => void,
35 : options?: EmbeddedDatabaseOptions,
36 : ) => Promise<EmbeddedComputeDatabase>;
37 :
38 1 : const setupDefaultDatabase: EmbeddedDatabaseFactory = async (context, logger, onSetupProgress, options) => {
39 1 : const { setupWebHyperDB } = await import('../hyperdb/hyperdb_provider_web.js');
40 1 : return await setupWebHyperDB(context, logger, onSetupProgress, options);
41 1 : };
42 :
43 1 : export const EmbeddedDatabaseProvider: React.FC<Props> = (props: Props) => {
44 1 : const logger = useLogger();
45 1 : const instantiation = React.useRef<Promise<EmbeddedComputeDatabase> | null>(null);
46 :
47 : // The options of the first instantiation, the instantiation waits until they are known
48 1 : const [options, resolveOptions] = React.useMemo(() => {
49 1 : let resolve: (options: EmbeddedDatabaseOptions) => void;
50 1 : const promise = new Promise<EmbeddedDatabaseOptions>(r => { resolve = r; });
51 1 : return [promise, resolve!] as const;
52 1 : }, []);
53 1 : const knownOptions = props.options === undefined ? DEFAULT_OPTIONS : props.options;
54 1 : React.useEffect(() => {
55 1 : if (knownOptions != null) resolveOptions(knownOptions);
56 1 : }, [knownOptions, resolveOptions]);
57 :
58 1 : const setup = React.useCallback(async (
59 1 : context: string,
60 1 : onSetupProgress?: (progress: SetupProgress) => void,
61 1 : ): Promise<EmbeddedComputeDatabase> => {
62 1 : if (instantiation.current != null) {
63 1 : return await instantiation.current;
64 : }
65 :
66 1 : const instantiate = async (): Promise<EmbeddedComputeDatabase> =>
67 1 : (props.setupDatabase ?? setupDefaultDatabase)(context, logger, onSetupProgress, await options);
68 :
69 1 : instantiation.current = instantiate().catch(error => {
70 1 : instantiation.current = null;
71 1 : throw error;
72 1 : });
73 1 : return await instantiation.current;
74 1 : }, [logger, props.setupDatabase, options]);
75 :
76 : // Another tab takes over the app. The database closes the files it shares with the other tab, e.g.
77 : // the log extracts of the connections, before that tab opens them.
78 1 : useAppHandOver(async () => {
79 0 : const pending = instantiation.current;
80 0 : instantiation.current = null;
81 0 : if (pending) {
82 0 : const database = await pending.catch(() => null);
83 0 : await database?.terminate();
84 : }
85 1 : });
86 :
87 1 : React.useEffect(() => {
88 1 : return () => {
89 1 : const pending = instantiation.current;
90 1 : instantiation.current = null;
91 1 : if (pending) {
92 1 : pending
93 1 : .then(database => database.terminate())
94 1 : .catch(() => { /* instantiation failed - nothing to terminate */ });
95 : }
96 1 : };
97 1 : }, []);
98 :
99 1 : return (
100 1 : <SETUP_CTX.Provider value={setup}>
101 1 : {props.children}
102 1 : </SETUP_CTX.Provider>
103 : );
104 1 : };
105 :
106 : export type EmbeddedDatabaseSetupFn = (
107 : context: string,
108 : onSetupProgress?: (progress: SetupProgress) => void,
109 : ) => Promise<EmbeddedComputeDatabase>;
110 1 : export function useEmbeddedDatabaseSetup(): EmbeddedDatabaseSetupFn {
111 1 : return React.useContext(SETUP_CTX)!;
112 1 : }
|