@duckdb/react-duckdb 1.13.1-dev260.0 → 1.13.1-dev273.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-dev273.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,62 +1,67 @@
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
- const bundle = useDuckDBBundle();
24
- const [db, setDb] = React.useState<duckdb.AsyncDuckDB | null>(null);
25
- const [status, setStatus] = React.useState<DuckDBStatus | null>(null);
21
+ const resolveBundle = useDuckDBBundleResolver();
22
+ const [setup, updateSetup] = React.useState<Resolvable<duckdb.AsyncDuckDB, duckdb.InstantiationProgress>>(
23
+ new Resolvable<duckdb.AsyncDuckDB, duckdb.InstantiationProgress>(),
24
+ );
26
25
 
27
- // Reinitialize the worker and the database when the bundle changes
28
- const lock = React.useRef<boolean>(false);
29
- React.useEffect(() => {
30
- // No bundle available?
31
- if (!bundle) return;
32
- // Is updating?
33
- if (lock.current) return;
34
- lock.current = true;
26
+ const worker = React.useRef<Worker | null>(null);
27
+ React.useEffect(
28
+ () => () => {
29
+ if (worker.current != null) {
30
+ worker.current.terminate();
31
+ worker.current = null;
32
+ }
33
+ },
34
+ [],
35
+ );
35
36
 
36
- // Create worker and next database
37
- let worker: Worker;
38
- let next: duckdb.AsyncDuckDB;
39
- try {
40
- worker = new Worker(bundle.mainWorker!);
41
- next = new duckdb.AsyncDuckDB(logger, worker);
42
- } catch (e) {
43
- lock.current = false;
44
- setStatus({
45
- instantiationProgress: null,
46
- instantiationError: e,
47
- });
48
- return;
49
- }
37
+ const inFlight = React.useRef<Promise<duckdb.AsyncDuckDB | null> | null>(null);
38
+ const resolver = React.useCallback(async () => {
39
+ // Run only once
40
+ if (inFlight.current) return await inFlight.current;
41
+ inFlight.current = (async () => {
42
+ // Resolve bundle
43
+ const bundle = await resolveBundle();
44
+ if (bundle == null) {
45
+ updateSetup(s => s.failWith('invalid bundle'));
46
+ return null;
47
+ }
50
48
 
51
- // Instantiate the database asynchronously
52
- const init = async () => {
49
+ // Create worker and next database
50
+ let worker: Worker;
51
+ let next: duckdb.AsyncDuckDB;
52
+ try {
53
+ worker = new Worker(bundle.mainWorker!);
54
+ next = new duckdb.AsyncDuckDB(logger, worker);
55
+ } catch (e: any) {
56
+ updateSetup(s => s.failWith(e));
57
+ return null;
58
+ }
59
+
60
+ // Instantiate the database asynchronously
53
61
  try {
54
62
  await next.instantiate(bundle.mainModule, bundle.pthreadWorker, (p: duckdb.InstantiationProgress) => {
55
63
  try {
56
- setStatus({
57
- instantiationProgress: p,
58
- instantiationError: null,
59
- });
64
+ updateSetup(s => s.updateRunning(p));
60
65
  } catch (e: any) {
61
66
  console.warn(`progress handler failed with error: ${e.toString()}`);
62
67
  }
@@ -64,27 +69,19 @@ export const DuckDBProvider: React.FC<DuckDBProps> = (props: DuckDBProps) => {
64
69
  if (props.config !== undefined) {
65
70
  await next.open(props.config!);
66
71
  }
67
- } catch (e) {
68
- lock.current = false;
69
- setStatus({
70
- instantiationProgress: null,
71
- instantiationError: e,
72
- });
73
- return;
72
+ } catch (e: any) {
73
+ updateSetup(s => s.failWith(e));
74
+ return null;
74
75
  }
75
- lock.current = false;
76
- setDb(next);
77
- };
78
- init();
79
- // Terminate the worker on destruction
80
- return () => {
81
- worker.terminate();
82
- };
83
- }, [logger, bundle]);
76
+ updateSetup(s => s.completeWith(next));
77
+ return next;
78
+ })();
79
+ return await inFlight.current;
80
+ }, [logger]);
84
81
 
85
82
  return (
86
- <dbCtx.Provider value={db}>
87
- <statusCtx.Provider value={status}>{props.children}</statusCtx.Provider>
88
- </dbCtx.Provider>
83
+ <resolverCtx.Provider value={resolver}>
84
+ <setupCtx.Provider value={setup}>{props.children}</setupCtx.Provider>
85
+ </resolverCtx.Provider>
89
86
  );
90
87
  };
package/src/index.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  export * from './connection_provider';
2
2
  export * from './database_provider';
3
3
  export * from './platform_provider';
4
- export * from './table_schema';
5
- export * from './table_schema_provider';
6
- export * from './epoch_contexts';
4
+ 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,42 @@ 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);
20
- const lock = React.useRef<boolean>(false);
21
- React.useEffect(() => {
22
- if (!launched || bundle != null || lock.current) return;
23
- lock.current = true;
24
- (async () => {
19
+ const [bundle, setBundle] = React.useState<Resolvable<duckdb.DuckDBBundle>>(new Resolvable());
20
+
21
+ const inFlight = React.useRef<Promise<duckdb.DuckDBBundle | null> | null>(null);
22
+ const resolver = React.useCallback(async () => {
23
+ if (bundle.error) return null;
24
+ if (bundle.value) return bundle.value;
25
+ if (inFlight.current) return await inFlight.current;
26
+ inFlight.current = (async () => {
25
27
  try {
26
- const b = await duckdb.selectBundle(props.bundles);
27
- setBundle(b);
28
- } catch (e) {
28
+ setBundle(b => b.updateRunning());
29
+ const next = await duckdb.selectBundle(props.bundles);
30
+ inFlight.current = null;
31
+ setBundle(b => b.completeWith(next));
32
+ return next;
33
+ } catch (e: any) {
34
+ inFlight.current = null;
29
35
  console.error(e);
36
+ setBundle(b => b.failWith(e));
37
+ return null;
30
38
  }
31
- lock.current = false;
32
39
  })();
33
- }, [launched, props.bundles]);
34
- const launcher = React.useCallback(() => setLaunched(true), [setLaunched]);
40
+ return await inFlight.current;
41
+ }, [props.bundles]);
42
+
35
43
  return (
36
44
  <loggerCtx.Provider value={props.logger}>
37
- <launcherCtx.Provider value={launcher}>
45
+ <resolverCtx.Provider value={resolver}>
38
46
  <bundleCtx.Provider value={bundle}>{props.children}</bundleCtx.Provider>
39
- </launcherCtx.Provider>
47
+ </resolverCtx.Provider>
40
48
  </loggerCtx.Provider>
41
49
  );
42
50
  };
@@ -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
+ }
@@ -1,7 +0,0 @@
1
- import React from 'react';
2
-
3
- export const TABLE_SCHEMA_EPOCH = React.createContext<number | null>(null);
4
- export const useTableSchemaEpoch = (): number | null => React.useContext(TABLE_SCHEMA_EPOCH);
5
-
6
- export const TABLE_DATA_EPOCH = React.createContext<number | null>(null);
7
- export const useTableDataEpoch = (): number | null => React.useContext(TABLE_DATA_EPOCH);
@@ -1,117 +0,0 @@
1
- import * as arrow from 'apache-arrow';
2
- import * as duckdb from '@duckdb/duckdb-wasm';
3
-
4
- /// A column group
5
- export interface TableSchemaColumnGroup {
6
- /// The group title
7
- title: string;
8
- /// The begin of the column span
9
- spanBegin: number;
10
- /// The size of the column span
11
- spanSize: number;
12
- }
13
-
14
- /// A table metadatStorea
15
- export interface TableSchema {
16
- /// The table schema
17
- readonly tableSchema: string;
18
- /// The table name
19
- readonly tableName: string;
20
-
21
- /// The column names
22
- readonly columnNames: string[];
23
- /// The column name indices
24
- readonly columnNameMapping: Map<string, number>;
25
- /// The column types
26
- readonly columnTypes: arrow.DataType[];
27
- /// The number of data columns.
28
- /// Allows to append compute metadata columns that are not rendered in the table viewer.
29
- readonly dataColumns: number;
30
-
31
- /// The column aliases (if any)
32
- readonly columnAliases: (string | null)[];
33
- /// The column grouping sets (if any)
34
- readonly columnGroupingSets: TableSchemaColumnGroup[][];
35
- /// The row grouping sets (if any)
36
- readonly rowGroupingSets: number[][];
37
- }
38
-
39
- /// Get raw qualified name
40
- export function getQualifiedNameRaw(schema: string, name: string) {
41
- return `${schema || 'main'}.${name}`;
42
- }
43
- /// Get qualified name
44
- export function getQualifiedName(table: TableSchema) {
45
- return `${table.tableSchema}.${table.tableName}`;
46
- }
47
-
48
- /// Collect table info
49
- export async function collectTableSchema(
50
- conn: duckdb.AsyncDuckDBConnection,
51
- info: Partial<TableSchema> & { tableSchema?: string; tableName: string },
52
- ): Promise<TableSchema> {
53
- // Use DESCRIBE to find all column types
54
- const columnNames: string[] = [];
55
- const columnNameMapping: Map<string, number> = new Map();
56
- const columnTypes: arrow.DataType[] = [];
57
- const describe = await conn.query<{ Field: arrow.Utf8; Type: arrow.Utf8 }>(
58
- `DESCRIBE ${info.tableSchema || 'main'}.${info.tableName}`,
59
- );
60
- let column = 0;
61
- for (const row of describe) {
62
- columnNames.push(row!.Field!);
63
- columnNameMapping.set(row!.Field!, column++);
64
- const mapType = (type: string): arrow.DataType => {
65
- switch (type) {
66
- case 'BOOLEAN':
67
- return new arrow.Bool();
68
- case 'TINYINT':
69
- return new arrow.Int8();
70
- case 'SMALLINT':
71
- return new arrow.Int16();
72
- case 'INTEGER':
73
- return new arrow.Int32();
74
- case 'BIGINT':
75
- return new arrow.Int64();
76
- case 'UTINYINT':
77
- return new arrow.Uint8();
78
- case 'USMALLINT':
79
- return new arrow.Uint16();
80
- case 'UINTEGER':
81
- return new arrow.Uint32();
82
- case 'UBIGINT':
83
- return new arrow.Uint64();
84
- case 'FLOAT':
85
- return new arrow.Float32();
86
- case 'HUGEINT':
87
- return new arrow.Decimal(32, 0);
88
- case 'DOUBLE':
89
- return new arrow.Float64();
90
- case 'VARCHAR':
91
- return new arrow.Utf8();
92
- case 'DATE':
93
- return new arrow.DateDay();
94
- case 'TIME':
95
- return new arrow.Time(arrow.TimeUnit.MILLISECOND, 32);
96
- case 'TIMESTAMP':
97
- return new arrow.TimeNanosecond();
98
- default:
99
- return new arrow.Null();
100
- }
101
- };
102
- columnTypes.push(mapType(row!.Type!));
103
- }
104
- const table: TableSchema = {
105
- ...info,
106
- tableSchema: info.tableSchema || 'main',
107
- columnNames,
108
- columnTypes,
109
- dataColumns: columnTypes.length,
110
- columnNameMapping,
111
- columnAliases: [],
112
- columnGroupingSets: [],
113
- rowGroupingSets: [],
114
- };
115
-
116
- return table;
117
- }
@@ -1,70 +0,0 @@
1
- import * as React from 'react';
2
- import { TableSchema, collectTableSchema } from './table_schema';
3
- import { useTableSchemaEpoch } from './epoch_contexts';
4
- import { useDuckDBConnection } from './connection_provider';
5
-
6
- interface Props {
7
- /// The children
8
- children: React.ReactElement | React.ReactElement[];
9
- /// The schema
10
- schema?: string;
11
- /// The name
12
- name: string;
13
- }
14
-
15
- interface State {
16
- /// The schema
17
- schema: string | null;
18
- /// The name
19
- name: string | null;
20
- /// The metadata
21
- metadata: TableSchema | null;
22
- /// The own epoch
23
- ownEpoch: number | null;
24
- }
25
-
26
- export const TABLE_METADATA = React.createContext<TableSchema | null>(null);
27
- export const useTableSchema = (): TableSchema | null => React.useContext(TABLE_METADATA);
28
-
29
- export const DuckDBTableSchemaProvider: React.FC<Props> = (props: Props) => {
30
- const conn = useDuckDBConnection();
31
- const epoch = useTableSchemaEpoch();
32
- const [state, setState] = React.useState<State>({
33
- schema: null,
34
- name: null,
35
- ownEpoch: epoch,
36
- metadata: null,
37
- });
38
- const inFlight = React.useRef<boolean>(false);
39
-
40
- // Detect unmount
41
- const isMounted = React.useRef(true);
42
- React.useEffect(() => {
43
- return () => void (isMounted.current = false);
44
- }, []);
45
-
46
- // Resolve the metadata
47
- React.useEffect(() => {
48
- if (!isMounted.current || !conn || inFlight.current) {
49
- return;
50
- }
51
- inFlight.current = true;
52
- const resolve = async (schema: string, name: string, epoch: number | null) => {
53
- const metadata = await collectTableSchema(conn!, {
54
- tableSchema: schema,
55
- tableName: name,
56
- });
57
- inFlight.current = false;
58
- if (!isMounted.current) return;
59
- setState({
60
- schema,
61
- name,
62
- ownEpoch: epoch,
63
- metadata,
64
- });
65
- };
66
- resolve(props.schema || 'main', props.name, epoch).catch(e => console.error(e));
67
- }, [conn, props.schema, props.name, epoch]);
68
-
69
- return <TABLE_METADATA.Provider value={state.metadata}>{props.children}</TABLE_METADATA.Provider>;
70
- };