What is DuckDB-Wasm Shell
main@duckdb/duckdb-wasm. It provides a command-line interface experience within a web environment, allowing users to interact with DuckDB directly in the browser. You can test the shell functionality at shell.duckdb.org.repository·main·Indexed 24 days ago
https://github.com/duckdb/duckdb-wasmA WebAssembly port of the DuckDB OLAP database engine for the browser and Node.js. It enables complex SQL queries on Parquet, CSV, and JSON files in client-side environments. The project includes the core Wasm library, a TypeScript API (@duckdb/duckdb-wasm), a Rust-based SQL shell (@duckdb/duckdb-wasm-shell), and React hooks (@duckdb/react-duckdb). Key features include support for Arrow IPC streams, lazy chunked results for large datasets, and a flexible extension system for core and community plugins.
@duckdb/duckdb-wasm. It provides a command-line interface experience within a web environment, allowing users to interact with DuckDB directly in the browser. You can test the shell functionality at shell.duckdb.org.DuckDB-Wasm is a WebAssembly port of DuckDB designed to run in browsers and Node.js. While it is based on DuckDB (currently v1.5.4), there are key behavioral differences to note:
LOAD httpfs in Wasm uses a JavaScript re-implementation. All requests are automatically upgraded to HTTPS, and servers must allow Cross-Origin (CORS) access.INSTALL extension_name FROM 'url'; defers fetching until the first LOAD extension_name; instruction. Shorthands like INSTALL x FROM community; are supported.LOAD icu;.To use DuckDB-Wasm, you must first instantiate an AsyncDuckDB instance. This process involves selecting an appropriate bundle (MVP or EH) based on browser capabilities, creating a Web Worker, and then calling .instantiate() on the AsyncDuckDB object.
Depending on your build tool (jsDelivr CDN, Webpack, Vite, or static serving), the method for loading the .wasm modules and worker scripts will vary. Always ensure you use a ConsoleLogger to monitor the database lifecycle.
import * as duckdb from '@duckdb/duckdb-wasm';
const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
// Select a bundle based on browser checks
const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
const worker_url = URL.createObjectURL(
new Blob([`importScripts("${bundle.mainWorker!}");`], {type: 'text/javascript'})
);
// Instantiate the asynchronous version of DuckDB-wasm
const worker = new Worker(worker_url);
const logger = new duckdb.ConsoleLogger();
const db = new duckdb.AsyncDuckDB(logger, worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
URL.revokeObjectURL(worker_url);DuckDB-Wasm supports importing data from various formats including Arrow, CSV, JSON, Parquet, and standard JavaScript arrays.
Common patterns include:
insertArrowTable for existing arrow.Table objects or insertArrowFromIPCStream for raw IPC streams.registerFileText or registerFileBuffer, then use insertCSVFromPath or insertJSONFromPath on a connection. You can provide typed options like schema, delimiter, and columns for precise control.registerFileHandle (for local File objects), registerFileURL (for HTTP), or registerFileBuffer (for Uint8Array).SELECT statement.Note: Always call connection.close() to release memory after data operations are complete.
// Data can be inserted from an existing arrow.Table
await c.insertArrowTable(existingTable, { name: 'arrow_table' });
// ..., from a raw Arrow IPC stream
const c = await db.connect();
const streamResponse = await fetch(`someapi`);
const streamReader = streamResponse.body.getReader();
const streamInserts = [];
while (true) {
const { value, done } = await streamReader.read();
if (done) break;
streamInserts.push(c.insertArrowFromIPCStream(value, { name: 'streamed' }));
}
await Promise.all(streamInserts);
// ..., from CSV files
await db.registerFileText(`data.csv`, '1|foo\n2|bar\n');
await c.insertCSVFromPath('data.csv', {
schema: 'main',
name: 'foo',
detect: false,
header: false,
delimiter: '|',
columns: {
col1: new arrow.Int32(),
col2: new arrow.Utf8(),
},
});
// ..., from Parquet files
const pickedFile: File = letUserPickFile();
await db.registerFileHandle('local.parquet', pickedFile, DuckDBDataProtocol.BROWSER_FILEREADER, true);
await db.registerFileURL('remote.parquet', 'https://origin/remote.parquet', DuckDBDataProtocol.HTTP, false);
// ..., by specifying URLs in the SQL text
await c.query(`
CREATE TABLE direct AS
SELECT * FROM "https://origin/remote.parquet"
`);
// Close the connection to release memory
await c.close();DuckDB-Wasm supports core, community, and external extensions. You can load them explicitly, via autoloading, or by registering them from specific repositories.
Explicit Loading
Use LOAD extension_name; to load an extension.
Autoloading
Many extensions (like parquet or json) are automatically loaded when you use relevant functions, such as read_parquet().
Registering from Repositories You can install extensions from specific URLs or community shorthands:
INSTALL extension_name FROM community;INSTALL extension_name FROM 'https://repository.endpoint.org';To verify which extensions are currently loaded, query the duckdb_extensions() function.
--- Explicitly load extensions
LOAD icu;
--- Or have them autoloaded when using relevant functions or settings
DESCRIBE FROM read_parquet('https://blobs.duckdb.org/stations.parquet');
--- Or register extensions
INSTALL h3 FROM community;
INSTALL sqlite_scanner FROM 'https://extensions.duckdb.org';
INSTALL quack FROM 'https://community-extensions.duckdb.org';
--- And then load them
LOAD h3;
LOAD sqlite_scanner;
LOAD quack;
--- Check loaded extensions
SELECT * FROM duckdb_extensions() WHERE loaded;To build the project from source, ensure you have the necessary build tools installed, then follow these steps to clone the repository, initialize submodules, apply patches, and start a local server.
git clone https://github.com/duckdb/duckdb-wasm.git
cd duckdb-wasm
git submodule init
git submodule update
make apply_patches
make serveThe @duckdb/react-duckdb package provides React Context providers and hooks to integrate DuckDB-Wasm into React applications. It exports several key modules for managing the DuckDB lifecycle, database connections, and platform-specific configurations:
connection_provider: Manages and provides DuckDB connections via React Context.database_provider: Manages the DuckDB database instance.platform_provider: Handles platform-specific setup (e.g., WebWorker vs Node.js).resolvable: Utilities for handling asynchronous or resolvable values within the React lifecycle.The DuckDBRuntime interface defines the low-level capabilities required to bridge the WASM module with the host environment (Browser or Node.js). It includes APIs for file management, directory operations, and UDF execution.
Key functional groups include:
openFile(mod, fileId, flags): Opens a file using its unique identifier.syncFile(mod, fileId): Synchronizes the file.closeFile(mod, fileId): Closes the file.readFile(mod, fileId, buffer, bytes, location): Reads data from a file into a buffer.writeFile(mod, fileId, buffer, bytes, location): Writes data from a buffer to a file.checkFile(mod, pathPtr, pathLen): Checks if a file exists at the given path.createDirectory(mod, pathPtr, pathLen): Creates a directory.listDirectoryEntries(mod, pathPtr, pathLen): Lists entries in a directory.removeFile(mod, pathPtr, pathLen): Removes a file.For files that require asynchronous acquisition (like network-based files), the runtime provides:
prepareFileHandle(path, protocol): Returns a Promise<PreparedDBFileHandle[]>.export interface DuckDBRuntime {
_files?: Map<string, any>;
_udfFunctions: Map<number, UDFFunction>;
testPlatformFeature(mod: DuckDBModule, feature: number): boolean;
getDefaultDataProtocol(mod: DuckDBModule): number;
openFile(mod: DuckDBModule, fileId: number, flags: FileFlags): void;
syncFile(mod: DuckDBModule, fileId: number): void;
closeFile(mod: DuckDBModule, fileId: number): void;
dropFile(mod: DuckDBModule, fileNamePtr: number, fileNameLen: number): void;
getLastFileModificationTime(mod: DuckDBModule, fileId: number): number;
truncateFile(mod: DuckDBModule, fileId: number, newSize: number): void;
readFile(mod: DuckDBModule, fileId: number, buffer: number, bytes: number, location: number): number;
writeFile(mod: DuckDBModule, fileId: number, buffer: number, bytes: number, location: number): number;
removeDirectory(mod: DuckDBModule, pathPtr: number, pathLen: number): void;
checkDirectory(mod: DuckDBModule, pathPtr: number, pathLen: number): boolean;
createDirectory(mod: DuckDBModule, pathPtr: number, pathLen: number): void;
listDirectoryEntries(mod: DuckDBModule, pathPtr: number, pathLen: number): boolean;
glob(mod: DuckDBModule, pathPtr: number, pathLen: number): void;
moveFile(mod: DuckDBModule, fromPtr: number, fromLen: number, toPtr: number, toLen: number): void;
checkFile(mod: DuckDBModule, pathPtr: number, pathLen: number): boolean;
removeFile(mod: DuckDBModule, pathPtr: number, pathLen: number): void;
prepareFileHandle?: (path: string, protocol: DuckDBDataProtocol) => Promise<PreparedDBFileHandle[]>;
prepareFileHandles?: (path: string[], protocol: DuckDBDataProtocol) => Promise<PreparedDBFileHandle[]>;
prepareDBFileHandle?: (path: string, protocol: DuckDBDataProtocol) => Promise<PreparedDBFileHandle[]>;
progressUpdate(final: number, percentage: number, iteration: number): void;
callScalarUDF(
mod: DuckDBModule,
response: number,
funcId: number,
descPtr: number,
descSize: number,
ptrsPtr: number,
ptrsSize: number,
): void;
}The WebDB class serves as the primary entry point for managing a DuckDB instance in a web environment, while the Connection class provides the interface for executing queries and managing data.
To use DuckDB-Wasm, you typically create a WebDB instance (or retrieve the static instance via Get()), open it using Open(), and then create a Connection using Connect(). All query execution and data manipulation occur through the Connection object.
Prepared statements allow you to pre-compile a SQL query and execute it multiple times with different parameters, which is more efficient and secure.
Workflow:
conn.prepare(sql)..query(params) for materialized results or .send(params) for chunked results..close() on the statement to release resources. Note that closing the parent connection will also release all associated statements.// Prepare query
const stmt = await conn.prepare(`SELECT v + ? FROM generate_series(0, 10000) as t(v);`);
// ... and run the query with materialized results
await stmt.query(234);
// ... or result chunks
for await (const batch of await stmt.send(234)) {
// ...
}
// Close the statement to release memory
await stmt.close();
// Closing the connection will release statements as well
await conn.close();You can execute SQL queries using a connection object in two ways:
conn.query<T>(sql) to fetch the entire result set at once. This is useful for smaller datasets where you want to work with the full result immediately.conn.send<T>(sql) to iterate over result chunks using an async iterator. This is more memory-efficient for large datasets as it processes data in batches.// Either materialize the query result
await conn.query<{ v: arrow.Int }>(`
SELECT * FROM generate_series(1, 100) t(v)
`);
// ..., or fetch the result chunks lazely
for await (const batch of await conn.send<{ v: arrow.Int }>(`
SELECT * FROM generate_series(1, 100) t(v)
`)) {
// ...
}
// Close the connection to release memory
await conn.close();When interacting with AWS S3 via DuckDB-Wasm, you can provide an S3Config object to specify connection details. This is used within DuckDBFileInfo or DuckDBGlobalFileInfo to manage S3-specific file access.
Available configuration keys:
region: The AWS region.endpoint: The S3 endpoint URL.accessKeyId: Your AWS access key ID.secretAccessKey: Your AWS secret access key.sessionToken: Your AWS session token.interface S3Config {
region?: string;
endpoint?: string;
accessKeyId?: string;
secretAccessKey?: string;
sessionToken?: string;
}