@duckdb/react-duckdb 1.13.1-dev260.0 → 1.13.1-dev266.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckdb/react-duckdb",
3
- "version": "1.13.1-dev260.0",
3
+ "version": "1.13.1-dev266.0",
4
4
  "description": "React components for DuckDB-Wasm",
5
5
  "license": "MPL-2.0",
6
6
  "repository": {
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import * as imm from 'immutable';
3
3
  import * as duckdb from '@duckdb/duckdb-wasm';
4
- import { useDuckDB } from './database_provider';
5
- import { useDuckDBLauncher } from './platform_provider';
4
+ import { useDuckDB, useDuckDBResolver } from './database_provider';
5
+ import { ResolvableStatus } from './resolvable';
6
6
 
7
7
  type DialerFn = (id?: number) => void;
8
8
 
@@ -22,8 +22,7 @@ type DuckDBConnectionProps = {
22
22
 
23
23
  export const DuckDBConnectionProvider: React.FC<DuckDBConnectionProps> = (props: DuckDBConnectionProps) => {
24
24
  const db = useDuckDB();
25
- const launchDB = useDuckDBLauncher();
26
- const launched = React.useRef<boolean>();
25
+ const resolveDB = useDuckDBResolver();
27
26
  const [pending, setPending] = React.useState<imm.List<number>>(imm.List());
28
27
  const [pool, setPool] = React.useState<imm.Map<number, duckdb.AsyncDuckDBConnection>>(imm.Map());
29
28
 
@@ -34,7 +33,7 @@ export const DuckDBConnectionProvider: React.FC<DuckDBConnectionProps> = (props:
34
33
  if (inFlight.current.get(id)) {
35
34
  return;
36
35
  }
37
- const conn = await db!.connect();
36
+ const conn = await db!.value!.connect();
38
37
  setPool(p => p.set(id, conn));
39
38
  inFlight.current.delete(id);
40
39
  };
@@ -42,13 +41,10 @@ export const DuckDBConnectionProvider: React.FC<DuckDBConnectionProps> = (props:
42
41
  // Resolve request or remember as pending
43
42
  const dialerCallback = React.useCallback(
44
43
  (id?: number) => {
45
- if (db != null) {
44
+ if (db.value != null) {
46
45
  dialer(id || 0);
47
- } else {
48
- if (!launched.current) {
49
- launched.current = true;
50
- launchDB();
51
- }
46
+ } else if (!db.resolving()) {
47
+ resolveDB();
52
48
  setPending(pending.push(id || 0));
53
49
  }
54
50
  },
@@ -57,7 +53,7 @@ export const DuckDBConnectionProvider: React.FC<DuckDBConnectionProps> = (props:
57
53
 
58
54
  // Process pending if possible
59
55
  React.useEffect(() => {
60
- if (!db) {
56
+ if (db.value == null) {
61
57
  return;
62
58
  }
63
59
  const claimed = pending;
@@ -1,90 +1,95 @@
1
1
  import React, { ReactElement } from 'react';
2
2
  import * as duckdb from '@duckdb/duckdb-wasm';
3
- import { useDuckDBLogger, useDuckDBBundle } from './platform_provider';
3
+ import { useDuckDBLogger, useDuckDBBundle, useDuckDBBundleResolver } from './platform_provider';
4
+ import { Resolvable, Resolver, ResolvableStatus } from './resolvable';
4
5
 
5
- export interface DuckDBStatus {
6
- instantiationProgress: duckdb.InstantiationProgress | null;
7
- instantiationError: any | null;
8
- }
6
+ const setupCtx = React.createContext<Resolvable<duckdb.AsyncDuckDB, duckdb.InstantiationProgress> | null>(null);
7
+ const resolverCtx = React.createContext<Resolver<duckdb.AsyncDuckDB> | null>(null);
9
8
 
10
- const dbCtx = React.createContext<duckdb.AsyncDuckDB | null>(null);
11
- const statusCtx = React.createContext<DuckDBStatus | null>(null);
12
-
13
- export const useDuckDB = (): duckdb.AsyncDuckDB | null => React.useContext(dbCtx);
14
- export const useDuckDBStatus = (): DuckDBStatus | null => React.useContext(statusCtx);
9
+ export const useDuckDB = (): Resolvable<duckdb.AsyncDuckDB, duckdb.InstantiationProgress> =>
10
+ React.useContext(setupCtx)!;
11
+ export const useDuckDBResolver = (): Resolver<duckdb.AsyncDuckDB> => React.useContext(resolverCtx)!;
15
12
 
16
13
  type DuckDBProps = {
17
14
  children: React.ReactElement | ReactElement[];
18
15
  config?: duckdb.DuckDBConfig;
16
+ value?: duckdb.AsyncDuckDB;
19
17
  };
20
18
 
21
19
  export const DuckDBProvider: React.FC<DuckDBProps> = (props: DuckDBProps) => {
22
20
  const logger = useDuckDBLogger();
23
21
  const bundle = useDuckDBBundle();
24
- const [db, setDb] = React.useState<duckdb.AsyncDuckDB | null>(null);
25
- const [status, setStatus] = React.useState<DuckDBStatus | null>(null);
22
+ const resolveBundle = useDuckDBBundleResolver();
23
+ const [setup, updateSetup] = React.useState<Resolvable<duckdb.AsyncDuckDB, duckdb.InstantiationProgress>>(
24
+ new Resolvable<duckdb.AsyncDuckDB, duckdb.InstantiationProgress>(),
25
+ );
26
26
 
27
- // Reinitialize the worker and the database when the bundle changes
28
- const lock = React.useRef<boolean>(false);
29
27
  React.useEffect(() => {
30
- // No bundle available?
31
- if (!bundle) return;
28
+ if (!bundle.resolving()) {
29
+ resolveBundle();
30
+ }
31
+ }, [bundle]);
32
+
33
+ const worker = React.useRef<Worker | null>(null);
34
+ React.useEffect(
35
+ () => () => {
36
+ if (worker.current != null) {
37
+ worker.current.terminate();
38
+ worker.current = null;
39
+ }
40
+ },
41
+ [],
42
+ );
43
+
44
+ const lock = React.useRef<boolean>(false);
45
+ const resolver = React.useCallback(async () => {
46
+ // Invalid input?
47
+ if (!logger || !bundle || bundle.value == null) return null;
32
48
  // Is updating?
33
- if (lock.current) return;
49
+ if (lock.current) return null;
34
50
  lock.current = true;
35
51
 
36
52
  // Create worker and next database
37
53
  let worker: Worker;
38
54
  let next: duckdb.AsyncDuckDB;
39
55
  try {
40
- worker = new Worker(bundle.mainWorker!);
56
+ worker = new Worker(bundle.value.mainWorker!);
41
57
  next = new duckdb.AsyncDuckDB(logger, worker);
42
- } catch (e) {
58
+ } catch (e: any) {
43
59
  lock.current = false;
44
- setStatus({
45
- instantiationProgress: null,
46
- instantiationError: e,
47
- });
48
- return;
60
+ updateSetup(s => s.failWith(e));
61
+ return null;
49
62
  }
50
63
 
51
64
  // Instantiate the database asynchronously
52
- const init = async () => {
53
- try {
54
- await next.instantiate(bundle.mainModule, bundle.pthreadWorker, (p: duckdb.InstantiationProgress) => {
65
+ try {
66
+ await next.instantiate(
67
+ bundle.value.mainModule,
68
+ bundle.value.pthreadWorker,
69
+ (p: duckdb.InstantiationProgress) => {
55
70
  try {
56
- setStatus({
57
- instantiationProgress: p,
58
- instantiationError: null,
59
- });
71
+ updateSetup(s => s.updateRunning(p));
60
72
  } catch (e: any) {
61
73
  console.warn(`progress handler failed with error: ${e.toString()}`);
62
74
  }
63
- });
64
- if (props.config !== undefined) {
65
- await next.open(props.config!);
66
- }
67
- } catch (e) {
68
- lock.current = false;
69
- setStatus({
70
- instantiationProgress: null,
71
- instantiationError: e,
72
- });
73
- return;
75
+ },
76
+ );
77
+ if (props.config !== undefined) {
78
+ await next.open(props.config!);
74
79
  }
80
+ } catch (e: any) {
75
81
  lock.current = false;
76
- setDb(next);
77
- };
78
- init();
79
- // Terminate the worker on destruction
80
- return () => {
81
- worker.terminate();
82
- };
82
+ updateSetup(s => s.failWith(e));
83
+ return null;
84
+ }
85
+ lock.current = false;
86
+ updateSetup(s => s.completeWith(next));
87
+ return next;
83
88
  }, [logger, bundle]);
84
89
 
85
90
  return (
86
- <dbCtx.Provider value={db}>
87
- <statusCtx.Provider value={status}>{props.children}</statusCtx.Provider>
88
- </dbCtx.Provider>
91
+ <resolverCtx.Provider value={resolver}>
92
+ <setupCtx.Provider value={setup}>{props.children}</setupCtx.Provider>
93
+ </resolverCtx.Provider>
89
94
  );
90
95
  };
package/src/index.ts CHANGED
@@ -4,3 +4,4 @@ export * from './platform_provider';
4
4
  export * from './table_schema';
5
5
  export * from './table_schema_provider';
6
6
  export * from './epoch_contexts';
7
+ export * from './resolvable';
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import * as duckdb from '@duckdb/duckdb-wasm';
3
+ import { Resolvable, Resolver, ResolvableStatus } from './resolvable';
3
4
 
4
5
  type PlatformProps = {
5
6
  children: React.ReactElement | React.ReactElement[];
@@ -8,35 +9,38 @@ type PlatformProps = {
8
9
  };
9
10
 
10
11
  const loggerCtx = React.createContext<duckdb.Logger | null>(null);
11
- const bundleCtx = React.createContext<duckdb.DuckDBBundle | null>(null);
12
- const launcherCtx = React.createContext<(() => void) | null>(null);
13
- export const useDuckDBLauncher = (): (() => void) => React.useContext(launcherCtx)!;
12
+ const bundleCtx = React.createContext<Resolvable<duckdb.DuckDBBundle> | null>(null);
13
+ const resolverCtx = React.createContext<Resolver<duckdb.DuckDBBundle> | null>(null);
14
14
  export const useDuckDBLogger = (): duckdb.Logger => React.useContext(loggerCtx)!;
15
- export const useDuckDBBundle = (): duckdb.DuckDBBundle => React.useContext(bundleCtx)!;
15
+ export const useDuckDBBundle = (): Resolvable<duckdb.DuckDBBundle> => React.useContext(bundleCtx)!;
16
+ export const useDuckDBBundleResolver = (): Resolver<duckdb.DuckDBBundle> => React.useContext(resolverCtx)!;
16
17
 
17
18
  export const DuckDBPlatform: React.FC<PlatformProps> = (props: PlatformProps) => {
18
- const [bundle, setBundle] = React.useState<duckdb.DuckDBBundle | null>(null);
19
- const [launched, setLaunched] = React.useState<boolean>(false);
19
+ const [bundle, setBundle] = React.useState<Resolvable<duckdb.DuckDBBundle>>(new Resolvable());
20
+
20
21
  const lock = React.useRef<boolean>(false);
21
- React.useEffect(() => {
22
- if (!launched || bundle != null || lock.current) return;
22
+ const resolver = React.useCallback(async () => {
23
+ if (lock.current) return null;
23
24
  lock.current = true;
24
- (async () => {
25
- try {
26
- const b = await duckdb.selectBundle(props.bundles);
27
- setBundle(b);
28
- } catch (e) {
29
- console.error(e);
30
- }
25
+ try {
26
+ setBundle(b => b.updateRunning());
27
+ const next = await duckdb.selectBundle(props.bundles);
28
+ lock.current = false;
29
+ setBundle(b => b.completeWith(next));
30
+ return next;
31
+ } catch (e: any) {
31
32
  lock.current = false;
32
- })();
33
- }, [launched, props.bundles]);
34
- const launcher = React.useCallback(() => setLaunched(true), [setLaunched]);
33
+ console.error(e);
34
+ setBundle(b => b.failWith(e));
35
+ return null;
36
+ }
37
+ }, [props.bundles]);
38
+
35
39
  return (
36
40
  <loggerCtx.Provider value={props.logger}>
37
- <launcherCtx.Provider value={launcher}>
41
+ <resolverCtx.Provider value={resolver}>
38
42
  <bundleCtx.Provider value={bundle}>{props.children}</bundleCtx.Provider>
39
- </launcherCtx.Provider>
43
+ </resolverCtx.Provider>
40
44
  </loggerCtx.Provider>
41
45
  );
42
46
  };
@@ -0,0 +1,35 @@
1
+ export enum ResolvableStatus {
2
+ NONE,
3
+ RUNNING,
4
+ FAILED,
5
+ COMPLETED,
6
+ }
7
+
8
+ export type Resolver<Value> = () => Promise<Value | null>;
9
+
10
+ export class Resolvable<Value, Progress = null, Error = string> {
11
+ public readonly status: ResolvableStatus;
12
+ public readonly value: Value | null;
13
+ public readonly error: Error | null;
14
+ public readonly progress: Progress | null;
15
+
16
+ constructor(status?: ResolvableStatus, value?: Value | null, error?: Error | null, progress?: Progress | null) {
17
+ this.status = status || ResolvableStatus.NONE;
18
+ this.value = value || null;
19
+ this.error = error || null;
20
+ this.progress = progress || null;
21
+ }
22
+
23
+ public resolving(): boolean {
24
+ return this.status != ResolvableStatus.NONE;
25
+ }
26
+ public completeWith(value: Value, progress: Progress | null = null): Resolvable<Value, Progress, Error> {
27
+ return new Resolvable(ResolvableStatus.COMPLETED, value, this.error, progress);
28
+ }
29
+ public failWith(error: Error, progress: Progress | null = null): Resolvable<Value, Progress, Error> {
30
+ return new Resolvable(ResolvableStatus.FAILED, this.value, error, progress);
31
+ }
32
+ public updateRunning(progress: Progress | null = null): Resolvable<Value, Progress, Error> {
33
+ return new Resolvable(ResolvableStatus.RUNNING, this.value, this.error, progress);
34
+ }
35
+ }