@hanzo/event 0.3.1 → 0.3.3

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/dist/index.d.ts CHANGED
@@ -1,16 +1,84 @@
1
- import { C as Cohort, A as Attribution } from './core-CrbiAQhN.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-CrbiAQhN.js';
1
+ import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame } from './core-B1XEdWLd.js';
2
+ export { c as Analytics, d as AnalyticsConfig, E as EventKind, e as Exception, f as SentryLevel, T as Transport, W as WireEvent, g as createAnalytics } from './core-B1XEdWLd.js';
3
3
 
4
4
  /** Read the persisted first-touch attribution. */
5
5
  declare function getFirstTouch(): Attribution | undefined;
6
6
  /** Read persisted cohort dimensions. */
7
7
  declare function getCohort(): Cohort | undefined;
8
8
 
9
+ declare const VERSION = "0.3.3";
10
+
11
+ /**
12
+ * parseDsn parses "https://<version>:<hmac>@<host>/v1/sentry/<projectId>" into its
13
+ * public key + the derived envelope ingest URL. The key (which itself contains a
14
+ * ':') is taken verbatim as the userinfo — we do NOT split it as user:pass. The
15
+ * key rides ?sentry_key= (not the DSN in the body) because that is the credential
16
+ * channel the server trusts AND the only one sendBeacon can carry on unload.
17
+ * Returns null for anything malformed (fail-safe: the caller then stays inert).
18
+ */
19
+ declare function parseDsn(dsn: string | undefined | null): Dsn | null;
20
+ /**
21
+ * framesFromStack parses a browser Error.stack into Sentry frames, OLDEST-FIRST
22
+ * (Sentry orders caller->callee; the crash site is last — matching the server's
23
+ * pickCrashFrame). Handles both V8 ("at fn (file:li:co)") and
24
+ * Firefox/Safari ("fn@file:li:co"). Unparseable lines are skipped.
25
+ */
26
+ declare function framesFromStack(stack: string | undefined): SentryFrame[];
27
+ /** Identity carried onto every error event — the SAME ids analytics uses. */
28
+ interface ErrorIdentity {
29
+ /** OIDC sub (post-identify) or anon id. NEVER email/PII. */
30
+ userId?: string;
31
+ sessionId?: string;
32
+ product?: string;
33
+ release?: string;
34
+ environment?: string;
35
+ }
36
+ interface BuildEventInput {
37
+ error: unknown;
38
+ options?: CaptureErrorOptions;
39
+ identity: ErrorIdentity;
40
+ capturePII?: boolean;
41
+ /** Injectable for deterministic tests. */
42
+ now?: number;
43
+ id?: string;
44
+ }
45
+ /**
46
+ * buildSentryEvent turns a throwable + identity into a Sentry `event`. The message
47
+ * (the leak surface) is scrubbed client-side; the user is ONLY the stable subject
48
+ * id — never email/username/ip. Level defaults to error, or fatal for uncaught.
49
+ */
50
+ declare function buildSentryEvent(input: BuildEventInput): SentryEvent;
51
+ /**
52
+ * buildEnvelope frames a Sentry event into a newline-delimited envelope:
53
+ *
54
+ * {"event_id","dsn","sent_at"}\n
55
+ * {"type":"event","content_type":"application/json","length":N}\n
56
+ * <event json>\n
57
+ *
58
+ * The item is length-delimited (N = UTF-8 byte length) — the framing the server's
59
+ * parseEnvelope reads first (falling back to newline-delimited otherwise).
60
+ */
61
+ declare function buildEnvelope(event: SentryEvent, dsn: Dsn, sentAt?: string): string;
62
+
63
+ /** redactSecrets removes known secret shapes. Always applied. */
64
+ declare function redactSecrets(s: string): string;
65
+ /** scrubPII masks emails and IPs. Applied unless PII capture is enabled. */
66
+ declare function scrubPII(s: string): string;
67
+ /** scrubText applies the redaction policy to a free-text field. Input is capped
68
+ * first: unbounded text is a denial-of-service surface, not just a size problem. */
69
+ declare function scrubText(s: string | undefined, capturePII?: boolean): string;
70
+
9
71
  declare const EVENTS: {
10
72
  readonly SIGNUP_VIEWED: "signup_viewed";
11
73
  readonly SIGNUP_SUBMITTED: "signup_submitted";
12
74
  readonly SIGNUP_VERIFIED: "signup_verified";
13
75
  readonly SIGNUP_COMPLETED: "signup_completed";
76
+ /** A RETURNING user authenticated — the non-signup half of the IAM callback.
77
+ * Keeping it distinct is what stops returning logins from inflating signups. */
78
+ readonly LOGIN_COMPLETED: "login_completed";
79
+ /** Activation: the first moment of real value. ONE event for every product —
80
+ * the product-specific moment is the `action` property (api_call, app_live,
81
+ * chat_reply), never a new event name. */
14
82
  readonly FIRST_ACTION: "first_action";
15
83
  readonly WAITLIST_JOINED: "waitlist_joined";
16
84
  readonly WAITLIST_SHARED: "waitlist_shared";
@@ -23,24 +91,127 @@ declare const EVENTS: {
23
91
  readonly FEATURE_USED: "feature_used";
24
92
  readonly API_KEY_CREATED: "api_key_created";
25
93
  readonly APP_CREATED: "app_created";
26
- readonly DEPLOY_STARTED: "deploy_started";
27
94
  readonly PROJECT_CREATED: "project_created";
28
95
  readonly AGENT_CREATED: "agent_created";
29
96
  readonly CHAT_STARTED: "chat_started";
30
97
  readonly CHAT_MESSAGE_SENT: "chat_message_sent";
98
+ /** The user switched model/endpoint — the single strongest quality signal a
99
+ * chat surface emits (a switch usually follows a bad answer). */
100
+ readonly MODEL_SWITCHED: "model_switched";
31
101
  readonly TASK_STARTED: "task_started";
32
102
  readonly TASK_COMPLETED: "task_completed";
103
+ readonly BUILD_STARTED: "build_started";
104
+ /** A model finished producing an artifact (an app build, a chat reply, an agent
105
+ * run). Carries `durationMs` — the outcome event owns its own duration, so no
106
+ * paired start event is needed. */
107
+ readonly GENERATION_COMPLETED: "generation_completed";
108
+ readonly GENERATION_FAILED: "generation_failed";
109
+ readonly DEPLOY_STARTED: "deploy_started";
110
+ readonly DEPLOY_SUCCEEDED: "deploy_succeeded";
111
+ readonly DEPLOY_FAILED: "deploy_failed";
33
112
  };
34
113
  type EventName = (typeof EVENTS)[keyof typeof EVENTS];
35
114
  /** The reserved event name a pageview is stored under (server + read lens). */
36
115
  declare const PAGEVIEW = "$pageview";
37
116
 
117
+ /** The emitting surfaces — the closed set of `AnalyticsConfig.product` values.
118
+ * `product` is on every event, so a funnel scopes by product instead of every
119
+ * surface prefixing its event names. */
120
+ declare const PRODUCTS: readonly ["site", "app", "chat", "console", "admin", "cloud"];
121
+ type ProductId = (typeof PRODUCTS)[number];
122
+ interface FunnelStep {
123
+ /** An EVENTS value (or PAGEVIEW). */
124
+ event: string;
125
+ /** Human label for the Insights step. */
126
+ label: string;
127
+ /** Property equality that qualifies the step, e.g. first_action{action:'api_call'}. */
128
+ where?: {
129
+ property: string;
130
+ equals: string;
131
+ };
132
+ }
133
+ interface FunnelDef {
134
+ label: string;
135
+ /** Surface(s) the steps are emitted from — matched against the `product` field. */
136
+ products: ProductId[];
137
+ /**
138
+ * How steps are joined:
139
+ * • 'person' — steps join on distinctId (one browser, or one logged-in
140
+ * person across surfaces). The normal case.
141
+ * • 'aggregate' — steps are emitted on DIFFERENT origins by a LOGGED-OUT
142
+ * visitor, so there is no shared id: hanzo.ai, hanzo.app and
143
+ * hanzo.chat each mint their own anonymousId in their own
144
+ * storage. Read these as step-over-step COUNTS, never as a
145
+ * per-person conversion. Honest by construction.
146
+ */
147
+ join: 'person' | 'aggregate';
148
+ steps: FunnelStep[];
149
+ }
150
+ declare const FUNNELS: {
151
+ /** hanzo.ai: land → sign up. IAM hosts the form, so `signup_submitted` is the
152
+ * redirect INTO IAM and `signup_completed` is the return at /auth/callback. */
153
+ readonly signup: {
154
+ readonly label: "Signup";
155
+ readonly products: ["site"];
156
+ readonly join: "person";
157
+ readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
158
+ };
159
+ /** The developer activation path: an account is worth nothing until a key has
160
+ * made a call. `first_action{action:'api_call'}` is emitted SERVER-SIDE by
161
+ * Cloud on an org's first successful /v1 request — a browser cannot see it. */
162
+ readonly apiActivation: {
163
+ readonly label: "API activation";
164
+ readonly products: ["site", "cloud"];
165
+ readonly join: "person";
166
+ readonly steps: [FunnelStep, FunnelStep, FunnelStep];
167
+ };
168
+ /** Upgrade intent → revenue. `order_completed{kind:'plan'}` is the Sale goal. */
169
+ readonly upgrade: {
170
+ readonly label: "Upgrade";
171
+ readonly products: ["site", "app", "console"];
172
+ readonly join: "person";
173
+ readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
174
+ };
175
+ /** hanzo.app: describe → build → deploy → live URL. The whole product thesis
176
+ * in five steps; `deploy_succeeded` is the moment a live URL exists. */
177
+ readonly appShip: {
178
+ readonly label: "Describe → ship";
179
+ readonly products: ["app"];
180
+ readonly join: "person";
181
+ readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
182
+ };
183
+ /** hanzo.chat: visit → first message → answer. `generation_completed` is what
184
+ * separates "typed something" from "got value". */
185
+ readonly chatEngage: {
186
+ readonly label: "Chat engagement";
187
+ readonly products: ["chat"];
188
+ readonly join: "person";
189
+ readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
190
+ };
191
+ /** The cross-surface handoff: the hanzo.ai composer forwards its prompt to
192
+ * hanzo.chat. Two origins, two anonymousIds — so this is an AGGREGATE funnel.
193
+ * The join is the `referrerProduct` property hanzo.chat reads off `?hz_ref=`,
194
+ * which makes the drop-off measurable without any cross-domain identity. */
195
+ readonly siteToChat: {
196
+ readonly label: "Site → Chat handoff";
197
+ readonly products: ["site", "chat"];
198
+ readonly join: "aggregate";
199
+ readonly steps: [FunnelStep, FunnelStep, FunnelStep];
200
+ };
201
+ };
202
+ type FunnelId = keyof typeof FUNNELS;
203
+ /** eventsOf flattens a funnel to its ordered event names — what a goal's `funnel`
204
+ * field carries, so the steps are defined exactly once (here). */
205
+ declare function eventsOf(id: FunnelId): string[];
206
+
38
207
  interface GoalDef {
39
208
  /** Human label shown in Insights. */
40
209
  label: string;
41
210
  /** The event whose occurrence counts as the goal conversion. */
42
211
  event: string;
43
- /** Optional ordered funnel leading to the goal (for funnel insights). */
212
+ /** The funnel leading to the goal — an id into FUNNELS (see funnels.ts). */
213
+ funnelId?: FunnelId;
214
+ /** The ordered event names of `funnelId`, derived — never hand-written. */
44
215
  funnel?: string[];
45
216
  /** Optional property equality filter that qualifies the conversion. */
46
217
  filter?: {
@@ -48,7 +219,7 @@ interface GoalDef {
48
219
  equals: string;
49
220
  };
50
221
  }
51
- declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent', GoalDef>;
222
+ declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent' | 'activation', GoalDef>;
52
223
  interface CohortDef {
53
224
  /** The hanzo.events column the cohort dimension maps to. */
54
225
  field: string;
@@ -69,4 +240,4 @@ declare function hasAttribution(a: Attribution): boolean;
69
240
  /** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
70
241
  declare function isoWeek(d: Date): string;
71
242
 
72
- export { Attribution, COHORTS, Cohort, type CohortDef, EVENTS, type EventName, GOALS, type GoalDef, PAGEVIEW, deriveChannel, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution };
243
+ export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, EVENTS, type ErrorIdentity, type EventName, FUNNELS, type FunnelDef, type FunnelId, type FunnelStep, GOALS, type GoalDef, PAGEVIEW, PRODUCTS, type ProductId, SentryEvent, SentryFrame, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, eventsOf, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText };