hazo_umetrics
v0.1.2
Published
Product analytics for hazo apps — GA4 hybrid + first-party stat store + feature flags
Downloads
281
Maintainers
Readme
hazo_umetrics
Product analytics for hazo apps — GA4 hybrid + first-party stat store + feature flags.
Gives every hazo Next.js app one consistent, auth-gated way to understand user behaviour:
- GA4 hybrid — traffic, SEO, and conversion funnels read via the GA4 Data API; auto-provisioned via GA4 Admin API (M1).
- Stat store — first-party numeric metrics over time, collector pattern, sparkline history.
- Feature flags — deterministic weighted-variant bucketing, signed consent cookie, sticky assignment.
- A/B experiments — same engine as flags + significance (deferred until traffic justifies it).
All surfaces mount inside hazo_admin via the existing kind:'metrics' section; every read/write is scope_id-gated.
Status
M0 Core shipped. M0 Interactive test-app is live (Admin Mount, Event Log, Metrics, Autotest). See design/master_plan.md.
Installation
npm install hazo_umetricsPeer dependencies (install the ones you use):
npm install hazo_core hazo_connect hazo_auth hazo_secure hazo_audit hazo_api hazo_ui hazo_datavizThe
hazo_umetrics/uipanel renders its trend chart withhazo_dataviz(the chart primitives moved out ofhazo_uiin the hazo_ui 4.0.0 migration), so installhazo_datavizwhen you useMetricsPanel/StatTrend.
Required env vars for cookie signing (A/B assignment):
HAZO_UMETRICS_COOKIE_KEY_CURRENT=v1
HAZO_UMETRICS_COOKIE_KEY_v1=<base64-encoded 32-byte AES-256 key>Entry points
| Import path | Contents |
|---|---|
| hazo_umetrics | Server: recordStat, getLatestStat, getStatSeries, resolveVariant, weightedBucket, GA4/experiment stubs |
| hazo_umetrics/api | Route factories: createStatHandlers, createExperimentHandlers, createGa4ConnectHandlers, createQueryHandlers |
| hazo_umetrics/client | Client: HazoMetricsProvider, useMetrics, getVariant, isEnabled, identify, recordStat |
| hazo_umetrics/ui | UI: MetricsPanel, StatCards, StatTrend (uses hazo_dataviz LineChart — requires hazo_dataviz peer dep) |
Stat store
import { recordStat, getLatestStat, getStatSeries, registerStatCollector, runStatSnapshot } from 'hazo_umetrics';
// Append a reading (always inserts, never upserts)
await recordStat(adapter, { scope_id: 'my-app', metric_key: 'active_users', value: 42 });
// Latest value
const stat = await getLatestStat(adapter, { scope_id: 'my-app', metric_key: 'active_users' });
// Series (ascending by captured_at)
const series = await getStatSeries(adapter, { scope_id: 'my-app', metric_key: 'active_users', limit: 30 });
// Register a collector (runs in runStatSnapshot)
registerStatCollector({
key: 'active_users',
label: 'Active users',
format: 'count',
collect: async (scope_id) => countActiveUsers(scope_id),
});
// Run all collectors (call on a schedule via hazo_jobs in M1)
const results = await runStatSnapshot(adapter, { scope_id: 'my-app' });Feature flags
import { resolveVariant, getVariant, isEnabled } from 'hazo_umetrics';
// Evaluate a flag with logged-in user (sticky assignment)
const { variant } = await resolveVariant(adapter, {
experimentKey: 'new_dashboard',
scope_id: 'my-app',
subjectId: user.id,
consent: true,
});
// Boolean flag shorthand
const enabled = await isEnabled(adapter, { experimentKey: 'new_feature', scope_id, subjectId });API route factories
import { createStatHandlers, createExperimentHandlers } from 'hazo_umetrics/api';
const statHandlers = createStatHandlers({ getAdapter: () => myAdapter });
// → statHandlers.getStat, statHandlers.getStatSeries, statHandlers.recordStat
const expHandlers = createExperimentHandlers({ getAdapter: () => myAdapter });
// → expHandlers.listExperiments (metrics.view), expHandlers.startExperiment (metrics.manage)DB setup
Run db_setup_sqlite.sql (SQLite) or db_setup_postgres.sql (PostgreSQL) once. Creates 7 tables, all prefixed hazo_umetrics_.
Config
Copy config/hazo_umetrics_config.ini.sample to hazo_umetrics_config.ini and fill in values. Sections: [env], [ga4], [stats], [ab_cookie], [retention].
Tailwind / shadcn setup for UI components
MetricsPanel renders inside HazoUiDialog and uses shadcn token classes (bg-background, text-muted-foreground, etc.). If those tokens aren't defined, the dialog panel will appear transparent. In your globals.css:
@import "tailwindcss";
/* shadcn tokens — required for bg-background, border, muted colors etc. */
@import "../node_modules/hazo_ui/dist/styles.css";
@source "../node_modules/hazo_umetrics/dist";
@source "../node_modules/hazo_ui/dist";
@source "../node_modules/hazo_dataviz/dist"; /* StatTrend's LineChart lives here */
/* Tailwind v4 — map tokens to utilities */
@theme inline {
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-border: hsl(var(--border));
/* add remaining tokens as needed — see hazo_ui/dist/styles.css for the full list */
}(Adjust @import and @source depth to match your project's distance from node_modules.)
