@hanzo/event 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 hanzo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @hanzo/event
2
+
3
+ Tiny, batched product-analytics capture client for Hanzo surfaces. Emits
4
+ `pageview` / `event` / `identify` / `group` to **Hanzo Cloud** — never to a
5
+ third-party or to `insights-capture` directly. Cloud (`/v1/analytics` +
6
+ `/v1/tracker`) is the one front door and fans out to the insights/datastore
7
+ warehouse.
8
+
9
+ - **Batched** with a size + interval flush, and **beacon-on-unload**
10
+ (`sendBeacon` for cookie apps, `fetch(keepalive)` for token apps).
11
+ - **First-touch attribution**: UTM + referrer + `refCode` are parsed once and
12
+ persisted, then attached to every event.
13
+ - **Cohorts**: `signupWeek`, `channel`, `refCode` ride each event.
14
+ - **Tenant-safe**: the client NEVER sends an org/tenant — Cloud stamps it from
15
+ the validated session.
16
+ - **SSR-safe**: importing on the server is a no-op; it only acts in the browser.
17
+
18
+ ## Core (framework-agnostic)
19
+
20
+ ```ts
21
+ import { createAnalytics, EVENTS } from '@hanzo/event'
22
+
23
+ // Cookie/session apps (console, admin): same-origin, no token.
24
+ const analytics = createAnalytics({ product: 'console' })
25
+
26
+ // Token apps (app, site): give the cloud host + a bearer getter.
27
+ const analytics = createAnalytics({
28
+ product: 'app',
29
+ host: 'https://api.hanzo.ai',
30
+ getToken: () => localStorage.getItem('hanzo_access_token') ?? undefined,
31
+ })
32
+
33
+ analytics.pageview()
34
+ analytics.identify('user-42')
35
+ analytics.capture(EVENTS.SIGNUP_COMPLETED, { plan: 'pro' })
36
+ analytics.capture(EVENTS.ORDER_COMPLETED, { kind: 'plan' }, { productId: 'plan_pro', revenue: 49, quantity: 1, currency: 'usd' })
37
+ ```
38
+
39
+ ## React
40
+
41
+ ```tsx
42
+ 'use client'
43
+ import { AnalyticsProvider, useAnalytics, usePageview } from '@hanzo/event/react'
44
+ import { usePathname } from 'next/navigation'
45
+
46
+ export function Providers({ children }) {
47
+ return <AnalyticsProvider config={{ product: 'console' }}>{children}</AnalyticsProvider>
48
+ }
49
+
50
+ function RouteTracker() {
51
+ usePageview(usePathname()) // one pageview per navigation
52
+ return null
53
+ }
54
+
55
+ function UpgradeButton() {
56
+ const a = useAnalytics()
57
+ return <button onClick={() => a.capture(EVENTS.PLAN_CLICKED, { plan: 'pro' })}>Upgrade</button>
58
+ }
59
+ ```
60
+
61
+ ## Goals & cohorts
62
+
63
+ `GOALS` and `COHORTS` (see `goals.ts`) are the shared, machine-readable insights
64
+ spec: **Signup** (funnel view→submit→verify→first-action), **Sale** (a
65
+ `order_completed` with `kind=plan`), and **Upgrade Intent** (`plan_clicked`,
66
+ funnel from `pricing_viewed`). Cohort fields map to the `signup_week`,
67
+ `channel`, and `ref_code` columns of `hanzo.events`.
@@ -0,0 +1,157 @@
1
+ /** The event kinds — the closed set the server understands. An error is just
2
+ * another event on the one stream (lensed to the error-tracking view). */
3
+ type EventKind = 'pageview' | 'event' | 'identify' | 'group' | 'error';
4
+ /** A captured exception. Carried on a `type:'error'` event; the server lenses it
5
+ * into the error-tracking view (sentry.hanzo.ai). */
6
+ interface Exception {
7
+ /** Constructor/class name, e.g. "TypeError". */
8
+ type?: string;
9
+ /** The error message. */
10
+ message: string;
11
+ /** Stack trace when available. */
12
+ stack?: string;
13
+ /** false = an unhandled/global error (window.onerror, unhandledrejection);
14
+ * true = a caught error the app chose to report. Defaults true. */
15
+ handled?: boolean;
16
+ }
17
+ /** First-touch marketing attribution, parsed once and persisted. */
18
+ interface Attribution {
19
+ utm: {
20
+ source?: string;
21
+ medium?: string;
22
+ campaign?: string;
23
+ term?: string;
24
+ content?: string;
25
+ };
26
+ referrer?: string;
27
+ refCode?: string;
28
+ /** Derived acquisition channel: direct | organic | paid | social | referral. */
29
+ channel?: string;
30
+ }
31
+ /** Cohort dimensions carried on every event once known (see goals.ts COHORTS). */
32
+ interface Cohort {
33
+ /** ISO week the person first signed up, e.g. "2026-W28". */
34
+ signupWeek?: string;
35
+ channel?: string;
36
+ refCode?: string;
37
+ }
38
+ /** One event as sent on the wire. tenant/org is NEVER set here — the server
39
+ * stamps it from the validated session. */
40
+ interface WireEvent {
41
+ messageId: string;
42
+ type: EventKind;
43
+ event?: string;
44
+ timestamp: string;
45
+ distinctId?: string;
46
+ anonymousId?: string;
47
+ personId?: string;
48
+ sessionId?: string;
49
+ product?: string;
50
+ url?: string;
51
+ path?: string;
52
+ referrer?: string;
53
+ utm?: Attribution['utm'];
54
+ refCode?: string;
55
+ channel?: string;
56
+ groupId?: string;
57
+ signupWeek?: string;
58
+ productId?: string;
59
+ quantity?: number;
60
+ revenue?: number;
61
+ currency?: string;
62
+ /** Set on `type:'error'` events — the captured exception. */
63
+ error?: Exception;
64
+ properties?: Record<string, unknown>;
65
+ library?: string;
66
+ libraryVersion?: string;
67
+ }
68
+ /** Injectable transports — overridden in tests; default in core.ts uses fetch. */
69
+ interface Transport {
70
+ /** Durable POST usable during page unload (fetch keepalive / sendBeacon). */
71
+ send(url: string, body: string, opts: {
72
+ beacon: boolean;
73
+ token?: string;
74
+ }): void;
75
+ }
76
+ interface AnalyticsConfig {
77
+ /** Cloud base URL. Same-origin ("") for cookie-auth apps (console/admin);
78
+ * e.g. "https://api.hanzo.ai" for bearer apps (app/site). */
79
+ host?: string;
80
+ /** Emitting surface: console | chat | app | site | admin. */
81
+ product: string;
82
+ /** Bearer token provider for token-auth apps. Omit for cookie/session apps
83
+ * (the client then relies on same-origin credentials). */
84
+ getToken?: () => string | undefined | null;
85
+ /** Max events buffered before an automatic flush. */
86
+ batchSize?: number;
87
+ /** Auto-flush cadence in ms. */
88
+ flushIntervalMs?: number;
89
+ /** Turn the client off entirely (e.g. opt-out / DNT). Defaults to enabled. */
90
+ enabled?: boolean;
91
+ /** Auto-capture unhandled errors + promise rejections (window.onerror,
92
+ * unhandledrejection) as error events. Browser-only. Defaults to enabled —
93
+ * this is what makes the client a drop-in @sentry replacement. */
94
+ captureErrors?: boolean;
95
+ /** Override the transport (tests). */
96
+ transport?: Transport;
97
+ /** Debug logging. */
98
+ debug?: boolean;
99
+ }
100
+
101
+ declare const VERSION = "0.2.0";
102
+ declare class Analytics {
103
+ private cfg;
104
+ private transport;
105
+ private queue;
106
+ private timer;
107
+ private personId?;
108
+ private attribution;
109
+ private cohort;
110
+ private started;
111
+ constructor(config: AnalyticsConfig);
112
+ /** init is idempotent and browser-only for its side effects: capture first-touch
113
+ * attribution, hydrate cohort, and register the unload flush. Safe to call from
114
+ * a React effect on every render. */
115
+ init(): void;
116
+ /** identify binds the current visitor to a stable person id (post-login). */
117
+ identify(personId: string, traits?: Record<string, unknown>): void;
118
+ /** group associates the visitor with an org/team (analytics grouping, not the
119
+ * server tenant — the server still derives tenant from the session). */
120
+ group(groupId: string, traits?: Record<string, unknown>): void;
121
+ /** pageview records a $pageview for the current (or given) location. */
122
+ pageview(path?: string, properties?: Record<string, unknown>): void;
123
+ /** capture records a named product event with optional properties. Commerce
124
+ * fields (productId/quantity/revenue/currency) may be passed for order events. */
125
+ capture(event: string, properties?: Record<string, unknown>, commerce?: Pick<WireEvent, 'productId' | 'quantity' | 'revenue' | 'currency'>): void;
126
+ /** track is an alias of capture (Segment familiarity). */
127
+ track: (event: string, properties?: Record<string, unknown>, commerce?: Pick<WireEvent, "productId" | "quantity" | "revenue" | "currency">) => void;
128
+ /** captureError records an exception as a first-class error event — the ONE
129
+ * error path (subsumes @sentry). A caught error, an unhandled rejection, or a
130
+ * manual report all become a type:'error' event on the same stream, lensed to
131
+ * the error-tracking view server-side. Never throws back into the app; errors
132
+ * are higher-signal than pageviews, so it flushes promptly (a crash may unload
133
+ * the page moments later). */
134
+ captureError(err: unknown, context?: {
135
+ handled?: boolean;
136
+ properties?: Record<string, unknown>;
137
+ }): void;
138
+ /** captureException — @sentry-familiar alias of captureError. */
139
+ captureException: (err: unknown, context?: {
140
+ handled?: boolean;
141
+ properties?: Record<string, unknown>;
142
+ }) => void;
143
+ /** setCohort persists cohort dimensions (e.g. signupWeek at signup) so they ride
144
+ * every subsequent event. */
145
+ setCohort(patch: Cohort): void;
146
+ /** flush drains the buffer to the server as one batch. beacon=true uses the
147
+ * unload-safe path. */
148
+ flush(beacon?: boolean): void;
149
+ private enqueue;
150
+ private build;
151
+ private schedule;
152
+ private clearTimer;
153
+ }
154
+ /** createAnalytics builds a client instance. Most apps use one shared instance. */
155
+ declare function createAnalytics(config: AnalyticsConfig): Analytics;
156
+
157
+ export { type Attribution as A, type Cohort as C, type EventKind as E, type Transport as T, VERSION as V, type WireEvent as W, Analytics as a, type AnalyticsConfig as b, type Exception as c, createAnalytics as d };
@@ -0,0 +1,157 @@
1
+ /** The event kinds — the closed set the server understands. An error is just
2
+ * another event on the one stream (lensed to the error-tracking view). */
3
+ type EventKind = 'pageview' | 'event' | 'identify' | 'group' | 'error';
4
+ /** A captured exception. Carried on a `type:'error'` event; the server lenses it
5
+ * into the error-tracking view (sentry.hanzo.ai). */
6
+ interface Exception {
7
+ /** Constructor/class name, e.g. "TypeError". */
8
+ type?: string;
9
+ /** The error message. */
10
+ message: string;
11
+ /** Stack trace when available. */
12
+ stack?: string;
13
+ /** false = an unhandled/global error (window.onerror, unhandledrejection);
14
+ * true = a caught error the app chose to report. Defaults true. */
15
+ handled?: boolean;
16
+ }
17
+ /** First-touch marketing attribution, parsed once and persisted. */
18
+ interface Attribution {
19
+ utm: {
20
+ source?: string;
21
+ medium?: string;
22
+ campaign?: string;
23
+ term?: string;
24
+ content?: string;
25
+ };
26
+ referrer?: string;
27
+ refCode?: string;
28
+ /** Derived acquisition channel: direct | organic | paid | social | referral. */
29
+ channel?: string;
30
+ }
31
+ /** Cohort dimensions carried on every event once known (see goals.ts COHORTS). */
32
+ interface Cohort {
33
+ /** ISO week the person first signed up, e.g. "2026-W28". */
34
+ signupWeek?: string;
35
+ channel?: string;
36
+ refCode?: string;
37
+ }
38
+ /** One event as sent on the wire. tenant/org is NEVER set here — the server
39
+ * stamps it from the validated session. */
40
+ interface WireEvent {
41
+ messageId: string;
42
+ type: EventKind;
43
+ event?: string;
44
+ timestamp: string;
45
+ distinctId?: string;
46
+ anonymousId?: string;
47
+ personId?: string;
48
+ sessionId?: string;
49
+ product?: string;
50
+ url?: string;
51
+ path?: string;
52
+ referrer?: string;
53
+ utm?: Attribution['utm'];
54
+ refCode?: string;
55
+ channel?: string;
56
+ groupId?: string;
57
+ signupWeek?: string;
58
+ productId?: string;
59
+ quantity?: number;
60
+ revenue?: number;
61
+ currency?: string;
62
+ /** Set on `type:'error'` events — the captured exception. */
63
+ error?: Exception;
64
+ properties?: Record<string, unknown>;
65
+ library?: string;
66
+ libraryVersion?: string;
67
+ }
68
+ /** Injectable transports — overridden in tests; default in core.ts uses fetch. */
69
+ interface Transport {
70
+ /** Durable POST usable during page unload (fetch keepalive / sendBeacon). */
71
+ send(url: string, body: string, opts: {
72
+ beacon: boolean;
73
+ token?: string;
74
+ }): void;
75
+ }
76
+ interface AnalyticsConfig {
77
+ /** Cloud base URL. Same-origin ("") for cookie-auth apps (console/admin);
78
+ * e.g. "https://api.hanzo.ai" for bearer apps (app/site). */
79
+ host?: string;
80
+ /** Emitting surface: console | chat | app | site | admin. */
81
+ product: string;
82
+ /** Bearer token provider for token-auth apps. Omit for cookie/session apps
83
+ * (the client then relies on same-origin credentials). */
84
+ getToken?: () => string | undefined | null;
85
+ /** Max events buffered before an automatic flush. */
86
+ batchSize?: number;
87
+ /** Auto-flush cadence in ms. */
88
+ flushIntervalMs?: number;
89
+ /** Turn the client off entirely (e.g. opt-out / DNT). Defaults to enabled. */
90
+ enabled?: boolean;
91
+ /** Auto-capture unhandled errors + promise rejections (window.onerror,
92
+ * unhandledrejection) as error events. Browser-only. Defaults to enabled —
93
+ * this is what makes the client a drop-in @sentry replacement. */
94
+ captureErrors?: boolean;
95
+ /** Override the transport (tests). */
96
+ transport?: Transport;
97
+ /** Debug logging. */
98
+ debug?: boolean;
99
+ }
100
+
101
+ declare const VERSION = "0.2.0";
102
+ declare class Analytics {
103
+ private cfg;
104
+ private transport;
105
+ private queue;
106
+ private timer;
107
+ private personId?;
108
+ private attribution;
109
+ private cohort;
110
+ private started;
111
+ constructor(config: AnalyticsConfig);
112
+ /** init is idempotent and browser-only for its side effects: capture first-touch
113
+ * attribution, hydrate cohort, and register the unload flush. Safe to call from
114
+ * a React effect on every render. */
115
+ init(): void;
116
+ /** identify binds the current visitor to a stable person id (post-login). */
117
+ identify(personId: string, traits?: Record<string, unknown>): void;
118
+ /** group associates the visitor with an org/team (analytics grouping, not the
119
+ * server tenant — the server still derives tenant from the session). */
120
+ group(groupId: string, traits?: Record<string, unknown>): void;
121
+ /** pageview records a $pageview for the current (or given) location. */
122
+ pageview(path?: string, properties?: Record<string, unknown>): void;
123
+ /** capture records a named product event with optional properties. Commerce
124
+ * fields (productId/quantity/revenue/currency) may be passed for order events. */
125
+ capture(event: string, properties?: Record<string, unknown>, commerce?: Pick<WireEvent, 'productId' | 'quantity' | 'revenue' | 'currency'>): void;
126
+ /** track is an alias of capture (Segment familiarity). */
127
+ track: (event: string, properties?: Record<string, unknown>, commerce?: Pick<WireEvent, "productId" | "quantity" | "revenue" | "currency">) => void;
128
+ /** captureError records an exception as a first-class error event — the ONE
129
+ * error path (subsumes @sentry). A caught error, an unhandled rejection, or a
130
+ * manual report all become a type:'error' event on the same stream, lensed to
131
+ * the error-tracking view server-side. Never throws back into the app; errors
132
+ * are higher-signal than pageviews, so it flushes promptly (a crash may unload
133
+ * the page moments later). */
134
+ captureError(err: unknown, context?: {
135
+ handled?: boolean;
136
+ properties?: Record<string, unknown>;
137
+ }): void;
138
+ /** captureException — @sentry-familiar alias of captureError. */
139
+ captureException: (err: unknown, context?: {
140
+ handled?: boolean;
141
+ properties?: Record<string, unknown>;
142
+ }) => void;
143
+ /** setCohort persists cohort dimensions (e.g. signupWeek at signup) so they ride
144
+ * every subsequent event. */
145
+ setCohort(patch: Cohort): void;
146
+ /** flush drains the buffer to the server as one batch. beacon=true uses the
147
+ * unload-safe path. */
148
+ flush(beacon?: boolean): void;
149
+ private enqueue;
150
+ private build;
151
+ private schedule;
152
+ private clearTimer;
153
+ }
154
+ /** createAnalytics builds a client instance. Most apps use one shared instance. */
155
+ declare function createAnalytics(config: AnalyticsConfig): Analytics;
156
+
157
+ export { type Attribution as A, type Cohort as C, type EventKind as E, type Transport as T, VERSION as V, type WireEvent as W, Analytics as a, type AnalyticsConfig as b, type Exception as c, createAnalytics as d };
@@ -0,0 +1,72 @@
1
+ import { C as Cohort, A as Attribution } from './core-DDGwms7M.cjs';
2
+ export { a as Analytics, b as AnalyticsConfig, E as EventKind, c as Exception, T as Transport, V as VERSION, W as WireEvent, d as createAnalytics } from './core-DDGwms7M.cjs';
3
+
4
+ /** Read the persisted first-touch attribution. */
5
+ declare function getFirstTouch(): Attribution | undefined;
6
+ /** Read persisted cohort dimensions. */
7
+ declare function getCohort(): Cohort | undefined;
8
+
9
+ declare const EVENTS: {
10
+ readonly SIGNUP_VIEWED: "signup_viewed";
11
+ readonly SIGNUP_SUBMITTED: "signup_submitted";
12
+ readonly SIGNUP_VERIFIED: "signup_verified";
13
+ readonly SIGNUP_COMPLETED: "signup_completed";
14
+ readonly FIRST_ACTION: "first_action";
15
+ readonly WAITLIST_JOINED: "waitlist_joined";
16
+ readonly WAITLIST_SHARED: "waitlist_shared";
17
+ readonly REFERRAL_USED: "referral_used";
18
+ readonly REFERRAL_CLAIMED: "referral_claimed";
19
+ readonly PRICING_VIEWED: "pricing_viewed";
20
+ readonly PLAN_CLICKED: "plan_clicked";
21
+ readonly CHECKOUT_STARTED: "checkout_started";
22
+ readonly ORDER_COMPLETED: "order_completed";
23
+ readonly FEATURE_USED: "feature_used";
24
+ readonly API_KEY_CREATED: "api_key_created";
25
+ readonly APP_CREATED: "app_created";
26
+ readonly DEPLOY_STARTED: "deploy_started";
27
+ readonly PROJECT_CREATED: "project_created";
28
+ readonly AGENT_CREATED: "agent_created";
29
+ readonly CHAT_STARTED: "chat_started";
30
+ readonly CHAT_MESSAGE_SENT: "chat_message_sent";
31
+ readonly TASK_STARTED: "task_started";
32
+ readonly TASK_COMPLETED: "task_completed";
33
+ };
34
+ type EventName = (typeof EVENTS)[keyof typeof EVENTS];
35
+ /** The reserved event name a pageview is stored under (server + read lens). */
36
+ declare const PAGEVIEW = "$pageview";
37
+
38
+ interface GoalDef {
39
+ /** Human label shown in Insights. */
40
+ label: string;
41
+ /** The event whose occurrence counts as the goal conversion. */
42
+ event: string;
43
+ /** Optional ordered funnel leading to the goal (for funnel insights). */
44
+ funnel?: string[];
45
+ /** Optional property equality filter that qualifies the conversion. */
46
+ filter?: {
47
+ property: string;
48
+ equals: string;
49
+ };
50
+ }
51
+ declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent', GoalDef>;
52
+ interface CohortDef {
53
+ /** The hanzo.events column the cohort dimension maps to. */
54
+ field: string;
55
+ label: string;
56
+ }
57
+ declare const COHORTS: Record<'signupWeek' | 'channel' | 'refCode', CohortDef>;
58
+
59
+ /** parseAttribution reads UTM params + ref/refCode from a query string and pairs
60
+ * them with the referrer. `search` is a location.search value ("?utm_source=…"). */
61
+ declare function parseAttribution(search: string, referrer: string): Attribution;
62
+ /** deriveChannel classifies the visit: paid | referral | social | organic | direct. */
63
+ declare function deriveChannel(a: Attribution): string;
64
+ /** hostOf extracts a bare lowercase host from a URL; "" when unparseable. */
65
+ declare function hostOf(raw?: string): string;
66
+ /** hasAttribution reports whether anything was captured (so we don't persist an
67
+ * empty first-touch that would shadow a later real one). */
68
+ declare function hasAttribution(a: Attribution): boolean;
69
+ /** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
70
+ declare function isoWeek(d: Date): string;
71
+
72
+ export { Attribution, COHORTS, Cohort, type CohortDef, EVENTS, type EventName, GOALS, type GoalDef, PAGEVIEW, deriveChannel, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution };
@@ -0,0 +1,72 @@
1
+ import { C as Cohort, A as Attribution } from './core-DDGwms7M.js';
2
+ export { a as Analytics, b as AnalyticsConfig, E as EventKind, c as Exception, T as Transport, V as VERSION, W as WireEvent, d as createAnalytics } from './core-DDGwms7M.js';
3
+
4
+ /** Read the persisted first-touch attribution. */
5
+ declare function getFirstTouch(): Attribution | undefined;
6
+ /** Read persisted cohort dimensions. */
7
+ declare function getCohort(): Cohort | undefined;
8
+
9
+ declare const EVENTS: {
10
+ readonly SIGNUP_VIEWED: "signup_viewed";
11
+ readonly SIGNUP_SUBMITTED: "signup_submitted";
12
+ readonly SIGNUP_VERIFIED: "signup_verified";
13
+ readonly SIGNUP_COMPLETED: "signup_completed";
14
+ readonly FIRST_ACTION: "first_action";
15
+ readonly WAITLIST_JOINED: "waitlist_joined";
16
+ readonly WAITLIST_SHARED: "waitlist_shared";
17
+ readonly REFERRAL_USED: "referral_used";
18
+ readonly REFERRAL_CLAIMED: "referral_claimed";
19
+ readonly PRICING_VIEWED: "pricing_viewed";
20
+ readonly PLAN_CLICKED: "plan_clicked";
21
+ readonly CHECKOUT_STARTED: "checkout_started";
22
+ readonly ORDER_COMPLETED: "order_completed";
23
+ readonly FEATURE_USED: "feature_used";
24
+ readonly API_KEY_CREATED: "api_key_created";
25
+ readonly APP_CREATED: "app_created";
26
+ readonly DEPLOY_STARTED: "deploy_started";
27
+ readonly PROJECT_CREATED: "project_created";
28
+ readonly AGENT_CREATED: "agent_created";
29
+ readonly CHAT_STARTED: "chat_started";
30
+ readonly CHAT_MESSAGE_SENT: "chat_message_sent";
31
+ readonly TASK_STARTED: "task_started";
32
+ readonly TASK_COMPLETED: "task_completed";
33
+ };
34
+ type EventName = (typeof EVENTS)[keyof typeof EVENTS];
35
+ /** The reserved event name a pageview is stored under (server + read lens). */
36
+ declare const PAGEVIEW = "$pageview";
37
+
38
+ interface GoalDef {
39
+ /** Human label shown in Insights. */
40
+ label: string;
41
+ /** The event whose occurrence counts as the goal conversion. */
42
+ event: string;
43
+ /** Optional ordered funnel leading to the goal (for funnel insights). */
44
+ funnel?: string[];
45
+ /** Optional property equality filter that qualifies the conversion. */
46
+ filter?: {
47
+ property: string;
48
+ equals: string;
49
+ };
50
+ }
51
+ declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent', GoalDef>;
52
+ interface CohortDef {
53
+ /** The hanzo.events column the cohort dimension maps to. */
54
+ field: string;
55
+ label: string;
56
+ }
57
+ declare const COHORTS: Record<'signupWeek' | 'channel' | 'refCode', CohortDef>;
58
+
59
+ /** parseAttribution reads UTM params + ref/refCode from a query string and pairs
60
+ * them with the referrer. `search` is a location.search value ("?utm_source=…"). */
61
+ declare function parseAttribution(search: string, referrer: string): Attribution;
62
+ /** deriveChannel classifies the visit: paid | referral | social | organic | direct. */
63
+ declare function deriveChannel(a: Attribution): string;
64
+ /** hostOf extracts a bare lowercase host from a URL; "" when unparseable. */
65
+ declare function hostOf(raw?: string): string;
66
+ /** hasAttribution reports whether anything was captured (so we don't persist an
67
+ * empty first-touch that would shadow a later real one). */
68
+ declare function hasAttribution(a: Attribution): boolean;
69
+ /** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
70
+ declare function isoWeek(d: Date): string;
71
+
72
+ export { Attribution, COHORTS, Cohort, type CohortDef, EVENTS, type EventName, GOALS, type GoalDef, PAGEVIEW, deriveChannel, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution };