@classytic/analytics
v0.2.0
Published
Framework-agnostic web + server analytics. One typed track() fans out to GA4, Meta Pixel/Conversions API, TikTok and GTM; browser↔server event_id dedup, consent gating. Commerce taxonomy (view_item…purchase) and a React provider ship as tree-shakeable sub
Downloads
44
Readme
@classytic/analytics
Framework-agnostic web + server analytics. One typed track() fans out to GA4,
Meta Pixel/Conversions API, TikTok and GTM — with browser↔server event_id
deduplication and per-purpose consent gating. Zero runtime dependencies.
The commerce event taxonomy (view_item … purchase) and a React provider ship
as tree-shakeable subpaths, so a non-commerce app pays for neither.
Subpaths
| Import | What |
|---|---|
| @classytic/analytics | core: createAnalytics, ConsentStore, browser adapter factories |
| @classytic/analytics/commerce | GA4-shaped ecommerce constructors + minor→major money mappers |
| @classytic/analytics/react | <AnalyticsProvider>, useTrack, usePageViews, useTrackOnce, useConsent |
| @classytic/analytics/server | Meta CAPI / GA4 MP / TikTok Events-API body builders + hashUserData |
Platform layout
src/platforms/
├── google/ ga4.ts (browser) · measurement-protocol.ts (server)
├── meta/ pixel.ts (browser) · capi.ts (server) · events.ts (translation)
├── tiktok/ pixel.ts (browser) · events-api.ts (server) · events.ts
└── gtm/ index.ts (dataLayer)One directory per platform; browser + server twins + the name/param translation table live together, so adding a channel is a self-contained folder.
React quick start
import { AnalyticsProvider } from '@classytic/analytics/react';
<AnalyticsProvider config={{
google: { measurementId: 'G-XXXX' },
meta: { pixelId: '1234567890' },
}}>
{children}
</AnalyticsProvider>import { useTrack, useTrackOnce } from '@classytic/analytics/react';
import { addToCart, purchase } from '@classytic/analytics/commerce';
// product page
const track = useTrack();
track(addToCart({ lines: [{ id: sku, name, unitPriceMinor: price }], currency: 'BDT' }));
// success page — idempotent across refresh / StrictMode
useTrackOnce(orderId, () => purchase({
transactionId: orderId, lines, currency: 'BDT', totalMinor: total,
}));Money
Callers pass integer minor units (paisa/cents) everywhere — the same
convention as the rest of the platform. Conversion to the major units pixels
expect happens exactly once, at this boundary (toMajor, default exponent 2).
Dedup
track() returns the resolved event with its eventId. Forward that id to the
server twin (Meta CAPI event_id, GA4 MP params.event_id) so the platform
collapses browser + server into one conversion instead of double-counting.
