@growth-labs/analytics
v0.10.0
Published
WAE-primary analytics with auto-injected behavioral tracking, attribution, and conversion event routing. Requires **Cloudflare Workers Paid plan** ($5/mo) for WAE access. No D1 fallback for behavioral events.
Readme
@growth-labs/analytics
WAE-primary analytics with auto-injected behavioral tracking, attribution, and conversion event routing. Requires Cloudflare Workers Paid plan ($5/mo) for WAE access. No D1 fallback for behavioral events.
Config
import analytics from '@growth-labs/analytics'
analytics({
siteId: 'fedweek', // Cookie prefix + WAE index
waeBinding: 'ANALYTICS', // WAE binding in wrangler.toml
d1Binding: 'SITE_DB', // D1 for conversion events
cookiePrefix: 'fedweek', // → fedweek_vid, fedweek_sid, fedweek_attr
session: { timeoutMinutes: 30, engagedThresholdSeconds: 10 },
scroll: { thresholds: [25, 50, 75, 90] },
media: { thresholds: [10, 25, 50, 75, 100] },
ecommerce: { enabled: true, currencyCode: 'USD' },
webVitals: { enabled: true },
socialSharing: {
enabled: true,
defaultProperty: 'fronts',
campaigns: ['fronts__membership-push__2026q2'],
},
})What It Injects
Middleware (server-side, every request):
- Reads/creates visitor ID cookie (
{cookiePrefix}_vid) - Manages session cookie (
{cookiePrefix}_sid) - Parses UTM params + referrer → attribution cookie (
{cookiePrefix}_attr) - Writes
page_viewto WAE (anonymous if no consent) - Bot filtering (rejects known bot user-agents)
Routes:
POST /api/analytics/event— beacon receiver for client-side events- Optional social routes when
socialSharing.enabledis true:/admin/share,/api/share-links,/api/social-spend/import,/go/[shareLinkId]
Client script (auto-injected):
- Scroll depth tracking (fires at 25/50/75/90%)
- Media progress tracking (native audio/video + Vidstack, including dynamic elements)
- Core Web Vitals (LCP, INP, CLS)
- Outbound link clicks, file downloads, 404 detection
- Form tracking (start, submit, abandon)
- E-commerce events (product_viewed, add_to_cart, checkout_success)
- Search events (search query + result count)
window.glAnalytics.track(eventName, data),.identify(userId), and.reset()public API
Event Routing
| Event type | Destination | Examples | |------------|-------------|---------| | Behavioral | WAE only | page_view, scroll_50, page_exit | | Conversion | WAE + D1 | newsletter_subscribed, checkout_success, form_submit | | System | WAE only | session_start, session_engaged | | Vital | WAE only | web_vital_lcp, web_vital_inp, web_vital_cls |
WAE Schema
20 blobs + 12 doubles per data point. Key fields:
- blob1: eventName, blob2: siteId, blob3: visitorId, blob5: pageUrl
- blob8-10: UTM params, blob11: derivedSource, blob12: country
- blob13: deviceType, blob16: contentSlug, blob18: eventCategory
- double1: timestamp, double2: eventValue / page-exit loaded seconds, double3: web vital value, double5: active/watch/listen seconds
Server-side helpers should use the exported WAE builders/writers instead
of ad hoc schemas. writeAuthEvent() is available for non-Astro auth
Workers; buildWAEDataPoint() remains the low-level schema builder for
package-owned event emitters.
Utility Exports
@growth-labs/analytics/utils re-exports the core event helpers plus
the v0.3 WAE infrastructure primitives:
writeAuthEvent()for best-effort auth success rows from Worker-only auth flows.trackServerEvent()for Astro/Worker server routes that need the same WAE + D1 attribution path as/api/analytics/event.appendCheckoutAttribution()for LemonSqueezy checkout links with visitor/session/first/latest/content custom data and discount code propagation.- Social attribution helpers:
buildSocialShareUrl(),parseSocialUtmContent(),extractShareIdFromUtmContent(),classifySocialShare(),parsePlatformClickEvidence(), andbuildPlatformTrackingFields(). queryWAE(),fetchWAE(), andassertValidWaeDataset()for Cloudflare Analytics Engine reads.buildQueryContext(),resolveDataSourceContext(),fetchSiteD1(),finalizeDataSourceStatus(), andpickDataForStatus()for analytics-platform-style live/demo/error data-source handling.cachedFetchWAE()/waeCacheKey()for KV-backed dashboard query caching.validateCloudflareCredentials(),probeWaeDataset(), andprobeSiteD1()for setup probes with injectable fetch/query hooks.compactNumber(),formatDate(),formatDuration(),formatPercent(), andformatPercentChange()for dashboard display formatting.encryptSecret()/decryptSecret()using Web Crypto AES-GCM.resolveWindow()/windowSqlFragment()for closed UTC reporting windows that exclude partial today, plus rolling sub-day windows ({ minutes: N },{ hours: N }) anchored tonow()for short-interval scheduled digests.@growth-labs/analytics/client/helpersfor consent-aware browser instrumentation of search, newsletter conversion, share/related links, client errors, HTTP error mapping, and redirect attribution.
D1 Tables (prefixed gl_)
gl_conversion_events— conversion event log with e-commerce fieldsgl_analytics_visitors— visitor summary (session count, first/last seen)gl_content_progress— legacy table retained by migrations; high-volume reading/watching progress now writes to WAE onlygl_identity_links— anonymous visitor to authenticated identity bridgegl_conversion_attribution_touches— bounded touch history materialized only when a conversion is writtengl_social_authors— public, non-PII author slug/id metadatagl_social_handles— social account metadata with brand-account flaggl_share_links— generated social links and canonical UTM fieldsgl_social_spend_daily— daily spend/ad-object metadata mapped to share links
Social Attribution
Social is measured through the normal attribution pipeline, not a separate analytics layer. The link builder emits canonical UTMs:
utm_source: platform, e.g.x,instagram,facebookutm_medium:socialorpaid_socialutm_campaign:property__initiative__periodutm_content:sl__{property}__{platform}__{handle_id}__{author_id}__{share_id}utm_term: placement, e.g.feed,story,bio,ad
The /admin/share route is for an admin operator. Writers do not log in;
the operator explicitly selects or type-to-creates the human author, and
the generated gl_share_links.created_by records the operator separately
from the marketing author_id.
Wrangler Bindings
[analytics_engine_datasets]
binding = "ANALYTICS"
[[d1_databases]]
binding = "SITE_DB"
database_id = "..."Runtime Behavior
Server entrypoints side-effect import virtual:growth-labs/analytics/config, so package state seeds itself in the Worker that actually handles the request.
Bindings resolve from the standard Cloudflare surfaces:
cloudflare:workersfor env bindingsAstro.locals.cfContext.waitUntil()for background writes
Legacy locals.runtime is still accepted as a fallback, but consumers should not patch @growth-labs/analytics/state or add package-specific runtime-state middleware.
Consent Integration
Optional peer dependency on @growth-labs/consent. If installed:
- No consent → anonymous page_view only (no visitor ID cookie, no attribution)
- With consent → full tracking (cookies, attribution, all events)
If consent package NOT installed → tracking runs unconditionally.
Client-Side Event API
// gl:* CustomEvent pattern for loose coupling
document.dispatchEvent(new CustomEvent('gl:track', {
detail: { event: 'product_viewed', data: { productId: '123' } }
}))
// Or direct API
window.glAnalytics.track('custom_event', { label: 'something' })
window.glAnalytics.identify('user_123')
window.glAnalytics.reset()Key Patterns
- Virtual module:
virtual:growth-labs/analytics/config - Runtime config self-seeds in middleware and routes via the virtual module
gl:*CustomEvent pattern ondocumentfor loose coupling between client-side packages. Package-localgl:*events are not automatically forwarded; usewindow.glAnalytics.track(...)when an event should be written to WAE.- Package-owned WAE writes use the shared schema helpers; auth Workers use
writeAuthEvent() - Bot filtering runs before any tracking
gl_prefix on all D1 table names
