@shipeasy/sdk 3.1.0 → 4.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/README.md CHANGED
@@ -53,6 +53,58 @@ const client = new FlagsClient({ sdkKey: process.env.SHIPEASY_SERVER_KEY! });
53
53
  const cfg = await client.getConfig("plan_limits", { user_id: "u-1" });
54
54
  ```
55
55
 
56
+ ## Error tracking — `see()`
57
+
58
+ `see` (shipeasy error) is the structured error reporter — opes-style: every
59
+ handled exception documents its **product consequence**, not just its stack.
60
+ Works in vanilla JS on both sides; the whole grammar hangs off one import:
61
+
62
+ ```ts
63
+ import { see } from "@shipeasy/sdk/client"; // or "@shipeasy/sdk/server"
64
+
65
+ try {
66
+ await submitOrder(order);
67
+ } catch (e) {
68
+ see(e).causes_the("checkout").to("use cached prices").extras({ order_id: order.id });
69
+ }
70
+
71
+ // Non-exception problems — the name is a stable identifier (it participates
72
+ // in the issue fingerprint); variable data goes in .message() / .extras():
73
+ if (rows.length > LIMIT) {
74
+ see.Violation("large query")
75
+ .message(`got ${rows.length} rows`)
76
+ .causes_the("search results")
77
+ .to("be trimmed");
78
+ }
79
+
80
+ // Expected control-flow exceptions: document them, report nothing —
81
+ // auto-capture skips marked errors. The reason must start with "because".
82
+ try {
83
+ return decodeFoo(blob);
84
+ } catch (e) {
85
+ see.ControlFlowException(e, "because it wasn't an encoded Foo");
86
+ return decodeBar(blob);
87
+ }
88
+ ```
89
+
90
+ Reports land in the Shipeasy **errors** primitive: fingerprint-grouped issues
91
+ (open / resolved / ignored, regression auto-reopens) with a near-real-time
92
+ occurrence timeseries. The chain dispatches on the next microtask — no
93
+ `.send()` — and ships immediately (`sendBeacon` in the browser, fire-and-forget
94
+ `fetch` on the server), spam-guarded by a 30s dedup window and a per-session
95
+ cap.
96
+
97
+ The client SDK also auto-captures uncaught exceptions, unhandled rejections,
98
+ and network failures (fetch network errors + 5xx) into the same primitive
99
+ (`autoCollect: { errors }`, on by default). Auto-capture is the outer safety
100
+ net — it does not replace `see()` in catch blocks, where you know the
101
+ consequence and it cannot.
102
+
103
+ **Rules**: if you don't know the consequence, don't catch the exception. Never
104
+ `see()` then `throw` (double counting — either handle or rethrow). Never use
105
+ `see.Violation()` for a caught exception (you'd drop the stack). No PII or
106
+ high-cardinality data in extras.
107
+
56
108
  ## Drop-in `<script>` loader (no bundler)
57
109
 
58
110
  ```html
@@ -1,3 +1,84 @@
1
+ type SeeExtras = Record<string, string | number | boolean | null | undefined>;
2
+ type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
3
+ /** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
4
+ interface Consequence {
5
+ readonly __seConsequence: true;
6
+ readonly subject: string;
7
+ readonly outcome: string;
8
+ }
9
+ /**
10
+ * Non-exception problem, built by `violation(name)`. A plain branded object
11
+ * (not an Error subclass) so `.message()` can be a builder method without
12
+ * colliding with `Error.prototype.message`.
13
+ */
14
+ interface Violation {
15
+ readonly __seViolation: true;
16
+ readonly violationName: string;
17
+ readonly violationMessage?: string;
18
+ /** Attach free-form detail. Variable data goes HERE (or in extras), never in the name. */
19
+ message(msg: string): Violation;
20
+ }
21
+ /**
22
+ * Identity of a problem that see() already reported, carried on the wire as
23
+ * the `caused_by` of a later occurrence. Holds exactly the fields the worker's
24
+ * fingerprint function consumes (raw `message`/`stack` — the server normalizes
25
+ * them) so the backend can recompute the prior issue's fingerprint and link
26
+ * the two issues. See `findCausedBy` for how the link is discovered.
27
+ */
28
+ interface SeeCausedBy {
29
+ error_type: string;
30
+ message: string;
31
+ stack?: string;
32
+ subject: string;
33
+ outcome: string;
34
+ }
35
+ /** Wire shape — the `type:"error"` RawEvent variant accepted by POST /collect. */
36
+ interface SeeErrorEvent {
37
+ type: "error";
38
+ kind: SeeKind;
39
+ /** Error class/name (e.g. "TypeError") or the violation name. */
40
+ error_type: string;
41
+ message: string;
42
+ stack?: string;
43
+ /** Consequence: "<error_type> causes the <subject> to <outcome>". */
44
+ subject: string;
45
+ outcome: string;
46
+ extras?: Record<string, string | number | boolean>;
47
+ url?: string;
48
+ user_id?: string;
49
+ anonymous_id?: string;
50
+ side: "client" | "server";
51
+ env?: string;
52
+ sdk_version: string;
53
+ ts: number;
54
+ /**
55
+ * The earlier reported problem this occurrence descends from — present when
56
+ * the same error was caught + reported at an inner boundary and then
57
+ * re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
58
+ * Lets the backend stitch the two issues into a cause chain instead of
59
+ * double-counting them as unrelated.
60
+ */
61
+ caused_by?: SeeCausedBy;
62
+ }
63
+ interface SeeExtrasTail {
64
+ /** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
65
+ extras(extras: SeeExtras): SeeExtrasTail;
66
+ }
67
+ interface SeeOutcomeStep {
68
+ /** The user-visible impact: `.causes_the("checkout").to("use cached prices")`. */
69
+ to(outcome: string): SeeExtrasTail;
70
+ }
71
+ interface SeeChain {
72
+ /** Start the consequence sentence — the product surface affected. */
73
+ causes_the(subject: string): SeeOutcomeStep;
74
+ /** camelCase alias of {@link SeeChain.causes_the}. */
75
+ causesThe(subject: string): SeeOutcomeStep;
76
+ }
77
+ interface SeeViolationChain extends SeeChain {
78
+ /** Free-form detail. Variable data goes here (or extras), never in the name. */
79
+ message(msg: string): SeeViolationChain;
80
+ }
81
+
1
82
  declare global {
2
83
  interface Window {
3
84
  i18n?: {
@@ -8,7 +89,7 @@ declare global {
8
89
  };
9
90
  }
10
91
  }
11
- declare const version = "1.0.0";
92
+ declare const version = "4.0.0";
12
93
  interface User {
13
94
  user_id?: string;
14
95
  [attr: string]: unknown;
@@ -51,6 +132,13 @@ interface FlagsClientBrowserOptions {
51
132
  * true when `autoGuardrails` is true).
52
133
  */
53
134
  autoGuardrailGroups?: Partial<AutoCollectGroups>;
135
+ /**
136
+ * Emit `__auto_*` metric events for ALL visitors, not just experiment
137
+ * participants. Default false: auto-metrics are gated on the visitor having
138
+ * seen ≥1 experiment exposure (the only data the analysis pipeline reads;
139
+ * ungated emission is pure AE write cost at scale — see cost.md).
140
+ */
141
+ autoCollectAlways?: boolean;
54
142
  /** Which published env to read values from. Defaults to "prod". */
55
143
  env?: FlagsClientBrowserEnv;
56
144
  /**
@@ -68,12 +156,14 @@ declare class FlagsClientBrowser {
68
156
  private readonly baseUrl;
69
157
  private readonly autoGuardrails;
70
158
  private readonly autoGuardrailGroups;
159
+ private readonly autoCollectAlways;
71
160
  private readonly env;
72
161
  private evalResult;
73
162
  private anonId;
74
163
  private userId;
75
164
  private buffer;
76
165
  private telemetry;
166
+ private seeLimiter;
77
167
  private guardrailsInstalled;
78
168
  private listeners;
79
169
  private overrideListenerInstalled;
@@ -81,6 +171,12 @@ declare class FlagsClientBrowser {
81
171
  private onOverrideChange;
82
172
  constructor(opts: FlagsClientBrowserOptions);
83
173
  identify(user: User): Promise<void>;
174
+ /**
175
+ * Report a structured error into the errors primitive. Flushes immediately
176
+ * (beacon-first) — error occurrences are near-real-time, never queued behind
177
+ * the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
178
+ */
179
+ reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind): void;
84
180
  get ready(): boolean;
85
181
  private notify;
86
182
  initFromBootstrap(data: EvalResponse): void;
@@ -171,10 +267,11 @@ interface ShipeasyClientConfig {
171
267
  */
172
268
  autoIdentify?: boolean;
173
269
  /**
174
- * Capture web vitals (LCP, CLS, INP, TTFB, FCP, navigation timing), JS /
175
- * network errors, and engagement signals (abandonment) as `__auto_*`
176
- * metric events. Defaults to `true` the worker bypasses event-catalog
177
- * validation for `__auto_*` names so this is safe out of the box.
270
+ * Capture web vitals (LCP, CLS, INP, TTFB, FCP, navigation timing) and
271
+ * engagement signals (abandonment) as `__auto_*` metric events, plus JS /
272
+ * network errors as structured error events in the errors primitive (same
273
+ * pipeline as `see()` grouped by fingerprint, near-real-time). Defaults
274
+ * to `true`.
178
275
  *
179
276
  * Pass `false` to disable everything, or a per-group object to narrow:
180
277
  *
@@ -183,8 +280,23 @@ interface ShipeasyClientConfig {
183
280
  * shipeasy({ clientKey, autoCollect: { errors: false } }); // vitals + engagement only
184
281
  * shipeasy({ clientKey }); // all groups on
185
282
  * ```
283
+ *
284
+ * Since 4.1.0, `__auto_*` metric events are only emitted for visitors who
285
+ * are in ≥1 active experiment (the analysis pipeline reads nothing else;
286
+ * gating cuts the dominant Analytics Engine write cost at scale). Pass
287
+ * `{ always: true }` to collect site-wide vitals regardless of experiment
288
+ * participation:
289
+ *
290
+ * ```ts
291
+ * shipeasy({ clientKey, autoCollect: { always: true } }); // site-wide vitals
292
+ * ```
293
+ *
294
+ * `__auto_abandoned` is always emitted (it fires when the user leaves
295
+ * before exposures could land) and error capture is unaffected.
186
296
  */
187
- autoCollect?: boolean | Partial<AutoCollectGroups>;
297
+ autoCollect?: boolean | (Partial<AutoCollectGroups> & {
298
+ always?: boolean;
299
+ });
188
300
  /**
189
301
  * Disable per-evaluation usage telemetry. Telemetry is ON by default — every
190
302
  * flag/config/experiment/killswitch read fires one fire-and-forget beacon
@@ -276,6 +388,58 @@ declare const flags: {
276
388
  /** True once identify() has completed and flags are available. */
277
389
  readonly ready: boolean;
278
390
  };
391
+ interface SeeApi {
392
+ /**
393
+ * Report a handled problem and its product consequence:
394
+ *
395
+ * ```ts
396
+ * import { see } from "@shipeasy/sdk/client";
397
+ *
398
+ * try {
399
+ * await submitOrder(order);
400
+ * } catch (e) {
401
+ * see(e).causes_the("checkout").to("use cached prices").extras({ order_id: order.id });
402
+ * }
403
+ * ```
404
+ *
405
+ * The chain dispatches on the next microtask, so the report ships
406
+ * immediately after the statement (no `.send()` needed) into the errors
407
+ * primitive — grouped by fingerprint, near-real-time timeseries. If you
408
+ * don't know the consequence of an exception, don't catch it.
409
+ */
410
+ (problem: unknown): SeeChain;
411
+ /**
412
+ * Report a non-exception problem. Prefer passing a caught Error to `see()`
413
+ * when one exists. The name is a stable identifier (it participates in the
414
+ * issue fingerprint) — variable data goes in `.message()` or `.extras()`.
415
+ *
416
+ * ```ts
417
+ * if (rows.length > LIMIT) {
418
+ * see.Violation("large query").message(`got ${rows.length} rows`)
419
+ * .causes_the("search results").to("be trimmed");
420
+ * }
421
+ * ```
422
+ */
423
+ Violation(name: string): SeeViolationChain;
424
+ /**
425
+ * Mark an exception as expected control flow — auto-capture skips it and
426
+ * nothing is reported. The reason must start with "because".
427
+ *
428
+ * ```ts
429
+ * } catch (e) {
430
+ * see.ControlFlowException(e, "because the blob wasn't an encoded Foo");
431
+ * return decodeAsBar(blob);
432
+ * }
433
+ * ```
434
+ */
435
+ ControlFlowException(err: unknown, because: string): void;
436
+ }
437
+ /**
438
+ * Structured error reporter — the whole grammar hangs off this one import.
439
+ * Safe to import anywhere; a call before `shipeasy({ clientKey })` warns and
440
+ * drops (never throws).
441
+ */
442
+ declare const see: SeeApi;
279
443
  declare const LABEL_MARKER_START = "\uFFF9";
280
444
  declare const LABEL_MARKER_SEP = "\uFFFA";
281
445
  declare const LABEL_MARKER_END = "\uFFFB";
@@ -314,4 +478,4 @@ interface I18nFacade {
314
478
  }
315
479
  declare const i18n: I18nFacade;
316
480
 
317
- export { type AutoCollectGroups, type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
481
+ export { type AutoCollectGroups, type BootstrapPayload, type Consequence, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, see, shipeasy, version };
@@ -1,3 +1,84 @@
1
+ type SeeExtras = Record<string, string | number | boolean | null | undefined>;
2
+ type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
3
+ /** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
4
+ interface Consequence {
5
+ readonly __seConsequence: true;
6
+ readonly subject: string;
7
+ readonly outcome: string;
8
+ }
9
+ /**
10
+ * Non-exception problem, built by `violation(name)`. A plain branded object
11
+ * (not an Error subclass) so `.message()` can be a builder method without
12
+ * colliding with `Error.prototype.message`.
13
+ */
14
+ interface Violation {
15
+ readonly __seViolation: true;
16
+ readonly violationName: string;
17
+ readonly violationMessage?: string;
18
+ /** Attach free-form detail. Variable data goes HERE (or in extras), never in the name. */
19
+ message(msg: string): Violation;
20
+ }
21
+ /**
22
+ * Identity of a problem that see() already reported, carried on the wire as
23
+ * the `caused_by` of a later occurrence. Holds exactly the fields the worker's
24
+ * fingerprint function consumes (raw `message`/`stack` — the server normalizes
25
+ * them) so the backend can recompute the prior issue's fingerprint and link
26
+ * the two issues. See `findCausedBy` for how the link is discovered.
27
+ */
28
+ interface SeeCausedBy {
29
+ error_type: string;
30
+ message: string;
31
+ stack?: string;
32
+ subject: string;
33
+ outcome: string;
34
+ }
35
+ /** Wire shape — the `type:"error"` RawEvent variant accepted by POST /collect. */
36
+ interface SeeErrorEvent {
37
+ type: "error";
38
+ kind: SeeKind;
39
+ /** Error class/name (e.g. "TypeError") or the violation name. */
40
+ error_type: string;
41
+ message: string;
42
+ stack?: string;
43
+ /** Consequence: "<error_type> causes the <subject> to <outcome>". */
44
+ subject: string;
45
+ outcome: string;
46
+ extras?: Record<string, string | number | boolean>;
47
+ url?: string;
48
+ user_id?: string;
49
+ anonymous_id?: string;
50
+ side: "client" | "server";
51
+ env?: string;
52
+ sdk_version: string;
53
+ ts: number;
54
+ /**
55
+ * The earlier reported problem this occurrence descends from — present when
56
+ * the same error was caught + reported at an inner boundary and then
57
+ * re-thrown (or wrapped via `{ cause }`) and reported again at an outer one.
58
+ * Lets the backend stitch the two issues into a cause chain instead of
59
+ * double-counting them as unrelated.
60
+ */
61
+ caused_by?: SeeCausedBy;
62
+ }
63
+ interface SeeExtrasTail {
64
+ /** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
65
+ extras(extras: SeeExtras): SeeExtrasTail;
66
+ }
67
+ interface SeeOutcomeStep {
68
+ /** The user-visible impact: `.causes_the("checkout").to("use cached prices")`. */
69
+ to(outcome: string): SeeExtrasTail;
70
+ }
71
+ interface SeeChain {
72
+ /** Start the consequence sentence — the product surface affected. */
73
+ causes_the(subject: string): SeeOutcomeStep;
74
+ /** camelCase alias of {@link SeeChain.causes_the}. */
75
+ causesThe(subject: string): SeeOutcomeStep;
76
+ }
77
+ interface SeeViolationChain extends SeeChain {
78
+ /** Free-form detail. Variable data goes here (or extras), never in the name. */
79
+ message(msg: string): SeeViolationChain;
80
+ }
81
+
1
82
  declare global {
2
83
  interface Window {
3
84
  i18n?: {
@@ -8,7 +89,7 @@ declare global {
8
89
  };
9
90
  }
10
91
  }
11
- declare const version = "1.0.0";
92
+ declare const version = "4.0.0";
12
93
  interface User {
13
94
  user_id?: string;
14
95
  [attr: string]: unknown;
@@ -51,6 +132,13 @@ interface FlagsClientBrowserOptions {
51
132
  * true when `autoGuardrails` is true).
52
133
  */
53
134
  autoGuardrailGroups?: Partial<AutoCollectGroups>;
135
+ /**
136
+ * Emit `__auto_*` metric events for ALL visitors, not just experiment
137
+ * participants. Default false: auto-metrics are gated on the visitor having
138
+ * seen ≥1 experiment exposure (the only data the analysis pipeline reads;
139
+ * ungated emission is pure AE write cost at scale — see cost.md).
140
+ */
141
+ autoCollectAlways?: boolean;
54
142
  /** Which published env to read values from. Defaults to "prod". */
55
143
  env?: FlagsClientBrowserEnv;
56
144
  /**
@@ -68,12 +156,14 @@ declare class FlagsClientBrowser {
68
156
  private readonly baseUrl;
69
157
  private readonly autoGuardrails;
70
158
  private readonly autoGuardrailGroups;
159
+ private readonly autoCollectAlways;
71
160
  private readonly env;
72
161
  private evalResult;
73
162
  private anonId;
74
163
  private userId;
75
164
  private buffer;
76
165
  private telemetry;
166
+ private seeLimiter;
77
167
  private guardrailsInstalled;
78
168
  private listeners;
79
169
  private overrideListenerInstalled;
@@ -81,6 +171,12 @@ declare class FlagsClientBrowser {
81
171
  private onOverrideChange;
82
172
  constructor(opts: FlagsClientBrowserOptions);
83
173
  identify(user: User): Promise<void>;
174
+ /**
175
+ * Report a structured error into the errors primitive. Flushes immediately
176
+ * (beacon-first) — error occurrences are near-real-time, never queued behind
177
+ * the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
178
+ */
179
+ reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind): void;
84
180
  get ready(): boolean;
85
181
  private notify;
86
182
  initFromBootstrap(data: EvalResponse): void;
@@ -171,10 +267,11 @@ interface ShipeasyClientConfig {
171
267
  */
172
268
  autoIdentify?: boolean;
173
269
  /**
174
- * Capture web vitals (LCP, CLS, INP, TTFB, FCP, navigation timing), JS /
175
- * network errors, and engagement signals (abandonment) as `__auto_*`
176
- * metric events. Defaults to `true` the worker bypasses event-catalog
177
- * validation for `__auto_*` names so this is safe out of the box.
270
+ * Capture web vitals (LCP, CLS, INP, TTFB, FCP, navigation timing) and
271
+ * engagement signals (abandonment) as `__auto_*` metric events, plus JS /
272
+ * network errors as structured error events in the errors primitive (same
273
+ * pipeline as `see()` grouped by fingerprint, near-real-time). Defaults
274
+ * to `true`.
178
275
  *
179
276
  * Pass `false` to disable everything, or a per-group object to narrow:
180
277
  *
@@ -183,8 +280,23 @@ interface ShipeasyClientConfig {
183
280
  * shipeasy({ clientKey, autoCollect: { errors: false } }); // vitals + engagement only
184
281
  * shipeasy({ clientKey }); // all groups on
185
282
  * ```
283
+ *
284
+ * Since 4.1.0, `__auto_*` metric events are only emitted for visitors who
285
+ * are in ≥1 active experiment (the analysis pipeline reads nothing else;
286
+ * gating cuts the dominant Analytics Engine write cost at scale). Pass
287
+ * `{ always: true }` to collect site-wide vitals regardless of experiment
288
+ * participation:
289
+ *
290
+ * ```ts
291
+ * shipeasy({ clientKey, autoCollect: { always: true } }); // site-wide vitals
292
+ * ```
293
+ *
294
+ * `__auto_abandoned` is always emitted (it fires when the user leaves
295
+ * before exposures could land) and error capture is unaffected.
186
296
  */
187
- autoCollect?: boolean | Partial<AutoCollectGroups>;
297
+ autoCollect?: boolean | (Partial<AutoCollectGroups> & {
298
+ always?: boolean;
299
+ });
188
300
  /**
189
301
  * Disable per-evaluation usage telemetry. Telemetry is ON by default — every
190
302
  * flag/config/experiment/killswitch read fires one fire-and-forget beacon
@@ -276,6 +388,58 @@ declare const flags: {
276
388
  /** True once identify() has completed and flags are available. */
277
389
  readonly ready: boolean;
278
390
  };
391
+ interface SeeApi {
392
+ /**
393
+ * Report a handled problem and its product consequence:
394
+ *
395
+ * ```ts
396
+ * import { see } from "@shipeasy/sdk/client";
397
+ *
398
+ * try {
399
+ * await submitOrder(order);
400
+ * } catch (e) {
401
+ * see(e).causes_the("checkout").to("use cached prices").extras({ order_id: order.id });
402
+ * }
403
+ * ```
404
+ *
405
+ * The chain dispatches on the next microtask, so the report ships
406
+ * immediately after the statement (no `.send()` needed) into the errors
407
+ * primitive — grouped by fingerprint, near-real-time timeseries. If you
408
+ * don't know the consequence of an exception, don't catch it.
409
+ */
410
+ (problem: unknown): SeeChain;
411
+ /**
412
+ * Report a non-exception problem. Prefer passing a caught Error to `see()`
413
+ * when one exists. The name is a stable identifier (it participates in the
414
+ * issue fingerprint) — variable data goes in `.message()` or `.extras()`.
415
+ *
416
+ * ```ts
417
+ * if (rows.length > LIMIT) {
418
+ * see.Violation("large query").message(`got ${rows.length} rows`)
419
+ * .causes_the("search results").to("be trimmed");
420
+ * }
421
+ * ```
422
+ */
423
+ Violation(name: string): SeeViolationChain;
424
+ /**
425
+ * Mark an exception as expected control flow — auto-capture skips it and
426
+ * nothing is reported. The reason must start with "because".
427
+ *
428
+ * ```ts
429
+ * } catch (e) {
430
+ * see.ControlFlowException(e, "because the blob wasn't an encoded Foo");
431
+ * return decodeAsBar(blob);
432
+ * }
433
+ * ```
434
+ */
435
+ ControlFlowException(err: unknown, because: string): void;
436
+ }
437
+ /**
438
+ * Structured error reporter — the whole grammar hangs off this one import.
439
+ * Safe to import anywhere; a call before `shipeasy({ clientKey })` warns and
440
+ * drops (never throws).
441
+ */
442
+ declare const see: SeeApi;
279
443
  declare const LABEL_MARKER_START = "\uFFF9";
280
444
  declare const LABEL_MARKER_SEP = "\uFFFA";
281
445
  declare const LABEL_MARKER_END = "\uFFFB";
@@ -314,4 +478,4 @@ interface I18nFacade {
314
478
  }
315
479
  declare const i18n: I18nFacade;
316
480
 
317
- export { type AutoCollectGroups, type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
481
+ export { type AutoCollectGroups, type BootstrapPayload, type Consequence, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, type Violation, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, see, shipeasy, version };