Skip to main content

TypeScript Types

Comprehensive type definitions for the DuckDB WebSocket SDK, providing full type safety and IntelliSense support.

Importing Types

All types are exported from the main package:
import {
  // Client
  DucklingClient,

  // Core types
  QueryMessage,
  QueryResponse,

  // Config types
  DuckDBSDKConfig,
  ConnectionStats,

  // Query types
  CountResult,
  AggregateResult,
  QueryRow,

  // Pagination types
  PaginationOptions,
  PaginatedResult,

  // Batch types
  BatchQueryRequest,
  BatchQueryResult,

  // Event types
  DucklingClientEvents,

  // Error types
  DuckDBError,
  DuckDBErrorType
} from '@chittihq/duckling';

Core Types

QueryMessage

Message sent to the DuckDB server.
interface QueryMessage {
  id: string;                  // Unique message identifier
  type: 'query' | 'ping' | 'auth';
  sql?: string;                // SQL query (for 'query' type)
  params?: any[];              // Optional query parameters
  apiKey?: string;             // API key (for 'auth' type)
}
Usage: Internal - used by SDK for WebSocket communication.

QueryResponse

Response from the DuckDB server.
interface QueryResponse<T = any> {
  id: string;                  // Message ID this responds to
  success: boolean;            // Operation success status
  result?: T[];                // Query results (on success)
  error?: string;              // Error message (on failure)
  duration?: number;           // Query duration in milliseconds
}
Usage: Returned by internal message handling.

Configuration Types

DuckDBSDKConfig

Client configuration options.
interface DuckDBSDKConfig {
  url: string;                      // WebSocket server URL
  apiKey: string;                   // API authentication key
  databaseName?: string;            // Database to connect to (default: 'default')
  autoConnect?: boolean;            // Auto-connect on first query (default: true)
  autoReconnect?: boolean;          // Auto-reconnect on failure (default: true)
  maxReconnectAttempts?: number;    // Max reconnect attempts (default: 5)
  reconnectDelay?: number;          // Reconnect delay in ms (default: 1000)
  connectionTimeout?: number;       // Connection timeout in ms (default: 5000)
  autoPing?: boolean;               // Keep-alive ping (default: true)
  pingInterval?: number;            // Ping interval in ms (default: 30000)
  enableLogging?: boolean;          // Enable logging (default: false)
  logLevel?: 'error' | 'warn' | 'info' | 'debug';  // Log level (default: 'info')
}
Example:
const config: DuckDBSDKConfig = {
  url: 'ws://localhost:3001/ws',
  apiKey: 'your-api-key',
  databaseName: 'production',
  autoConnect: true,
  autoPing: true,
  pingInterval: 30000,
  maxReconnectAttempts: 5
};

const client = new DucklingClient(config);

ConnectionStats

Connection statistics.
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
}
Example:
const stats = client.getStats();
console.log(`Connected: ${stats.connected}`);
console.log(`Pending: ${stats.pendingRequests}`);

Query Result Types

Typed Query Results

Define your table schema and get full type safety:
interface User {
  id: number;
  name: string;
  email: string;
  createdAt: string;
}

const users = await client.query<User>('SELECT * FROM User LIMIT 10');
// users is typed as User[]
// TypeScript knows: users[0].email, users[0].name exist

CountResult

For COUNT queries.
interface CountResult {
  count: number;
}
Example:
const result = await client.query<CountResult>('SELECT COUNT(*) as count FROM User');
const totalUsers = result[0].count; // number

AggregateResult

For aggregate queries.
interface AggregateResult {
  count?: number;
  sum?: number;
  avg?: number;
  min?: number;
  max?: number;
}
Example:
const stats = await client.query<AggregateResult>(`
  SELECT
    COUNT(*) as count,
    AVG(total) as avg,
    SUM(total) as sum
  FROM Order
`);

console.log(`Average: ${stats[0].avg}`);

QueryRow

Generic query result row type.
type QueryRow = Record<string, any>;
Usage: Default type when no generic is specified.

Custom Result Types

Create custom types for complex queries:
interface UserWithOrderCount {
  id: number;
  name: string;
  email: string;
  orderCount: number;
}

const users = await client.query<UserWithOrderCount>(`
  SELECT u.*, COUNT(o.id) as orderCount
  FROM User u
  LEFT JOIN Order o ON u.id = o.userId
  GROUP BY u.id
`);

Pagination Types

PaginationOptions

Options for paginated queries.
interface PaginationOptions {
  limit: number;   // Number of rows to return
  offset: number;  // Number of rows to skip
}
Example:
const paginationOptions: PaginationOptions = {
  limit: 20,
  offset: 0
};

const result = await client.queryPaginated<User>(
  'SELECT * FROM User ORDER BY createdAt DESC',
  paginationOptions
);

PaginatedResult

Paginated query result.
interface PaginatedResult<T = QueryRow> {
  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
  };
}
Example:
const result = await client.queryPaginated<User>(
  'SELECT * FROM User ORDER BY createdAt DESC',
  { limit: 20, offset: 0 }
);

console.log(`Showing ${result.data.length} of ${result.pagination.total} users`);
console.log(`Has more: ${result.pagination.hasMore}`);

Batch Query Types

BatchQueryRequest

Individual batch query request.
interface BatchQueryRequest {
  sql: string;         // SQL query
  params?: any[];      // Optional parameters
}
Example:
const requests: BatchQueryRequest[] = [
  { sql: 'SELECT * FROM User LIMIT 10' },
  { sql: 'SELECT * FROM Order WHERE userId = ?', params: [123] }
];

BatchQueryResult

Individual batch query result.
interface BatchQueryResult<T = QueryRow> {
  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
}
Example:
const results = await client.queryBatchDetailed<QueryRow>(requests);

results.forEach((result, index) => {
  if (result.success) {
    console.log(`Query ${index + 1}: ${result.data?.length} rows in ${result.duration}ms`);
  } else {
    console.error(`Query ${index + 1}: ${result.error}`);
  }
});

Event Types

DucklingClientEvents

Strongly-typed event interface.
interface DucklingClientEvents {
  connected: () => void;           // Connected to server
  disconnected: () => void;        // Disconnected from server
  error: (error: Error) => void;   // Error occurred
  reconnecting: (attempt: number) => void;  // Reconnection attempt
  message: (response: QueryResponse) => void;  // Message received
}
Example:
// TypeScript ensures correct event names and signatures
client.on('connected', () => {
  console.log('Connected!');
});

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

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

Error Types

DuckDBErrorType

Error type enumeration.
enum DuckDBErrorType {
  CONNECTION_ERROR = 'CONNECTION_ERROR',
  AUTH_ERROR = 'AUTH_ERROR',
  QUERY_ERROR = 'QUERY_ERROR',
  TIMEOUT_ERROR = 'TIMEOUT_ERROR',
  CONFIG_ERROR = 'CONFIG_ERROR',
  UNKNOWN_ERROR = 'UNKNOWN_ERROR'
}

DuckDBError

Enhanced error class.
class DuckDBError extends Error {
  constructor(
    public type: DuckDBErrorType,
    message: string,
    public context?: Record<string, any>
  )
}
Example:
try {
  await client.query('SELECT * FROM NonExistentTable');
} catch (error) {
  if (error instanceof DuckDBError) {
    console.error(`${error.type}: ${error.message}`);
    console.error('Context:', error.context);
  }
}

Best Practices

1. Define Schema Types

Create TypeScript interfaces matching your database schema:
// schema.ts
export interface User {
  id: number;
  name: string;
  email: string;
  createdAt: string;
  updatedAt: string;
}

export interface Order {
  id: number;
  userId: number;
  total: number;
  status: 'pending' | 'completed' | 'cancelled';
  createdAt: string;
}

2. Use Generic Type Parameters

Always specify the expected result type:
// ✓ Good - type-safe
const users = await client.query<User>('SELECT * FROM User');

// ✗ Bad - loses type information
const users = await client.query('SELECT * FROM User');

3. Create Custom Types for Complex Queries

interface UserWithOrderCount extends User {
  orderCount: number;
}

const users = await client.query<UserWithOrderCount>(`
  SELECT u.*, COUNT(o.id) as orderCount
  FROM User u
  LEFT JOIN Order o ON u.id = o.userId
  GROUP BY u.id
`);

4. Handle Errors with Type Guards

try {
  await client.query<User>('SELECT * FROM User');
} catch (error) {
  if (error instanceof DuckDBError) {
    // Handle DuckDB-specific errors
    console.error(`${error.type}: ${error.message}`);
  } else if (error instanceof Error) {
    // Handle generic errors
    console.error(error.message);
  }
}

5. Use Typed Events

// TypeScript ensures correct event signatures
client.on('connected', () => {
  console.log('Connected!');
});

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

Type-Safe Examples

Basic Typed Query

import { DucklingClient } from '@chittihq/duckling';

interface User {
  id: number;
  name: string;
  email: string;
}

const client = new DucklingClient({
  url: 'ws://localhost:3001/ws',
  apiKey: process.env.DUCKLING_API_KEY!
});

const users = await client.query<User>('SELECT * FROM User LIMIT 10');
// users: User[]
// Full IntelliSense for users[0].email, users[0].name, etc.

Pagination with Types

const result = await client.queryPaginated<User>(
  'SELECT * FROM User ORDER BY createdAt DESC',
  { limit: 20, offset: 0 }
);

console.log(`Page: ${result.data.length} users`);
console.log(`Has more: ${result.pagination.hasMore}`);

Batch Queries with Types

const results = await client.queryBatchDetailed<QueryRow>([
  { sql: 'SELECT * FROM User LIMIT 10' },
  { sql: 'SELECT * FROM Order LIMIT 10' }
]);

results.forEach(result => {
  if (result.success) {
    console.log(`Success: ${result.data?.length} rows`);
  } else {
    console.error(`Failed: ${result.error}`);
  }
});

Custom Aggregate Types

interface OrderStats {
  totalOrders: number;
  totalRevenue: number;
  avgOrderValue: number;
}

const stats = await client.query<OrderStats>(`
  SELECT
    COUNT(*) as totalOrders,
    SUM(total) as totalRevenue,
    AVG(total) as avgOrderValue
  FROM Order
`);

console.log(`Average order: $${stats[0].avgOrderValue.toFixed(2)}`);

Next Steps

Client API

Explore DucklingClient methods

Examples

See real-world usage examples

Build docs developers (and LLMs) love