Duck-UIDuck-UI

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-wasm

Or with npm:

npm install @duck_ui/core @duckdb/duckdb-wasm

DuckUI 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>:

VariantExample
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:

ValueGenerated SQL
'shipped'WHERE "status" = 'shipped'
42WHERE "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 all

FilterInjector

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" = 2025

Themes

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

On this page