Skip to main content

Consuming the Data

:::caution Pre-release This is a pre-1.0 library (v0.1.1) — API may change without notice. :::

Events are persisted to your raw_event table via the sink callback you provide. This guide covers how to query and consume that data.

Your Sink Implementation

Define where and how events are stored:

const handler = createCollectHandler({
tokenSecrets: { /* ... */ },
ipSalt: process.env.NS_IP_SALT!,
sink: {
async storeEvents(batch) {
// Example: Drizzle ORM
await db
.insert(rawEvent)
.values(
batch.events.map((e) => ({
session_id: batch.sessionId,
event_type: e.event_type,
route_pattern: e.route_pattern,
click_target_label: e.click_target_label,
click_count: e.click_count,
effect_latency_ms: e.effect_latency_ms,
conversion_source: e.conversion_source,
conversion_name: e.conversion_name,
ts: new Date(e.ts),
// ... other columns ...
}))
);
},
},
});

The batch object contains:

{
sessionId: string; // Session identifier
batch_id: string; // Unique batch identifier
events: JourneyEvent[]; // Array of 1-10 events
truncated: boolean; // true if session hit 200-event cap
}

Control Plane: Agent API

For AI agents and automated systems, the apps/agent-api package provides a query surface over your raw events. This is the recommended entry point for machine learning pipelines and control-plane logic.

Note: Deep-diving the agent API is out of scope for this docs site. See the apps/agent-api README for its schema and query interface.

Direct Database Queries

If you prefer direct SQL access, query raw_event directly:

-- All page views
SELECT * FROM raw_event
WHERE event_type = 'page_view'
ORDER BY ts DESC
LIMIT 1000;

-- Rage clicks (frustration signal)
SELECT
route_pattern,
click_target_label,
COUNT(*) as count,
AVG(click_count) as avg_click_count
FROM raw_event
WHERE event_type = 'rage_click'
GROUP BY route_pattern, click_target_label
ORDER BY count DESC;

-- Dead clicks (UX friction)
SELECT
route_pattern,
click_target_label,
COUNT(*) as count
FROM raw_event
WHERE event_type = 'dead_click'
GROUP BY route_pattern, click_target_label
ORDER BY count DESC;

-- Conversions
SELECT
conversion_source,
conversion_name,
COUNT(*) as total,
COUNT(DISTINCT session_id) as sessions
FROM raw_event
WHERE event_type = 'conversion'
GROUP BY conversion_source, conversion_name
ORDER BY total DESC;

Data Retention and Partitioning

Raw events are stored in a partitioned table:

-- Check partition status
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size
FROM pg_tables
WHERE tablename LIKE 'raw_event_%'
ORDER BY tablename DESC;

Partition lifecycle:

  • New partition: Created daily for that day's UTC date.
  • Active: Used for 24-30 hours (configurable).
  • Archived: Old partitions remain queryable for 30 days (configurable).
  • Dropped: After retention period, the partition is dropped (not individual rows deleted).

For GDPR compliance, deletion is scoped to raw_event and session_summary tables. Aggregated tables are unaffected.

Monitoring and Analytics

Common queries for operational insight:

-- Events per minute (throughput)
SELECT
DATE_TRUNC('minute', ts) as minute,
COUNT(*) as event_count
FROM raw_event
WHERE ts > NOW() - INTERVAL '1 hour'
GROUP BY minute
ORDER BY minute DESC;

-- Session duration (from first page_view to last event)
SELECT
session_id,
MIN(ts) as session_start,
MAX(ts) as session_end,
(MAX(ts) - MIN(ts)) / 1000.0 as session_duration_seconds,
COUNT(*) as event_count
FROM raw_event
WHERE event_type IN ('page_view', 'rage_click', 'dead_click')
GROUP BY session_id
HAVING COUNT(*) > 1
ORDER BY session_duration_seconds DESC
LIMIT 100;

-- Conversion funnel
SELECT
event_type,
COUNT(DISTINCT session_id) as unique_sessions,
COUNT(*) as total_events
FROM raw_event
WHERE event_type IN ('page_view', 'conversion')
GROUP BY event_type
ORDER BY CASE WHEN event_type = 'page_view' THEN 1 ELSE 2 END;

Schema Notes

The exact table structure depends on your sink implementation. Common columns:

  • id: Primary key (UUID or auto-increment).
  • session_id: Browser session identifier.
  • event_type: One of "page_view", "rage_click", "dead_click", "conversion".
  • route_pattern: Normalized URL (no query/hash).
  • click_target_label: Scrubbed button/input label, or null.
  • click_count: For rage clicks, the number of rapid clicks.
  • effect_latency_ms: Milliseconds until UI change, or null.
  • conversion_source: "route_pattern" or "explicit".
  • conversion_name: User-supplied conversion name, or null.
  • ts: Client timestamp (event occurred at).
  • created_at: Server timestamp (event stored at).
  • hashed_ip: Daily-rotating-salt hash of the client IP.
  • site_id: From the verified token's origin.

Privacy reminder: raw_event contains the most sensitive data (per-user events). Ensure database access is restricted to authorized services only.


What's Next

  • Query patterns for your specific use case
  • Aggregate events into dashboards or reports
  • Feed events to AI agents via apps/agent-api
  • Set up monitoring alerts on rage/dead click spikes
  • Measure conversion funnel performance