Core API
Pure JavaScript DuckUI engine, programmatic queries, and chart data factories
Core API
@duck_ui/core is the framework-agnostic foundation of Duck-UI. It provides the DuckDB-WASM engine, query execution, filter injection, and chart data factories -- with no dependency on React or the DOM.
Use it when you want full programmatic control, or when building your own UI layer on top.
Installation
bun add @duck_ui/core @duckdb/duckdb-wasmOr with npm:
npm install @duck_ui/core @duckdb/duckdb-wasmDuckUI Class
The main entry point. Creates and manages a DuckDB-WASM instance.
import { DuckUI } from '@duck_ui/core'
const ui = new DuckUI()init(data)
Boot DuckDB-WASM and load data into tables. Call this once before any queries.
await ui.init({
orders: [
{ id: 1, product: 'Widget', status: 'shipped', total: 99.50 },
{ id: 2, product: 'Gadget', status: 'pending', total: 149.00 },
],
sales: { url: '/data/sales.parquet', format: 'parquet' },
users: { fetch: () => fetch('/api/users').then(r => r.json()) },
})The data argument is Record<string, DataInput> -- the same types used by DuckUIProvider and <duck-provider>:
| Variant | Example |
|---|---|
| Array of objects | { orders: [{ id: 1, total: 99 }] } |
| URL (CSV/JSON/Parquet) | { sales: { url: '/data/sales.parquet', format: 'parquet' } } |
| Fetch callback | { users: { fetch: () => api.getUsers() } } |
| Browser File | { upload: fileFromInput } |
query(sql)
Execute any SQL against the in-browser DuckDB.
const result = await ui.query('SELECT product, SUM(total) AS revenue FROM orders GROUP BY 1')
console.log(result.rows)
// [{ product: 'Widget', revenue: 99.5 }, { product: 'Gadget', revenue: 149 }]
console.log(result.columns)
// [{ name: 'product', type: 'VARCHAR', nullable: true }, { name: 'revenue', type: 'DOUBLE', nullable: true }]
console.log(result.rowCount) // 2
console.log(result.executionTime) // 1.2 (ms)If filters are active (via setFilter), they are injected as WHERE clauses automatically.
setFilter(column, value)
Set a global filter. All subsequent queries will include this filter.
ui.setFilter('status', 'shipped')
// Now this query automatically gets WHERE "status" = 'shipped'
const result = await ui.query('SELECT * FROM orders')Supported filter values:
| Value | Generated SQL |
|---|---|
'shipped' | WHERE "status" = 'shipped' |
42 | WHERE "col" = 42 |
['A', 'B'] | WHERE "col" IN ('A', 'B') |
{ min: 10, max: 100 } | WHERE "col" >= 10 AND "col" <= 100 |
{ start: '2024-01-01', end: '2024-12-31' } | WHERE "col" BETWEEN '2024-01-01' AND '2024-12-31' |
null | (removes the filter) |
clearFilters()
Remove all active filters.
ui.clearFilters()getSchema(tableName)
Inspect the columns and types of a loaded table.
const schema = await ui.getSchema('orders')
if (schema) {
console.log(schema.tableName) // 'orders'
console.log(schema.columns)
// [
// { name: 'id', type: 'INTEGER', nullable: true },
// { name: 'product', type: 'VARCHAR', nullable: true },
// { name: 'status', type: 'VARCHAR', nullable: true },
// { name: 'total', type: 'DOUBLE', nullable: true },
// ]
}Returns null if the table does not exist.
destroy()
Tear down DuckDB-WASM and release all memory.
await ui.destroy()After calling destroy(), the instance cannot be reused. Create a new DuckUI() if needed.
Chart Factories
Pure functions that transform a QueryResult into chart-ready data structures. They have no DOM dependency -- use them to feed data into any charting library (D3, Chart.js, Highcharts, etc.).
import {
toBarData,
toLineData,
toAreaData,
toScatterData,
toPieData,
toSparklineData,
} from '@duck_ui/core'toBarData(result)
First column becomes labels, remaining columns become series.
const result = await ui.query('SELECT product, SUM(total) AS revenue FROM orders GROUP BY 1')
const bar = toBarData(result)
// { labels: ['Widget', 'Gadget'], series: [{ name: 'revenue', values: [99.5, 149] }] }toLineData(result) / toAreaData(result)
First column becomes x-values, remaining columns become series. toAreaData produces the same structure (rendered with fill by the chart library).
const result = await ui.query('SELECT month, SUM(rev) AS revenue FROM sales GROUP BY 1 ORDER BY 1')
const line = toLineData(result)
// { x: [1, 2, 3, ...], series: [{ name: 'revenue', values: [1000, 1200, 900, ...] }] }toScatterData(result)
First column becomes x, second column becomes y.
const result = await ui.query('SELECT price, quantity FROM products')
const scatter = toScatterData(result)
// { points: [{ x: 9.99, y: 150 }, { x: 24.99, y: 42 }, ...] }toPieData(result)
First column becomes labels, second column becomes values.
const result = await ui.query('SELECT status, COUNT(*) AS n FROM orders GROUP BY 1')
const pie = toPieData(result)
// { slices: [{ label: 'shipped', value: 3 }, { label: 'pending', value: 1 }] }toSparklineData(result)
Returns a flat array of numbers from the second column (for inline trend lines).
const result = await ui.query('SELECT month, SUM(total) FROM orders GROUP BY 1 ORDER BY 1')
const spark = toSparklineData(result)
// [1200, 1400, 980, 1600, ...]Engine Internals
For advanced use cases, you can use the lower-level building blocks directly.
DuckDBManager
Manages the DuckDB-WASM lifecycle.
import { DuckDBManager } from '@duck_ui/core'
const manager = new DuckDBManager()
await manager.initialize()
const db = manager.getDatabase()ConnectionPool
Manages a pool of DuckDB connections (default: 4 concurrent).
import { ConnectionPool } from '@duck_ui/core'
const pool = new ConnectionPool(manager)
const conn = await pool.acquire()
// ... use conn
pool.release(conn)QueryExecutor
Executes SQL and coerces result types (BigInt to Number, Date to ISO string, etc.).
import { QueryExecutor } from '@duck_ui/core'
const executor = new QueryExecutor(pool)
const result = await executor.execute('SELECT * FROM orders')QueryCache
LRU cache for query results. Keyed by SQL string + filter state.
import { QueryCache } from '@duck_ui/core'
const cache = new QueryCache({ maxSize: 100 })
cache.set(key, result)
const cached = cache.get(key)
cache.invalidate() // clear allFilterInjector
Injects WHERE clauses into SQL strings based on active filters.
import { FilterInjector } from '@duck_ui/core'
const filters = { region: 'North', year: 2025 }
const injected = FilterInjector.inject('SELECT * FROM sales', filters)
// SELECT * FROM (SELECT * FROM sales) AS _filtered WHERE "region" = 'North' AND "year" = 2025Themes
Built-in theme objects for chart styling:
import { lightTheme, darkTheme, defaultPalette, darkPalette, colorblindPalette } from '@duck_ui/core'These are plain objects -- use them with any charting library, or pass to @duck_ui/embed's DuckUIProvider.
Full Example
A complete Node.js-style example (also works in the browser):
import { DuckUI, toBarData, toLineData } from '@duck_ui/core'
async function main() {
const ui = new DuckUI()
await ui.init({
orders: { url: '/data/orders.parquet', format: 'parquet' },
})
// Inspect schema
const schema = await ui.getSchema('orders')
console.log('Columns:', schema?.columns.map(c => c.name))
// Run a query
const result = await ui.query(
'SELECT product, SUM(total) AS revenue FROM orders GROUP BY 1 ORDER BY 2 DESC'
)
console.log('Top products:', result.rows)
// Transform for a chart library
const chartData = toBarData(result)
console.log('Chart labels:', chartData.labels)
console.log('Chart values:', chartData.series[0].values)
// Apply a filter
ui.setFilter('status', 'shipped')
const filtered = await ui.query('SELECT COUNT(*) AS n FROM orders')
console.log('Shipped orders:', filtered.rows[0].n)
// Clean up
ui.clearFilters()
await ui.destroy()
}
main()Next Steps
- Web Components -- use Custom Elements for zero-framework dashboards
- API Reference -- full reference for all packages
- Architecture -- how the packages fit together
- Charts -- chart types and customization (React)