@duckdb/react-duckdb 1.11.1-dev.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 ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@duckdb/react-duckdb",
3
+ "version": "1.11.1-dev.0",
4
+ "description": "React components for DuckDB-Wasm",
5
+ "license": "MPL-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/duckdb/duckdb-wasm.git"
9
+ },
10
+ "keywords": [
11
+ "sql",
12
+ "duckdb",
13
+ "relational",
14
+ "database",
15
+ "data",
16
+ "query",
17
+ "wasm",
18
+ "analytics",
19
+ "olap",
20
+ "arrow",
21
+ "parquet",
22
+ "json",
23
+ "csv"
24
+ ],
25
+ "files": [
26
+ "src"
27
+ ],
28
+ "main": "src/index.ts",
29
+ "types": "src/index.ts"
30
+ }
@@ -0,0 +1,71 @@
1
+ import React from 'react';
2
+ import * as imm from 'immutable';
3
+ import * as duckdb from '@duckdb/duckdb-wasm';
4
+ import { useDuckDB, useDuckDBResolver } from './database_provider';
5
+ import { ResolvableStatus } from './resolvable';
6
+
7
+ type DialerFn = (id?: number) => void;
8
+
9
+ export const poolCtx = React.createContext<imm.Map<number, duckdb.AsyncDuckDBConnection>>(imm.Map());
10
+ export const dialerCtx = React.createContext<DialerFn | null>(null);
11
+
12
+ export const useDuckDBConnection = (id?: number): duckdb.AsyncDuckDBConnection | null =>
13
+ React.useContext(poolCtx)?.get(id || 0) || null;
14
+ export const useDuckDBConnectionDialer = (): DialerFn => React.useContext(dialerCtx)!;
15
+
16
+ type DuckDBConnectionProps = {
17
+ /// The children
18
+ children: React.ReactElement | React.ReactElement[];
19
+ /// The epoch
20
+ epoch?: number;
21
+ };
22
+
23
+ export const DuckDBConnectionProvider: React.FC<DuckDBConnectionProps> = (props: DuckDBConnectionProps) => {
24
+ const db = useDuckDB();
25
+ const resolveDB = useDuckDBResolver();
26
+ const [pending, setPending] = React.useState<imm.List<number>>(imm.List());
27
+ const [pool, setPool] = React.useState<imm.Map<number, duckdb.AsyncDuckDBConnection>>(imm.Map());
28
+
29
+ const inFlight = React.useRef<Map<number, boolean>>(new Map());
30
+
31
+ // Resolve request assuming that the database is ready
32
+ const dialer = async (id: number) => {
33
+ if (inFlight.current.get(id)) {
34
+ return;
35
+ }
36
+ const conn = await db!.value!.connect();
37
+ setPool(p => p.set(id, conn));
38
+ inFlight.current.delete(id);
39
+ };
40
+
41
+ // Resolve request or remember as pending
42
+ const dialerCallback = React.useCallback(
43
+ (id?: number) => {
44
+ if (db.value != null) {
45
+ dialer(id || 0);
46
+ } else if (!db.resolving()) {
47
+ resolveDB();
48
+ setPending(pending.push(id || 0));
49
+ }
50
+ },
51
+ [db],
52
+ );
53
+
54
+ // Process pending if possible
55
+ React.useEffect(() => {
56
+ if (db.value == null) {
57
+ return;
58
+ }
59
+ const claimed = pending;
60
+ setPending(imm.List());
61
+ for (const id of claimed) {
62
+ dialer(id);
63
+ }
64
+ }, [db, pending]);
65
+
66
+ return (
67
+ <dialerCtx.Provider value={dialerCallback}>
68
+ <poolCtx.Provider value={pool}>{props.children}</poolCtx.Provider>
69
+ </dialerCtx.Provider>
70
+ );
71
+ };
@@ -0,0 +1,87 @@
1
+ import React, { ReactElement } from 'react';
2
+ import * as duckdb from '@duckdb/duckdb-wasm';
3
+ import { useDuckDBLogger, useDuckDBBundle, useDuckDBBundleResolver } from './platform_provider';
4
+ import { Resolvable, Resolver, ResolvableStatus } from './resolvable';
5
+
6
+ const setupCtx = React.createContext<Resolvable<duckdb.AsyncDuckDB, duckdb.InstantiationProgress> | null>(null);
7
+ const resolverCtx = React.createContext<Resolver<duckdb.AsyncDuckDB> | null>(null);
8
+
9
+ export const useDuckDB = (): Resolvable<duckdb.AsyncDuckDB, duckdb.InstantiationProgress> =>
10
+ React.useContext(setupCtx)!;
11
+ export const useDuckDBResolver = (): Resolver<duckdb.AsyncDuckDB> => React.useContext(resolverCtx)!;
12
+
13
+ type DuckDBProps = {
14
+ children: React.ReactElement | ReactElement[];
15
+ config?: duckdb.DuckDBConfig;
16
+ value?: duckdb.AsyncDuckDB;
17
+ };
18
+
19
+ export const DuckDBProvider: React.FC<DuckDBProps> = (props: DuckDBProps) => {
20
+ const logger = useDuckDBLogger();
21
+ const resolveBundle = useDuckDBBundleResolver();
22
+ const [setup, updateSetup] = React.useState<Resolvable<duckdb.AsyncDuckDB, duckdb.InstantiationProgress>>(
23
+ new Resolvable<duckdb.AsyncDuckDB, duckdb.InstantiationProgress>(),
24
+ );
25
+
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
+ );
36
+
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
+ }
48
+
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
61
+ try {
62
+ await next.instantiate(bundle.mainModule, bundle.pthreadWorker, (p: duckdb.InstantiationProgress) => {
63
+ try {
64
+ updateSetup(s => s.updateRunning(p));
65
+ } catch (e: any) {
66
+ console.warn(`progress handler failed with error: ${e.toString()}`);
67
+ }
68
+ });
69
+ if (props.config !== undefined) {
70
+ await next.open(props.config!);
71
+ }
72
+ } catch (e: any) {
73
+ updateSetup(s => s.failWith(e));
74
+ return null;
75
+ }
76
+ updateSetup(s => s.completeWith(next));
77
+ return next;
78
+ })();
79
+ return await inFlight.current;
80
+ }, [logger]);
81
+
82
+ return (
83
+ <resolverCtx.Provider value={resolver}>
84
+ <setupCtx.Provider value={setup}>{props.children}</setupCtx.Provider>
85
+ </resolverCtx.Provider>
86
+ );
87
+ };
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './connection_provider';
2
+ export * from './database_provider';
3
+ export * from './platform_provider';
4
+ export * from './resolvable';
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import * as duckdb from '@duckdb/duckdb-wasm';
3
+ import { Resolvable, Resolver, ResolvableStatus } from './resolvable';
4
+
5
+ type PlatformProps = {
6
+ children: React.ReactElement | React.ReactElement[];
7
+ logger: duckdb.Logger;
8
+ bundles: duckdb.DuckDBBundles;
9
+ };
10
+
11
+ const loggerCtx = React.createContext<duckdb.Logger | null>(null);
12
+ const bundleCtx = React.createContext<Resolvable<duckdb.DuckDBBundle> | null>(null);
13
+ const resolverCtx = React.createContext<Resolver<duckdb.DuckDBBundle> | null>(null);
14
+ export const useDuckDBLogger = (): duckdb.Logger => React.useContext(loggerCtx)!;
15
+ export const useDuckDBBundle = (): Resolvable<duckdb.DuckDBBundle> => React.useContext(bundleCtx)!;
16
+ export const useDuckDBBundleResolver = (): Resolver<duckdb.DuckDBBundle> => React.useContext(resolverCtx)!;
17
+
18
+ export const DuckDBPlatform: React.FC<PlatformProps> = (props: PlatformProps) => {
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 () => {
27
+ try {
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;
35
+ console.error(e);
36
+ setBundle(b => b.failWith(e));
37
+ return null;
38
+ }
39
+ })();
40
+ return await inFlight.current;
41
+ }, [props.bundles]);
42
+
43
+ return (
44
+ <loggerCtx.Provider value={props.logger}>
45
+ <resolverCtx.Provider value={resolver}>
46
+ <bundleCtx.Provider value={bundle}>{props.children}</bundleCtx.Provider>
47
+ </resolverCtx.Provider>
48
+ </loggerCtx.Provider>
49
+ );
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
+ }