DuckDB

Embedded DuckDB AnalyticsSink — real column-store OLAP in-process, no external service to run.

@voltro/plugin-duckdb is the embedded AnalyticsSink — it runs DuckDB in-process (via @duckdb/node-api) so you get real column-store + vectorized OLAP performance without deploying an external service. Events land in a voltro_events column-store table inside the DuckDB instance; the sink implements all four contract methods against it. It's the sweet spot between postgres-lite (no infra, but row-store) and ClickHouse (fastest, but an external cluster). The catch: it's single-process. For the shared AnalyticsSink API and the useAnalytics() read methods, see Analytics & warehouse sinks.

Install

pnpm add @voltro/plugin-duckdb

@duckdb/node-api is an optional dependency — installed alongside the plugin.

Wiring

// app.config.ts
import { duckdbAnalytics } from '@voltro/plugin-duckdb'

export default {
  type:      'api' as const,
  name:      'myApi',
  store:     'postgres' as const,
  analytics: duckdbAnalytics({ path: '.voltro/analytics.duckdb' }),
}

At boot the plugin opens the DuckDB connection and creates the events table; the connection + instance are closed on graceful shutdown.

Options

duckdbAnalytics(options?) — all optional:

Option Type Default Notes
path string in-memory A file path (.voltro/analytics.duckdb) → durable across restarts, single-process. Omit or pass :memory: → events live in process memory, lost on restart (ephemeral dev / tests).
mirrorTables ReadonlyArray<string> [] (events-only) Reactive tables to CDC-mirror into voltro_mirror_<table> inside DuckDB so analytical queries JOIN events against live user data.
mirrorPrimaryKey string 'id' Primary-key column on the mirrored source rows.
analytics: duckdbAnalytics({
  path: '.voltro/analytics.duckdb',
  mirrorTables: ['users', 'teams'],
})

Tenant isolation

Parity with postgres-lite: track stamps the event's tenantId into a tenant_id column, and aggregate/timeseries/topN filter by the query's tenantId (injected by useAnalytics() from the caller's subject), so a tenant-scoped read never spans tenants. A query with no tenantId is an unscoped system read. Swapping postgresAnalytics()duckdbAnalytics() preserves the same tenant semantics.

Single-process constraint

DuckDB can't open the same file from two workers. For multi-instance deployments, either pin all analytics traffic to one replica (sticky-session style) or use @voltro/plugin-clickhouse instead. There is no provider escape-hatch Tag — the contract methods are the whole surface.

See also

  • Analytics & warehouse sinks — the shared AnalyticsSink contract, the useAnalytics() read API, composeAnalytics, and the CDC-mirror details (incl. the voltro_mirror_<table> JOIN pattern).