Skip to main content

DucklingClient API Reference

The DucklingClient class provides a high-performance WebSocket interface to DuckDB.

Constructor

Create a new DucklingClient instance.
const client = new DucklingClient(config: DuckDBSDKConfig);
config
DuckDBSDKConfig
required
Configuration object for the client. See Installation for all options.

Methods

connect()

Manually connect to DuckDB server and authenticate with API key.
This method is optional when autoConnect: true (default). The client automatically connects on first query.
await client.connect(): Promise<void>
Example:
const client = new DucklingClient({
  url: 'ws://localhost:3001/ws',
  apiKey: process.env.DUCKLING_API_KEY!,
  autoConnect: false  // Disable auto-connect
});

// Manually connect
await client.connect();
Throws:
  • Error if connection fails
  • Error if authentication fails

query()

Execute SQL query and return typed results.
await client.query<T = any>(sql: string, params?: any[]): Promise<T[]>
sql
string
required
SQL query to execute
params
any[]
Optional query parameters for parameterized queries
T
type parameter
TypeScript type for result rows (defaults to any)
Returns: Promise<T[]> - Array of result rows Examples:
const users = await client.query('SELECT * FROM User LIMIT 10');
console.log(users);
Throws:
  • Error if query execution fails
  • Error if not connected (when autoConnect: false)

queryBatch()

Execute multiple queries in parallel.
await client.queryBatch<T = any>(queries: string[]): Promise<T[][]>
queries
string[]
required
Array of SQL queries to execute in parallel
T
type parameter
TypeScript type for result rows
Returns: Promise<T[][]> - Array of result arrays (one per query) Example:
const results = await client.queryBatch([
  'SELECT COUNT(*) as count FROM User',
  'SELECT COUNT(*) as count FROM Product',
  'SELECT COUNT(*) as count FROM Order'
]);

console.log('User count:', results[0][0].count);
console.log('Product count:', results[1][0].count);
console.log('Order count:', results[2][0].count);
Batch queries execute 3-5x faster than sequential execution. See Examples for benchmarks.

queryPaginated()

Execute paginated query with automatic LIMIT and OFFSET.
await client.queryPaginated<T = QueryRow>(
  sql: string,
  options: PaginationOptions
): Promise<PaginatedResult<T>>
sql
string
required
Base SQL query (without LIMIT/OFFSET)
options
PaginationOptions
required
Pagination options with limit and offset
Returns: Promise<PaginatedResult<T>> - Paginated result with metadata Example:
const result = await client.queryPaginated<User>(
  'SELECT * FROM User ORDER BY createdAt DESC',
  { limit: 20, offset: 0 }
);

console.log(`Showing ${result.data.length} users`);
console.log(`Has more: ${result.pagination.hasMore}`);
console.log(`Offset: ${result.pagination.offset}`);
console.log(`Limit: ${result.pagination.limit}`);
PaginatedResult structure:
interface PaginatedResult<T> {
  data: T[];                   // Result rows
  pagination: {
    offset: number;            // Current offset
    limit: number;             // Requested limit
    total?: number;            // Total rows (if available)
    hasMore?: boolean;         // Whether more rows exist
  };
}

queryBatchDetailed()

Execute batch queries with individual success/failure handling.
await client.queryBatchDetailed<T = QueryRow>(
  requests: BatchQueryRequest[]
): Promise<BatchQueryResult<T>[]>
requests
BatchQueryRequest[]
required
Array of query requests with optional parameters
Returns: Promise<BatchQueryResult<T>[]> - Array of results (success or error for each query) Example:
const requests: BatchQueryRequest[] = [
  { sql: 'SELECT * FROM User LIMIT 10' },
  { sql: 'SELECT * FROM InvalidTable' },  // Will fail
  { sql: 'SELECT * FROM Order LIMIT 10' }
];

const results = await client.queryBatchDetailed(requests);

results.forEach((result, index) => {
  if (result.success) {
    console.log(`Query ${index + 1}: Success - ${result.data?.length} rows in ${result.duration}ms`);
  } else {
    console.error(`Query ${index + 1}: Failed - ${result.error}`);
  }
});
BatchQueryResult structure:
interface BatchQueryResult<T> {
  query: string;       // Original query
  success: boolean;    // Query success status
  data?: T[];          // Results (on success)
  error?: string;      // Error message (on failure)
  duration?: number;   // Execution time in ms
}

ping()

Test connection with ping.
await client.ping(): Promise<boolean>
Returns: Promise<boolean> - true if connection is healthy, false otherwise Example:
const isAlive = await client.ping();
if (isAlive) {
  console.log('✓ Connection healthy');
} else {
  console.log('✗ Connection unhealthy');
}
Auto-ping is enabled by default (autoPing: true). Manual ping is useful for health checks.

close()

Close WebSocket connection.
client.close(): void
Example:
try {
  const users = await client.query('SELECT * FROM User');
  console.log(users);
} finally {
  client.close();
}
Always call close() when done to free resources. Use in finally blocks to ensure cleanup.

isConnected()

Check if client is connected and authenticated.
client.isConnected(): boolean
Returns: boolean - true if connected and authenticated Example:
if (client.isConnected()) {
  await client.query('SELECT * FROM User');
} else {
  console.log('Not connected');
}

getStats()

Get connection statistics.
client.getStats(): ConnectionStats
Returns: ConnectionStats object with connection information Example:
const stats = client.getStats();
console.log(stats);
// {
//   connected: true,
//   authenticated: true,
//   pendingRequests: 2,
//   reconnectAttempts: 0,
//   url: 'ws://localhost:3001/ws'
// }
ConnectionStats structure:
interface ConnectionStats {
  connected: boolean;          // Currently connected and authenticated
  authenticated: boolean;      // Successfully authenticated
  pendingRequests: number;     // Requests waiting for response
  reconnectAttempts: number;   // Reconnection attempts made
  url: string;                 // WebSocket server URL
}

Event Handling

The client extends EventEmitter and provides strongly-typed events.

on()

Listen to events.
client.on(event: string, listener: Function): this
Available Events:
connected
() => void
Emitted when successfully connected to server
disconnected
() => void
Emitted when disconnected from server
error
(error: Error) => void
Emitted when an error occurs
reconnecting
(attempt: number) => void
Emitted when reconnection attempt starts
message
(response: QueryResponse) => void
Emitted when a message is received
Example:
client.on('connected', () => {
  console.log('Connected to DuckDB server');
});

client.on('disconnected', () => {
  console.log('Disconnected from server');
});

client.on('error', (error: Error) => {
  console.error('WebSocket error:', error.message);
});

client.on('reconnecting', (attempt: number) => {
  console.log(`Reconnection attempt ${attempt}`);
});

Error Handling

Handle query errors with try-catch:
try {
  const result = await client.query('SELECT * FROM NonExistentTable');
} catch (error) {
  if (error instanceof Error) {
    console.error('Query failed:', error.message);
  }
}
Common Error Types:
  • Connection errors: Server unreachable, authentication failed
  • Query errors: Invalid SQL, table not found, syntax errors
  • Timeout errors: Query exceeded timeout (30 seconds)

Performance Tips

Execute multiple queries in parallel with queryBatch() for 3-5x speedup:
// ✓ Good - parallel execution
const results = await client.queryBatch([
  'SELECT COUNT(*) FROM User',
  'SELECT COUNT(*) FROM Product',
  'SELECT COUNT(*) FROM Order'
]);

// ✗ Bad - sequential execution
const users = await client.query('SELECT COUNT(*) FROM User');
const products = await client.query('SELECT COUNT(*) FROM Product');
const orders = await client.query('SELECT COUNT(*) FROM Order');
Create multiple clients for 10,000+ queries/second:
const pool = Array.from({ length: 5 }, () => new DucklingClient(config));

// Round-robin query distribution
let currentIndex = 0;
function query(sql: string) {
  const client = pool[currentIndex];
  currentIndex = (currentIndex + 1) % pool.length;
  return client.query(sql);
}
See Connection Pool Example for full implementation.
Auto-ping keeps connections alive automatically (enabled by default):
const client = new DucklingClient({
  url: 'ws://localhost:3001/ws',
  apiKey: process.env.DUCKLING_API_KEY!,
  autoPing: true,       // Default: true
  pingInterval: 30000   // Default: 30 seconds
});
Define schema types for full IntelliSense and compile-time checks:
interface User {
  id: number;
  name: string;
  email: string;
}

const users = await client.query<User>('SELECT * FROM User');
// TypeScript knows: users[0].email exists

Next Steps

TypeScript Types

Learn about type definitions

Examples

See real-world usage examples

Build docs developers (and LLMs) love