Postgres analytics
First-party lite AnalyticsSink — stores events in the main DataStore, cross-dialect, zero external infra. ~10M events/day ceiling.
@voltro/plugin-analytics-postgres is the day-1 AnalyticsSink — it stores
events in an _voltro_events table on the main DataStore, so there's no
external service to run. It implements all four contract methods (track /
aggregate / timeseries / topN) by compiling to portable SQL that works on
every dialect (postgres / mysql / mariadb / mssql / sqlite). Pick it first;
swap to a real OLAP sink (DuckDB / ClickHouse / Tinybird) only once you outgrow
its ~10M events/day ceiling — the swap is a one-line config change because
every sink shares the same contract. For the shared AnalyticsSink API and the
useAnalytics() read methods, see Analytics & warehouse sinks.
Install
pnpm add @voltro/plugin-analytics-postgresWiring
// app.config.ts
import { postgresAnalytics } from '@voltro/plugin-analytics-postgres'
export default {
type: 'api' as const,
name: 'myApi',
store: 'postgres' as const,
analytics: postgresAnalytics(),
}The framework calls the factory at boot, hands it the SqlClient bound to the
running DataStore, and provides the resulting sink under AnalyticsSink. The
plugin's _voltro_events table is auto-merged into your schema, so
applySchema() creates it on first boot — no *.entity.ts needed.
Options
postgresAnalytics(options?) — all optional:
| Option | Type | Default | Notes |
|---|---|---|---|
sqlClient |
SqlClient.SqlClient |
the main DataStore's client | Override to land events in a SEPARATE database (e.g. a dedicated analytics Postgres while the app runs MySQL). You must create the events table there yourself. |
mirrorTables |
ReadonlyArray<string> |
[] (events-only) |
Reactive tables to CDC-mirror into _voltro_mirror_<table> ({ id, data }) so analytical queries can JOIN events against live user data. |
mirrorPrimaryKey |
string |
'id' |
Primary-key column on the mirrored source rows. |
analytics: postgresAnalytics({ mirrorTables: ['users', 'teams'] })Environment
VOLTRO_EVENTS_TTL_HOURS— retention TTL for_voltro_events. Default8760(365 days). A boot retention sweep drains events past the TTL so the table never grows without bound.
Honest ceiling
By ~10M events/day, range aggregates over 30 days take >5s on Postgres. Past
that, switch to @voltro/plugin-duckdb (embedded) or
@voltro/plugin-clickhouse (external). Reads are
tenant-scoped automatically — useAnalytics() stamps the caller's tenantId
onto every track and every aggregate.
Schema introspection
The plugin re-exports its events-table descriptor so you can extend the schema
(add a GIN index on properties, partition by month) via your own migration:
import { _voltroEventsTable } from '@voltro/plugin-analytics-postgres'See also
- Analytics & warehouse sinks — the shared
AnalyticsSinkcontract, theuseAnalytics()read API,composeAnalytics, and the CDC-mirror details.