Appearance
@hpcc-js/wasm-duckdb
A WebAssembly wrapper for DuckDB, an in-process SQL OLAP database management system. This package provides a consistent loading experience with the rest of the @hpcc-js/wasm library.
Features
- ✅ In-process SQL database (no server required)
- ✅ OLAP-optimized for analytical queries
- ✅ Full SQL support with DuckDB's rich feature set
- ✅ Prepared statements with parameter binding
- ✅ Transaction management (commit/rollback)
- ✅ Virtual file system for CSV, JSON, and other formats
- ✅ JSON export of query results
- ✅ Type-safe value handling
Installation
sh
npm install @hpcc-js/wasm-duckdbQuick Start
typescript
import { DuckDB } from "@hpcc-js/wasm-duckdb";
// Load the WASM module
const duckdb = await DuckDB.load();
// Create a connection
const connection = duckdb.connect();
// Execute a query
const result = connection.query("SELECT 'Hello, DuckDB!' AS message");
console.log(result.getValue(0, 0)); // "Hello, DuckDB!"
// Clean up
result.delete();
connection.delete();API Reference
DuckDB Class
Static Methods
DuckDB.load(): Promise<DuckDB>
Loads and initializes the DuckDB WASM module. Returns a singleton instance.
typescript
const duckdb = await DuckDB.load();DuckDB.unload(): void
Unloads the WASM instance (useful for cleanup in tests).
Instance Methods
version(): string
Returns the DuckDB version string.
typescript
console.log(duckdb.version()); // e.g., "v1.4.3"numberOfThreads(): number
Returns the number of threads available.
typescript
console.log(duckdb.numberOfThreads()); // e.g., 1connect(): Connection
Creates a new database connection.
typescript
const connection = duckdb.connect();registerFile(path: string, content: Uint8Array): void
Registers a file in the virtual file system with binary content.
typescript
const csvContent = "id,name\n1,Alice\n2,Bob";
const bytes = new TextEncoder().encode(csvContent);
duckdb.registerFile("data.csv", bytes);registerFileString(fileName: string, content: string): void
Registers a file in the virtual file system with string content.
typescript
const data = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
duckdb.registerFileString("data.json", JSON.stringify(data));Connection Class
Query Execution
query(sql: string): MaterializedQueryResult
Executes a SQL query and returns the result.
typescript
const result = connection.query("SELECT * FROM users WHERE age > 18");
console.log(result.rowCount()); // Number of rows
result.delete(); // Always clean up!prepare(sql: string): PreparedStatement
Creates a prepared statement for repeated execution with different parameters.
typescript
const stmt = connection.prepare("SELECT * FROM users WHERE id = ?");
const result = stmt.execute([42]);
result.delete();
stmt.delete();interrupt(): void
Interrupts the currently running query.
typescript
connection.interrupt();getQueryProgress(): number
Gets the progress of the currently executing query (0.0 to 1.0).
typescript
const progress = connection.getQueryProgress();
console.log(`Query is ${progress * 100}% complete`);Transaction Management
beginTransaction(): void
Starts a new transaction.
typescript
connection.beginTransaction();
connection.query("INSERT INTO users VALUES (1, 'Alice')").delete();
connection.commit();commit(): void
Commits the current transaction.
rollback(): void
Rolls back the current transaction.
typescript
connection.beginTransaction();
connection.query("INSERT INTO users VALUES (1, 'Alice')").delete();
connection.rollback(); // Changes discardedsetAutoCommit(enabled: boolean): void
Enables or disables auto-commit mode (default: enabled).
typescript
connection.setAutoCommit(false);
// Multiple operations in a single transaction
connection.setAutoCommit(true);isAutoCommit(): boolean
Checks if auto-commit is enabled.
typescript
console.log(connection.isAutoCommit()); // truehasActiveTransaction(): boolean
Checks if there's an active transaction.
typescript
console.log(connection.hasActiveTransaction()); // false
connection.beginTransaction();
console.log(connection.hasActiveTransaction()); // truePreparedStatement Class
Prepared statements allow you to execute the same query multiple times with different parameters efficiently.
execute(params: any[]): QueryResult
Executes the prepared statement with the provided parameters.
typescript
const stmt = connection.prepare("SELECT * FROM users WHERE age > ? AND city = ?");
const result1 = stmt.execute([18, "New York"]);
const result2 = stmt.execute([25, "London"]);
result1.delete();
result2.delete();
stmt.delete();Supported parameter types:
string- VARCHARnumber- INTEGER or DOUBLEboolean- BOOLEANnullorundefined- NULL
names(): string[]
Returns the column names that will be returned by the query.
typescript
const stmt = connection.prepare("SELECT id, name FROM users");
console.log(stmt.names()); // ["id", "name"]
stmt.delete();types(): string[]
Returns the column type names.
typescript
const stmt = connection.prepare("SELECT id, name FROM users");
console.log(stmt.types()); // ["INTEGER", "VARCHAR"]
stmt.delete();columnCount(): number
Returns the number of columns in the result.
typescript
console.log(stmt.columnCount()); // 2statementType(): number
Returns the statement type as a numeric code.
hasError(): boolean
Checks if the statement has an error.
typescript
const stmt = connection.prepare("SELECT * FROM invalid_syntax WHERE");
if (stmt.hasError()) {
console.error(stmt.getError());
}
stmt.delete();getError(): string
Returns the error message if hasError() is true.
MaterializedQueryResult Class
Represents the result of a query execution.
Result Metadata
rowCount(): bigint
Returns the number of rows in the result.
typescript
const result = connection.query("SELECT * FROM users");
console.log(Number(result.rowCount())); // e.g., 10
result.delete();columnCount(): bigint
Returns the number of columns in the result.
typescript
console.log(Number(result.columnCount())); // e.g., 3columnNames(): string[]
Returns an array of column names.
typescript
const result = connection.query("SELECT id, name, age FROM users LIMIT 1");
console.log(result.columnNames()); // ["id", "name", "age"]
result.delete();columnName(index: bigint): string
Returns the name of a specific column by index.
typescript
console.log(result.columnName(0n)); // "id"
console.log(result.columnName(1n)); // "name"columnTypes(): string[]
Returns an array of column type names.
typescript
console.log(result.columnTypes()); // ["INTEGER", "VARCHAR", "INTEGER"]resultType(): number
Returns the result type as a numeric code.
statementType(): number
Returns the statement type that produced this result.
Data Access
getValue(column: number, row: number): any
Returns the value at the specified column and row.
typescript
const result = connection.query("SELECT id, name FROM users LIMIT 1");
const id = result.getValue(0, 0); // First column, first row
const name = result.getValue(1, 0); // Second column, first row
console.log(`User ${id}: ${name}`);
result.delete();Return types:
nullfor NULL valuesbooleanfor BOOLEAN columnsnumberfor numeric columns (TINYINT, SMALLINT, INTEGER, BIGINT, FLOAT, DOUBLE)stringfor VARCHAR and other types
toJSON(): string
Converts the entire result to a JSON string (array of objects).
typescript
const result = connection.query("SELECT id, name, age FROM users");
const json = result.toJSON();
// Returns: '[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","age":25}]'
const data = JSON.parse(json);
console.log(data[0].name); // "Alice"
result.delete();Features:
- Returns a JSON array with one object per row
- Column names become object keys
- NULL values are represented as
null - Proper JSON escaping for special characters
- NaN and Infinity are converted to
null
collection(): ColumnDataCollection
Returns the underlying column data collection.
typescript
const collection = result.collection();
console.log(Number(collection.count())); // Row count
console.log(Number(collection.columnCount())); // Column countOutput
stringify(): string
Returns a string representation of the result.
typescript
const result = connection.query("SELECT * FROM users LIMIT 3");
console.log(result.stringify());
result.delete();print(): void
Prints the result to the console.
typescript
result.print();
// Outputs formatted table to consoleError Handling
hasError(): boolean
Checks if the query resulted in an error.
typescript
const result = connection.query("SELECT * FROM nonexistent_table");
if (result.hasError()) {
console.error("Query failed:", result.getError());
}
result.delete();getError(): string
Returns the error message if one occurred.
typescript
const errorMsg = result.getError();
console.log(errorMsg); // Error message stringUsage Examples
Basic Query
typescript
import { DuckDB } from "@hpcc-js/wasm-duckdb";
const duckdb = await DuckDB.load();
const connection = duckdb.connect();
const result = connection.query(`
SELECT
id,
name,
age
FROM (VALUES
(1, 'Alice', 30),
(2, 'Bob', 25),
(3, 'Charlie', 35)
) AS users(id, name, age)
WHERE age > 25
`);
console.log(`Found ${result.rowCount()} users`);
for (let i = 0; i < Number(result.rowCount()); i++) {
const id = result.getValue(0, i);
const name = result.getValue(1, i);
const age = result.getValue(2, i);
console.log(`${id}: ${name} (${age} years old)`);
}
result.delete();
connection.delete();Prepared Statements
typescript
const connection = duckdb.connect();
// Create a table
connection.query("CREATE TABLE users (id INTEGER, name VARCHAR, age INTEGER)").delete();
// Insert data using prepared statement
const insertStmt = connection.prepare("INSERT INTO users VALUES (?, ?, ?)");
insertStmt.execute([1, "Alice", 30]).delete();
insertStmt.execute([2, "Bob", 25]).delete();
insertStmt.execute([3, "Charlie", 35]).delete();
insertStmt.delete();
// Query with prepared statement
const queryStmt = connection.prepare("SELECT * FROM users WHERE age > ?");
const result = queryStmt.execute([26]);
console.log(result.toJSON());
// [{"id":1,"name":"Alice","age":30},{"id":3,"name":"Charlie","age":35}]
result.delete();
queryStmt.delete();
connection.delete();Working with JSON Files
typescript
const duckdb = await DuckDB.load();
const connection = duckdb.connect();
// Register JSON data as a file
const data = [
{ product: "Laptop", price: 999.99, quantity: 5 },
{ product: "Mouse", price: 29.99, quantity: 20 },
{ product: "Keyboard", price: 79.99, quantity: 15 }
];
duckdb.registerFileString("products.json", JSON.stringify(data));
// Query the JSON file
const result = connection.query(`
SELECT
product,
price,
quantity,
price * quantity AS total_value
FROM read_json_auto('products.json')
ORDER BY total_value DESC
`);
console.log(result.toJSON());
result.delete();
connection.delete();Working with CSV Files
typescript
const csvData = `id,name,department,salary
1,Alice,Engineering,120000
2,Bob,Sales,80000
3,Charlie,Engineering,110000
4,Diana,HR,75000`;
duckdb.registerFileString("employees.csv", csvData);
const result = connection.query(`
SELECT
department,
COUNT(*) as employee_count,
AVG(salary) as avg_salary
FROM read_csv_auto('employees.csv')
GROUP BY department
ORDER BY avg_salary DESC
`);
result.print(); // Prints formatted table
result.delete();Transaction Management
typescript
const connection = duckdb.connect();
connection.query("CREATE TABLE accounts (id INTEGER, balance DECIMAL)").delete();
connection.query("INSERT INTO accounts VALUES (1, 1000), (2, 500)").delete();
// Transfer money between accounts
connection.setAutoCommit(false);
connection.beginTransaction();
try {
connection.query("UPDATE accounts SET balance = balance - 200 WHERE id = 1").delete();
connection.query("UPDATE accounts SET balance = balance + 200 WHERE id = 2").delete();
connection.commit();
console.log("Transfer successful");
} catch (error) {
connection.rollback();
console.error("Transfer failed, rolled back");
}
connection.delete();JSON Export
typescript
const result = connection.query("SELECT * FROM users ORDER BY age");
// Export entire result as JSON
const jsonString = result.toJSON();
// Save to file or send to API
console.log(jsonString);
// [{"id":2,"name":"Bob","age":25},{"id":1,"name":"Alice","age":30},...]
// Parse back to JavaScript
const users = JSON.parse(jsonString);
users.forEach(user => {
console.log(`${user.name} is ${user.age} years old`);
});
result.delete();Error Handling
typescript
const connection = duckdb.connect();
// Query error handling
const result = connection.query("SELECT * FROM nonexistent_table");
if (result.hasError()) {
console.error("Query error:", result.getError());
} else {
console.log("Query succeeded");
}
result.delete();
// Prepared statement error handling
const stmt = connection.prepare("SELECT * FROM invalid_syntax WHERE");
if (stmt.hasError()) {
console.error("Statement preparation error:", stmt.getError());
}
stmt.delete();
connection.delete();Memory Management
Important: Always call .delete() on results, prepared statements, and connections when done to free memory:
typescript
const result = connection.query("SELECT * FROM users");
// Use result...
result.delete(); // Clean up!
const stmt = connection.prepare("SELECT * FROM users WHERE id = ?");
// Use stmt...
stmt.delete(); // Clean up!
connection.delete(); // Clean up when done with connectionTypeScript Support
This package includes full TypeScript type definitions for type-safe development.
Reference
License
See the root package for license information.