@hanzo/event 0.3.2 → 0.3.4
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/README.md +20 -0
- package/TAXONOMY.md +257 -0
- package/dist/index.cjs +242 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +147 -5
- package/dist/index.d.ts +147 -5
- package/dist/index.mjs +238 -81
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +43 -15
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +43 -15
- package/dist/react.mjs.map +1 -1
- package/package.json +4 -3
- package/src/core.test.ts +9 -4
- package/src/core.ts +21 -13
- package/src/dsn.test.ts +62 -0
- package/src/dsn.ts +45 -0
- package/src/events.ts +30 -1
- package/src/funnels.test.ts +92 -0
- package/src/funnels.ts +154 -0
- package/src/goals.ts +25 -11
- package/src/index.ts +3 -0
- package/src/scrub.ts +6 -1
- package/src/sentry.test.ts +52 -0
- package/src/sentry.ts +33 -3
- package/src/version.ts +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ 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.
|
|
9
|
+
declare const VERSION = "0.3.3";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* parseDsn parses "https://<version>:<hmac>@<host>/v1/sentry/<projectId>" into its
|
|
@@ -60,6 +60,39 @@ declare function buildSentryEvent(input: BuildEventInput): SentryEvent;
|
|
|
60
60
|
*/
|
|
61
61
|
declare function buildEnvelope(event: SentryEvent, dsn: Dsn, sentAt?: string): string;
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The product → Sentry DSN registry.
|
|
65
|
+
*
|
|
66
|
+
* An app declares WHAT it is (`product: 'console'`); this module knows WHERE its
|
|
67
|
+
* errors go. That split is the whole point: no surface has to learn a DSN, carry
|
|
68
|
+
* a build argument, or grow a config file to report errors — declaring the
|
|
69
|
+
* product it already declares is enough.
|
|
70
|
+
*
|
|
71
|
+
* A Sentry DSN is PUBLIC by construction. It ships inside the client bundle and
|
|
72
|
+
* is readable in devtools on any deployed page, and it grants exactly one
|
|
73
|
+
* capability: submitting new events. It cannot read issues, projects, or any
|
|
74
|
+
* other data. So committing it is not leaking a secret — it is recording a public
|
|
75
|
+
* identifier next to the code that needs it. (Contrast the server-side collector
|
|
76
|
+
* DSN in the `team-analytics-sentry` Secret, which is HMAC-derived and revocable
|
|
77
|
+
* precisely because a server-side credential is NOT public.)
|
|
78
|
+
*
|
|
79
|
+
* Why a literal map instead of deriving `hanzo-${product}`: the projects predate
|
|
80
|
+
* this registry and do not derive cleanly — `site` lives in `hanzo-ai`, not
|
|
81
|
+
* `hanzo-site`. An explicit map is honest about that; a derivation rule plus an
|
|
82
|
+
* exception table is the same data with a trap in it.
|
|
83
|
+
*
|
|
84
|
+
* Projects are org-scoped and named `<org>-<app>`. To add one: create the project
|
|
85
|
+
* (POST /v1/sentry/projects with X-Org-Id), then add its `dsn` here keyed by the
|
|
86
|
+
* product name the app passes to `createAnalytics`.
|
|
87
|
+
*/
|
|
88
|
+
/** PRODUCT_DSN maps a `product` to the DSN its errors are submitted to. */
|
|
89
|
+
declare const PRODUCT_DSN: Readonly<Record<string, string>>;
|
|
90
|
+
/** dsnForProduct resolves the registered DSN for a product, or undefined when the
|
|
91
|
+
* product has no project yet — which leaves the error plane inert rather than
|
|
92
|
+
* guessing a destination and silently posting a surface's errors into the wrong
|
|
93
|
+
* project. */
|
|
94
|
+
declare function dsnForProduct(product: string | undefined): string | undefined;
|
|
95
|
+
|
|
63
96
|
/** redactSecrets removes known secret shapes. Always applied. */
|
|
64
97
|
declare function redactSecrets(s: string): string;
|
|
65
98
|
/** scrubPII masks emails and IPs. Applied unless PII capture is enabled. */
|
|
@@ -73,6 +106,12 @@ declare const EVENTS: {
|
|
|
73
106
|
readonly SIGNUP_SUBMITTED: "signup_submitted";
|
|
74
107
|
readonly SIGNUP_VERIFIED: "signup_verified";
|
|
75
108
|
readonly SIGNUP_COMPLETED: "signup_completed";
|
|
109
|
+
/** A RETURNING user authenticated — the non-signup half of the IAM callback.
|
|
110
|
+
* Keeping it distinct is what stops returning logins from inflating signups. */
|
|
111
|
+
readonly LOGIN_COMPLETED: "login_completed";
|
|
112
|
+
/** Activation: the first moment of real value. ONE event for every product —
|
|
113
|
+
* the product-specific moment is the `action` property (api_call, app_live,
|
|
114
|
+
* chat_reply), never a new event name. */
|
|
76
115
|
readonly FIRST_ACTION: "first_action";
|
|
77
116
|
readonly WAITLIST_JOINED: "waitlist_joined";
|
|
78
117
|
readonly WAITLIST_SHARED: "waitlist_shared";
|
|
@@ -85,24 +124,127 @@ declare const EVENTS: {
|
|
|
85
124
|
readonly FEATURE_USED: "feature_used";
|
|
86
125
|
readonly API_KEY_CREATED: "api_key_created";
|
|
87
126
|
readonly APP_CREATED: "app_created";
|
|
88
|
-
readonly DEPLOY_STARTED: "deploy_started";
|
|
89
127
|
readonly PROJECT_CREATED: "project_created";
|
|
90
128
|
readonly AGENT_CREATED: "agent_created";
|
|
91
129
|
readonly CHAT_STARTED: "chat_started";
|
|
92
130
|
readonly CHAT_MESSAGE_SENT: "chat_message_sent";
|
|
131
|
+
/** The user switched model/endpoint — the single strongest quality signal a
|
|
132
|
+
* chat surface emits (a switch usually follows a bad answer). */
|
|
133
|
+
readonly MODEL_SWITCHED: "model_switched";
|
|
93
134
|
readonly TASK_STARTED: "task_started";
|
|
94
135
|
readonly TASK_COMPLETED: "task_completed";
|
|
136
|
+
readonly BUILD_STARTED: "build_started";
|
|
137
|
+
/** A model finished producing an artifact (an app build, a chat reply, an agent
|
|
138
|
+
* run). Carries `durationMs` — the outcome event owns its own duration, so no
|
|
139
|
+
* paired start event is needed. */
|
|
140
|
+
readonly GENERATION_COMPLETED: "generation_completed";
|
|
141
|
+
readonly GENERATION_FAILED: "generation_failed";
|
|
142
|
+
readonly DEPLOY_STARTED: "deploy_started";
|
|
143
|
+
readonly DEPLOY_SUCCEEDED: "deploy_succeeded";
|
|
144
|
+
readonly DEPLOY_FAILED: "deploy_failed";
|
|
95
145
|
};
|
|
96
146
|
type EventName = (typeof EVENTS)[keyof typeof EVENTS];
|
|
97
147
|
/** The reserved event name a pageview is stored under (server + read lens). */
|
|
98
148
|
declare const PAGEVIEW = "$pageview";
|
|
99
149
|
|
|
150
|
+
/** The emitting surfaces — the closed set of `AnalyticsConfig.product` values.
|
|
151
|
+
* `product` is on every event, so a funnel scopes by product instead of every
|
|
152
|
+
* surface prefixing its event names. */
|
|
153
|
+
declare const PRODUCTS: readonly ["site", "app", "chat", "console", "admin", "cloud"];
|
|
154
|
+
type ProductId = (typeof PRODUCTS)[number];
|
|
155
|
+
interface FunnelStep {
|
|
156
|
+
/** An EVENTS value (or PAGEVIEW). */
|
|
157
|
+
event: string;
|
|
158
|
+
/** Human label for the Insights step. */
|
|
159
|
+
label: string;
|
|
160
|
+
/** Property equality that qualifies the step, e.g. first_action{action:'api_call'}. */
|
|
161
|
+
where?: {
|
|
162
|
+
property: string;
|
|
163
|
+
equals: string;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
interface FunnelDef {
|
|
167
|
+
label: string;
|
|
168
|
+
/** Surface(s) the steps are emitted from — matched against the `product` field. */
|
|
169
|
+
products: ProductId[];
|
|
170
|
+
/**
|
|
171
|
+
* How steps are joined:
|
|
172
|
+
* • 'person' — steps join on distinctId (one browser, or one logged-in
|
|
173
|
+
* person across surfaces). The normal case.
|
|
174
|
+
* • 'aggregate' — steps are emitted on DIFFERENT origins by a LOGGED-OUT
|
|
175
|
+
* visitor, so there is no shared id: hanzo.ai, hanzo.app and
|
|
176
|
+
* hanzo.chat each mint their own anonymousId in their own
|
|
177
|
+
* storage. Read these as step-over-step COUNTS, never as a
|
|
178
|
+
* per-person conversion. Honest by construction.
|
|
179
|
+
*/
|
|
180
|
+
join: 'person' | 'aggregate';
|
|
181
|
+
steps: FunnelStep[];
|
|
182
|
+
}
|
|
183
|
+
declare const FUNNELS: {
|
|
184
|
+
/** hanzo.ai: land → sign up. IAM hosts the form, so `signup_submitted` is the
|
|
185
|
+
* redirect INTO IAM and `signup_completed` is the return at /auth/callback. */
|
|
186
|
+
readonly signup: {
|
|
187
|
+
readonly label: "Signup";
|
|
188
|
+
readonly products: ["site"];
|
|
189
|
+
readonly join: "person";
|
|
190
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
191
|
+
};
|
|
192
|
+
/** The developer activation path: an account is worth nothing until a key has
|
|
193
|
+
* made a call. `first_action{action:'api_call'}` is emitted SERVER-SIDE by
|
|
194
|
+
* Cloud on an org's first successful /v1 request — a browser cannot see it. */
|
|
195
|
+
readonly apiActivation: {
|
|
196
|
+
readonly label: "API activation";
|
|
197
|
+
readonly products: ["site", "cloud"];
|
|
198
|
+
readonly join: "person";
|
|
199
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep];
|
|
200
|
+
};
|
|
201
|
+
/** Upgrade intent → revenue. `order_completed{kind:'plan'}` is the Sale goal. */
|
|
202
|
+
readonly upgrade: {
|
|
203
|
+
readonly label: "Upgrade";
|
|
204
|
+
readonly products: ["site", "app", "console"];
|
|
205
|
+
readonly join: "person";
|
|
206
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
207
|
+
};
|
|
208
|
+
/** hanzo.app: describe → build → deploy → live URL. The whole product thesis
|
|
209
|
+
* in five steps; `deploy_succeeded` is the moment a live URL exists. */
|
|
210
|
+
readonly appShip: {
|
|
211
|
+
readonly label: "Describe → ship";
|
|
212
|
+
readonly products: ["app"];
|
|
213
|
+
readonly join: "person";
|
|
214
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
215
|
+
};
|
|
216
|
+
/** hanzo.chat: visit → first message → answer. `generation_completed` is what
|
|
217
|
+
* separates "typed something" from "got value". */
|
|
218
|
+
readonly chatEngage: {
|
|
219
|
+
readonly label: "Chat engagement";
|
|
220
|
+
readonly products: ["chat"];
|
|
221
|
+
readonly join: "person";
|
|
222
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
223
|
+
};
|
|
224
|
+
/** The cross-surface handoff: the hanzo.ai composer forwards its prompt to
|
|
225
|
+
* hanzo.chat. Two origins, two anonymousIds — so this is an AGGREGATE funnel.
|
|
226
|
+
* The join is the `referrerProduct` property hanzo.chat reads off `?hz_ref=`,
|
|
227
|
+
* which makes the drop-off measurable without any cross-domain identity. */
|
|
228
|
+
readonly siteToChat: {
|
|
229
|
+
readonly label: "Site → Chat handoff";
|
|
230
|
+
readonly products: ["site", "chat"];
|
|
231
|
+
readonly join: "aggregate";
|
|
232
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep];
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
type FunnelId = keyof typeof FUNNELS;
|
|
236
|
+
/** eventsOf flattens a funnel to its ordered event names — what a goal's `funnel`
|
|
237
|
+
* field carries, so the steps are defined exactly once (here). */
|
|
238
|
+
declare function eventsOf(id: FunnelId): string[];
|
|
239
|
+
|
|
100
240
|
interface GoalDef {
|
|
101
241
|
/** Human label shown in Insights. */
|
|
102
242
|
label: string;
|
|
103
243
|
/** The event whose occurrence counts as the goal conversion. */
|
|
104
244
|
event: string;
|
|
105
|
-
/**
|
|
245
|
+
/** The funnel leading to the goal — an id into FUNNELS (see funnels.ts). */
|
|
246
|
+
funnelId?: FunnelId;
|
|
247
|
+
/** The ordered event names of `funnelId`, derived — never hand-written. */
|
|
106
248
|
funnel?: string[];
|
|
107
249
|
/** Optional property equality filter that qualifies the conversion. */
|
|
108
250
|
filter?: {
|
|
@@ -110,7 +252,7 @@ interface GoalDef {
|
|
|
110
252
|
equals: string;
|
|
111
253
|
};
|
|
112
254
|
}
|
|
113
|
-
declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent', GoalDef>;
|
|
255
|
+
declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent' | 'activation', GoalDef>;
|
|
114
256
|
interface CohortDef {
|
|
115
257
|
/** The hanzo.events column the cohort dimension maps to. */
|
|
116
258
|
field: string;
|
|
@@ -131,4 +273,4 @@ declare function hasAttribution(a: Attribution): boolean;
|
|
|
131
273
|
/** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
|
|
132
274
|
declare function isoWeek(d: Date): string;
|
|
133
275
|
|
|
134
|
-
export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, EVENTS, type ErrorIdentity, type EventName, GOALS, type GoalDef, PAGEVIEW, SentryEvent, SentryFrame, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText };
|
|
276
|
+
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, PRODUCT_DSN, type ProductId, SentryEvent, SentryFrame, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, dsnForProduct, eventsOf, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText };
|