@hanzo/event 0.3.19 → 0.3.20

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.cts CHANGED
@@ -1,12 +1,12 @@
1
- import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame } from './core-D4PaPlOB.cjs';
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-D4PaPlOB.cjs';
1
+ import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame, E as ExceptionEntry, c as SentryLevel, d as ExceptionProperties } from './core-CHLpJy51.cjs';
2
+ export { e as Analytics, f as AnalyticsConfig, g as EventKind, h as Exception, i as ExceptionFrame, T as Transport, W as WireEvent, j as createAnalytics } from './core-CHLpJy51.cjs';
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.19";
9
+ declare const VERSION = "0.3.20";
10
10
 
11
11
  /**
12
12
  * parseDsn parses "https://<version>:<hmac>@<host>/v1/sentry/<projectId>" into its
@@ -170,6 +170,59 @@ declare const EVENTS: {
170
170
  type EventName = (typeof EVENTS)[keyof typeof EVENTS];
171
171
  /** The reserved event name a pageview is stored under (server + read lens). */
172
172
  declare const PAGEVIEW = "$pageview";
173
+ /** The reserved name every captured exception is emitted under. Error Tracking
174
+ * reads exactly this name; an exception emitted under its own message instead
175
+ * makes every distinct message a permanent entry in the event taxonomy. */
176
+ declare const EXCEPTION = "$exception";
177
+
178
+ /**
179
+ * digest is a stable 32-hex-char (128-bit) content hash, computed synchronously.
180
+ *
181
+ * Grouping keys are needed on the capture path, which is synchronous and may be
182
+ * running inside an unload handler — SubtleCrypto is async and unavailable on
183
+ * insecure origins, so it cannot be used here. This is FNV-1a run over four seeds
184
+ * and concatenated. It is a GROUPING key, never a security boundary: it is not
185
+ * collision-resistant against a chosen-input adversary, and nothing authorizes or
186
+ * authenticates on it. The product treats the value as opaque.
187
+ */
188
+ declare function digest(s: string): string;
189
+ /**
190
+ * exceptionEntry builds the single `$exception_list` entry for a throwable.
191
+ *
192
+ * One entry, not a chain: `Error.cause` chaining is a distinct fact with its own
193
+ * ordering rules, and emitting it wrongly is worse than not emitting it.
194
+ */
195
+ declare function exceptionEntry(err: unknown, opts: {
196
+ handled: boolean;
197
+ id: string;
198
+ }): ExceptionEntry;
199
+ /**
200
+ * fingerprint is the issue grouping key.
201
+ *
202
+ * Keyed on exception type plus each in-app frame's function and source — the same
203
+ * pieces the server-side grouper records ("Exception Type", "Resolved function
204
+ * name", "Source file name"). Deliberately NOT the message: `Loading chunk 3324
205
+ * failed` and `Loading chunk 998 failed` are one bug, and grouping on message is
206
+ * precisely the mistake that made every distinct error string its own event name.
207
+ *
208
+ * Falls back to the type alone when no in-app frame survived, which keeps
209
+ * stackless errors (`Script error.`, cross-origin) in one issue instead of
210
+ * scattering them.
211
+ */
212
+ declare function fingerprint(entry: ExceptionEntry): string;
213
+ /**
214
+ * exceptionProperties builds the full `$exception_*` property bag for one captured
215
+ * throwable — everything Error Tracking reads off the event.
216
+ *
217
+ * The denormalized arrays are ordered like `frames`: last element is the throw
218
+ * site, which is the element the issue list indexes at -1 for its source/function
219
+ * columns.
220
+ */
221
+ declare function exceptionProperties(err: unknown, opts: {
222
+ handled: boolean;
223
+ id: string;
224
+ level?: SentryLevel;
225
+ }): ExceptionProperties;
173
226
 
174
227
  /** The emitting surfaces — the closed set of `AnalyticsConfig.product` values.
175
228
  * `product` is on every event, so a funnel scopes by product instead of every
@@ -297,4 +350,4 @@ declare function hasAttribution(a: Attribution): boolean;
297
350
  /** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
298
351
  declare function isoWeek(d: Date): string;
299
352
 
300
- 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_PROJECT, type ProductId, SentryEvent, SentryFrame, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, dsnForProduct, eventsOf, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
353
+ export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, EVENTS, EXCEPTION, type ErrorIdentity, type EventName, ExceptionEntry, ExceptionProperties, FUNNELS, type FunnelDef, type FunnelId, type FunnelStep, GOALS, type GoalDef, PAGEVIEW, PRODUCTS, PRODUCT_PROJECT, type ProductId, SentryEvent, SentryFrame, SentryLevel, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, digest, dsnForProduct, eventsOf, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame } from './core-D4PaPlOB.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-D4PaPlOB.js';
1
+ import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame, E as ExceptionEntry, c as SentryLevel, d as ExceptionProperties } from './core-CHLpJy51.js';
2
+ export { e as Analytics, f as AnalyticsConfig, g as EventKind, h as Exception, i as ExceptionFrame, T as Transport, W as WireEvent, j as createAnalytics } from './core-CHLpJy51.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.19";
9
+ declare const VERSION = "0.3.20";
10
10
 
11
11
  /**
12
12
  * parseDsn parses "https://<version>:<hmac>@<host>/v1/sentry/<projectId>" into its
@@ -170,6 +170,59 @@ declare const EVENTS: {
170
170
  type EventName = (typeof EVENTS)[keyof typeof EVENTS];
171
171
  /** The reserved event name a pageview is stored under (server + read lens). */
172
172
  declare const PAGEVIEW = "$pageview";
173
+ /** The reserved name every captured exception is emitted under. Error Tracking
174
+ * reads exactly this name; an exception emitted under its own message instead
175
+ * makes every distinct message a permanent entry in the event taxonomy. */
176
+ declare const EXCEPTION = "$exception";
177
+
178
+ /**
179
+ * digest is a stable 32-hex-char (128-bit) content hash, computed synchronously.
180
+ *
181
+ * Grouping keys are needed on the capture path, which is synchronous and may be
182
+ * running inside an unload handler — SubtleCrypto is async and unavailable on
183
+ * insecure origins, so it cannot be used here. This is FNV-1a run over four seeds
184
+ * and concatenated. It is a GROUPING key, never a security boundary: it is not
185
+ * collision-resistant against a chosen-input adversary, and nothing authorizes or
186
+ * authenticates on it. The product treats the value as opaque.
187
+ */
188
+ declare function digest(s: string): string;
189
+ /**
190
+ * exceptionEntry builds the single `$exception_list` entry for a throwable.
191
+ *
192
+ * One entry, not a chain: `Error.cause` chaining is a distinct fact with its own
193
+ * ordering rules, and emitting it wrongly is worse than not emitting it.
194
+ */
195
+ declare function exceptionEntry(err: unknown, opts: {
196
+ handled: boolean;
197
+ id: string;
198
+ }): ExceptionEntry;
199
+ /**
200
+ * fingerprint is the issue grouping key.
201
+ *
202
+ * Keyed on exception type plus each in-app frame's function and source — the same
203
+ * pieces the server-side grouper records ("Exception Type", "Resolved function
204
+ * name", "Source file name"). Deliberately NOT the message: `Loading chunk 3324
205
+ * failed` and `Loading chunk 998 failed` are one bug, and grouping on message is
206
+ * precisely the mistake that made every distinct error string its own event name.
207
+ *
208
+ * Falls back to the type alone when no in-app frame survived, which keeps
209
+ * stackless errors (`Script error.`, cross-origin) in one issue instead of
210
+ * scattering them.
211
+ */
212
+ declare function fingerprint(entry: ExceptionEntry): string;
213
+ /**
214
+ * exceptionProperties builds the full `$exception_*` property bag for one captured
215
+ * throwable — everything Error Tracking reads off the event.
216
+ *
217
+ * The denormalized arrays are ordered like `frames`: last element is the throw
218
+ * site, which is the element the issue list indexes at -1 for its source/function
219
+ * columns.
220
+ */
221
+ declare function exceptionProperties(err: unknown, opts: {
222
+ handled: boolean;
223
+ id: string;
224
+ level?: SentryLevel;
225
+ }): ExceptionProperties;
173
226
 
174
227
  /** The emitting surfaces — the closed set of `AnalyticsConfig.product` values.
175
228
  * `product` is on every event, so a funnel scopes by product instead of every
@@ -297,4 +350,4 @@ declare function hasAttribution(a: Attribution): boolean;
297
350
  /** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
298
351
  declare function isoWeek(d: Date): string;
299
352
 
300
- 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_PROJECT, type ProductId, SentryEvent, SentryFrame, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, dsnForProduct, eventsOf, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
353
+ export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, EVENTS, EXCEPTION, type ErrorIdentity, type EventName, ExceptionEntry, ExceptionProperties, FUNNELS, type FunnelDef, type FunnelId, type FunnelStep, GOALS, type GoalDef, PAGEVIEW, PRODUCTS, PRODUCT_PROJECT, type ProductId, SentryEvent, SentryFrame, SentryLevel, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, digest, dsnForProduct, eventsOf, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
package/dist/index.mjs CHANGED
@@ -142,6 +142,7 @@ var EVENTS = {
142
142
  DEPLOY_FAILED: "deploy_failed"
143
143
  };
144
144
  var PAGEVIEW = "$pageview";
145
+ var EXCEPTION = "$exception";
145
146
 
146
147
  // src/scrub.ts
147
148
  var REDACTED = "[redacted]";
@@ -371,7 +372,7 @@ function uuidv7Time(id) {
371
372
  }
372
373
 
373
374
  // src/version.ts
374
- var VERSION = "0.3.19";
375
+ var VERSION = "0.3.20";
375
376
 
376
377
  // src/sentry.ts
377
378
  var MAX_FRAMES = 50;
@@ -563,6 +564,94 @@ ${itemHeader}
563
564
  ${payload}
564
565
  `;
565
566
  }
567
+ var MAX_VALUE = 4096;
568
+ function digest(s2) {
569
+ let out = "";
570
+ for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
571
+ let h = seed >>> 0;
572
+ for (let i = 0; i < s2.length; i++) {
573
+ h ^= s2.charCodeAt(i);
574
+ h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
575
+ }
576
+ out += h.toString(16).padStart(8, "0");
577
+ }
578
+ return out;
579
+ }
580
+ function frameOf(f) {
581
+ const source = f.filename ?? "";
582
+ const name = f.function ?? "<anonymous>";
583
+ const line = f.lineno ?? 0;
584
+ const column = f.colno ?? 0;
585
+ return {
586
+ // Stable per code location, so the same frame groups and re-renders under one
587
+ // key. The '/part' suffix is the product's format for one raw frame expanding
588
+ // into several resolved ones; a browser frame is always part 0.
589
+ raw_id: `${digest(`${source}|${name}|${line}|${column}`)}/0`,
590
+ mangled_name: name,
591
+ source,
592
+ line,
593
+ column,
594
+ in_app: f.in_app ?? false,
595
+ lang: "javascript",
596
+ resolved: false,
597
+ resolve_failure: "no symbol set uploaded for this release",
598
+ resolved_name: null,
599
+ module: null
600
+ };
601
+ }
602
+ function truncate2(s2, max) {
603
+ if (s2.length <= max) return s2;
604
+ let end = max;
605
+ const c = s2.charCodeAt(end - 1);
606
+ if (c >= 55296 && c <= 56319) end--;
607
+ return s2.slice(0, end);
608
+ }
609
+ function exceptionEntry(err, opts) {
610
+ const n = normalizeError(err);
611
+ const frames = framesFromStack(n.stack).slice(-50);
612
+ const entry = {
613
+ id: opts.id,
614
+ type: n.name,
615
+ value: truncate2(n.message, MAX_VALUE),
616
+ mechanism: {
617
+ type: "generic",
618
+ handled: opts.handled,
619
+ // An exception the app reported by hand was constructed, not thrown by the
620
+ // runtime at a real call site.
621
+ synthetic: !(err instanceof Error)
622
+ }
623
+ };
624
+ if (frames.length > 0) {
625
+ entry.stacktrace = { type: "resolved", frames: frames.map(frameOf) };
626
+ }
627
+ return entry;
628
+ }
629
+ function fingerprint(entry) {
630
+ const frames = entry.stacktrace?.frames ?? [];
631
+ const pieces = frames.filter((f) => f.in_app).map((f) => `${f.mangled_name}@${f.source}`);
632
+ return digest([entry.type, ...pieces].join("\n"));
633
+ }
634
+ function exceptionProperties(err, opts) {
635
+ const entry = exceptionEntry(err, opts);
636
+ const frames = entry.stacktrace?.frames ?? [];
637
+ const fp = fingerprint(entry);
638
+ return {
639
+ $exception_list: [entry],
640
+ $exception_fingerprint: fp,
641
+ // Records WHY this fingerprint holds. 'manual' is the honest value: the client
642
+ // supplied the key rather than a server grouper deriving one.
643
+ $exception_fingerprint_record: [{ type: "manual" }],
644
+ $exception_type: entry.type,
645
+ $exception_message: entry.value,
646
+ $exception_level: opts.level ?? "error",
647
+ $exception_handled: opts.handled,
648
+ $exception_synthetic: entry.mechanism?.synthetic ?? false,
649
+ $exception_types: [entry.type],
650
+ $exception_values: [entry.value],
651
+ $exception_sources: frames.map((f) => f.source).filter((s2) => !!s2),
652
+ $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s2) => !!s2)
653
+ };
654
+ }
566
655
 
567
656
  // src/storage.ts
568
657
  var KEY = {
@@ -839,9 +928,20 @@ var Analytics = class {
839
928
  } catch {
840
929
  }
841
930
  try {
931
+ const handled = context?.handled ?? true;
842
932
  const ex = normalizeError2(err);
843
- ex.handled = context?.handled ?? true;
844
- this.enqueue("error", ex.message, { error: ex, properties: context?.properties });
933
+ ex.handled = handled;
934
+ this.enqueue("event", EXCEPTION, {
935
+ error: ex,
936
+ properties: {
937
+ ...context?.properties,
938
+ ...exceptionProperties(err, {
939
+ handled,
940
+ id: hzUuidv7(),
941
+ level: context?.level
942
+ })
943
+ }
944
+ });
845
945
  this.flush();
846
946
  } catch {
847
947
  }
@@ -1148,6 +1248,6 @@ var COHORTS = {
1148
1248
  * reformat of one copy is a diff against the other.
1149
1249
  */
1150
1250
 
1151
- export { Analytics, COHORTS, EVENTS, FUNNELS, GOALS, PAGEVIEW, PRODUCTS, PRODUCT_PROJECT, VERSION, buildEnvelope, buildSentryEvent, createAnalytics, deriveChannel, dsnForProduct, eventsOf, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
1251
+ export { Analytics, COHORTS, EVENTS, EXCEPTION, FUNNELS, GOALS, PAGEVIEW, PRODUCTS, PRODUCT_PROJECT, VERSION, buildEnvelope, buildSentryEvent, createAnalytics, deriveChannel, digest, dsnForProduct, eventsOf, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
1152
1252
  //# sourceMappingURL=index.mjs.map
1153
1253
  //# sourceMappingURL=index.mjs.map