@ducklings/browser
v1.5.2
Published
Minimal DuckDB WASM for serverless environments
Maintainers
Readme
@ducklings/browser
Minimal DuckDB WASM for browsers. Async worker-based API with full TypeScript support.
Installation
npm install @ducklings/browserQuick Start
import { init, DuckDB } from '@ducklings/browser';
// Initialize the WASM module (runs in Web Worker)
await init();
// Create database and connection
const db = new DuckDB();
const conn = await db.connect();
// Query returns array of JS objects
const rows = await conn.query<{answer: number}>('SELECT 42 as answer');
console.log(rows); // [{ answer: 42 }]
// Clean up
await conn.close();
await db.close();Features
- Async API - queries run in Web Worker, UI stays responsive
- ~6.3 MiB gzipped WASM
- Built-in Parquet and httpfs extensions
- Arrow Table support via Flechette (query + insert)
- Prepared statements with type-safe parameter binding
- Streaming results for large datasets
- Transaction support
- File registration (URL, buffer, text)
Extension Availability
The published browser package does not bundle DuckDB's json extension so the bundled WASM stays under Cloudflare Pages' 25 MiB per-file limit.
JSON functions such as json_extract(...), the ::JSON type alias, and file readers like read_json() are therefore not available in the standard @ducklings/browser package. Use a custom build if you need DuckDB's json extension.
API
Initialization
import { init, DuckDB, version } from '@ducklings/browser';
// Auto-locate WASM and worker files
await init();
// Custom URLs (optional)
await init({
wasmUrl: '/path/to/duckdb.wasm',
workerUrl: '/path/to/worker.js'
});
// Create database
const db = new DuckDB();
// Get DuckDB version
const v = await version(); // "v1.4.3"CDN Usage
Load directly from jsDelivr or unpkg - cross-origin workers are handled automatically:
<script type="module">
import { init, DuckDB } from 'https://cdn.jsdelivr.net/npm/@ducklings/[email protected]/+esm';
await init();
const db = new DuckDB();
const conn = await db.connect();
const result = await conn.query('SELECT 42 as answer');
console.log(result); // [{ answer: 42 }]
await conn.close();
await db.close();
</script>Bundler Usage (Vite / Webpack)
When using a bundler, import asset URLs explicitly instead of relying on auto-resolution:
Vite:
import { init, DuckDB } from '@ducklings/browser';
import wasmUrl from '@ducklings/browser/wasm/duckdb.wasm?url';
import wasmJsUrl from '@ducklings/browser/wasm/duckdb.js?url';
import workerUrl from '@ducklings/browser/worker?url';
await init({ wasmUrl, wasmJsUrl, workerUrl });Webpack:
import { init, DuckDB } from '@ducklings/browser';
const wasmUrl = new URL('@ducklings/browser/wasm/duckdb.wasm', import.meta.url).href;
const wasmJsUrl = new URL('@ducklings/browser/wasm/duckdb.js', import.meta.url).href;
const workerUrl = new URL('@ducklings/browser/worker', import.meta.url).href;
await init({ wasmUrl, wasmJsUrl, workerUrl });Query Methods
const conn = await db.connect();
// Returns array of objects
const rows = await conn.query<{id: number, name: string}>('SELECT * FROM users');
// Returns Arrow Table (Flechette)
const table = await conn.queryArrow('SELECT * FROM users');
// Execute without returning results
await conn.execute('INSERT INTO users VALUES (1, "Alice")');
// Insert Arrow IPC data into a table
const ipc = tableToIPC(arrowTable, { format: 'stream' });
await conn.insertArrowFromIPCStream('my_table', ipc);Prepared Statements
const stmt = await conn.prepare('SELECT * FROM users WHERE id = ? AND active = ?');
stmt.bindInt32(1, 42); // Bind methods are sync
stmt.bindBoolean(2, true);
const results = await stmt.run(); // Execution is async
await stmt.close();Streaming Results
const stream = await conn.queryStreaming('SELECT * FROM large_table');
for await (const chunk of stream) {
console.log(`Processing ${chunk.rowCount} rows`);
for (const row of chunk.toArray()) {
processRow(row);
}
}
// Stream auto-closes after iterationFile Registration
// Register remote file
await db.registerFileURL('data.parquet', 'https://example.com/data.parquet');
// Register in-memory data
const csvData = new TextEncoder().encode('id,name\n1,Alice');
await db.registerFileBuffer('data.csv', csvData);
// Query registered files
const rows = await conn.query("SELECT * FROM 'data.parquet'");Remote Files (httpfs)
// Query remote Parquet file directly
const rows = await conn.query(`
SELECT * FROM 'https://example.com/data.parquet'
LIMIT 10
`);Arrow Support
import { tableFromArrays, tableFromIPC, tableToIPC, utf8 } from '@ducklings/browser';
// Query as Arrow Table
const table = await conn.queryArrow('SELECT * FROM users');
// Build Arrow tables
const custom = tableFromArrays({
id: [1, 2, 3],
name: ['Alice', 'Bob', 'Charlie']
});
// Serialize to/from Arrow IPC
const bytes = tableToIPC(table, { format: 'stream' });
const restored = tableFromIPC(bytes);
// Insert Arrow IPC data directly into a table
const data = tableFromArrays(
{ id: [1, 2], label: ['x', 'y'] },
{ types: { label: utf8() } } // Use plain utf8 (see note below)
);
const ipc = tableToIPC(data, { format: 'stream' });
await conn.insertArrowFromIPCStream('my_table', ipc);Dictionary encoding: Flechette's
tableFromArrays()defaults todictionary(utf8())for string columns. The Arrow IPC decoder used internally does not support dictionary-encoded streams. When building tables forinsertArrowFromIPCStream(), explicitly set string columns toutf8():import { utf8 } from '@ducklings/browser'; tableFromArrays({ col: ['a', 'b'] }, { types: { col: utf8() } });
Cloudflare Workers
For Cloudflare Workers, use the @ducklings/workers package instead, which uses Asyncify for proper httpfs support in the Workers runtime.
Limitations
- No dynamic extension loading: Only statically compiled extensions (Parquet, httpfs, Avro, Iceberg) are available in the default build.
INSTALL/LOADcommands for other extensions will not work.
License
MIT
