MotherDuck is a managed DuckDB-in-the-cloud service.
DuckDB WASM brings DuckDB to every browser thanks to WebAssembly.
The MotherDuck WASM Client library enables using MotherDuck through DuckDB WASM in your own browser applications.
The @motherduckdb/wasm-client package is currently served from an NPM repository hosted on GitHub, not the main NPM repository on npmjs.com.
Before running npm install @motherduckdb/wasm-client
in your project, add the following to your .npmrc
file:
@motherduckdb:registry=https://npm.pkg.github.com
(If you don't already have an .npmrc
file, create one next to the package.json
file for your project.)
To faciliate efficient communication across worker threads, the MotherDuck WASM Client library currently uses advanced browser features, including SharedArrayBuffer.
Due to security requirements of modern browsers, these features require applications to be cross-origin isolated.
To use the MotherDuck WASM Client library, your application must be in cross-origin isolation mode, which is enabled when it is served with the following headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
You can check whether your application is in this mode by examining the crossOriginIsolated property in the browser console.
Note that applications in this mode are restricted in some ways. In particular, resources from different origins can only be loaded if they are served with a Cross-Origin-Resource-Policy (CORS) header with the value cross-origin
.
The MotherDuck WASM Client library is written in TypeScript and exposes full TypeScript type definitions. These instructions assume you are using it from TypeScript.
Once you have installed @motherduckdb/wasm-client
, you can import the main class, MDConnection
, as follows:
import { MDConnection } from '@motherduckdb/wasm-client';
To create a connection
to a MotherDuck-connected DuckDB instance, call the create
static method:
const connection = MDConnection.create({
mdToken: token
});
The mdToken
parameter is required and should be set to a valid MotherDuck service token. You can find your MotherDuck service token in the MotherDuck UI, under your user menu in the top right. Click "Settings", then find the "Service token" section. Copy the token using the button to the right of the (obscured) value.
The create
call returns immediately, but starts the process of loading the DuckDB WASM assets from https://app.motherduck.com
and starting the DuckDB WASM worker.
This initialization process happens asynchronously. Any query executed before initialization is complete will be queued.
To determine whether initialization is complete, call the isInitialized
method, which returns a promise resolving to true
when DuckDB WASM is initialized:
await connection.isInitialized();
Multiple connections can be created. Connections share a DuckDB WASM instance, so creating subsequent connections will not repeat the initialization process.
Queries executed on different connections happen concurrently; queries executed on the same connection are queued sequentially.
To execute a query, call the executeQuery
method on the connection
object:
try {
const result = await connection.evaluateQuery(sql);
console.log('query result', result);
} catch (err) {
console.log('query failed', err);
}
The executeQuery
method returns a promise for the result. In an async function, you can use the await
syntax as above. Or, you can use the then
and/or catch
methods:
connection.evaluateQuery(sql).then((result) => {
console.log('query result', result);
}).catch((reason) => {
console.log('query failed', reason);
});
To evaluate a prepared statement, call the evaluatePreparedStatement
method:
const result = await connection.evaluatePreparedStatement('SELECT v + ? FROM generate_series(0, 10000) AS t(v);', [234]);
To evalute a query that can be canceled, use the enqueueQuery
and evaluateQueuedQuery
methods:
const queryId = connection.enqueueQuery(sql);
const result = await connection.evaluateQueuedQuery(queryId);
To cancel a query evaluated in this fashion, use the cancelQuery
method, passing the queryId
returned by enqueueQuery
:
const queryWasCanceled = await connection.cancelQuery(queryId);
The cancelQuery
method returns a promise for a boolean indicating whether the query was successfully canceled.
The result promise of a canceled query will be rejected with and error message. The cancelQuery
method takes an optional second argument for controlling this message:
const queryWasCanceled = await connection.cancelQuery(queryId, 'custom error message');
The query result promises returned by evaluateQuery
, evaluatePreparedStatement
, and evaluateQueuedQuery
will be rejected in the case of an error.
For convenience, "safe" variants of these three method are provided that catch this error and always resolve to a value indicating success or failure. For example:
const result = await connection.saveEvaluateQuery(sql);
if (result.status === 'success') {
console.log('rows', result.rows);
} else {
console.log('error', result.err);
}
A successful query result contains a rows
property, which is an array of row objects.
Each row object has one property per column, named after that column. (Multiple columns with the same name are not currently supported.)
The type of each column property of a row object depends on the type of the corresponding column in DuckDB.
Many values are converted to a JavaScript primitive type, such as boolean
, number
, or string
.
Some numeric values too large to fit in a JavaScript number
(e.g a DuckDB BIGINT) are converted to a JavaScript bigint
.
Values may also be JavaScript arrays or objects, for nested types such as DuckDB LIST or MAP.
Some DuckDB types, such as DATE, TIME, TIMESTAMP, and DECIMAL, are converted to JavaScript objects implementing an interface specific to that type.
These objects all implement toString
to return a string representation identical to DuckDB's string conversion (e.g. using CAST to VARCHAR).
They also have properties exposing the underlying value. For example, the object for a DuckDB TIME has a microseconds
property (of type bigint
). See the TypeScript type definitions for details.
Note that these result types differ from those returned by DuckDB WASM without the MotherDuck WASM Client library. The MotherDuck WASM Client library implements custom conversion logic to preserve the full range of some types.