@shipeasy/sdk 4.2.0 → 4.3.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.
@@ -51,6 +51,16 @@ interface SeeErrorEvent {
51
51
  env?: string;
52
52
  sdk_version: string;
53
53
  ts: number;
54
+ /**
55
+ * Per-request correlation token. The client mints one per same-origin fetch
56
+ * and ships it on both the request header (`X-SE-Correlation`) and any 5xx
57
+ * occurrence it reports; the server safety net reports the matching uncaught
58
+ * error under the same token. The backend joins the two issues by it —
59
+ * populating `caused_by` across the network boundary, where the in-process
60
+ * `.cause`-chain stamp (see `findCausedBy`) cannot reach. Join-only metadata,
61
+ * never persisted as an issue field.
62
+ */
63
+ correlation_id?: string;
54
64
  /**
55
65
  * The earlier reported problem this occurrence descends from — present when
56
66
  * the same error was caught + reported at an inner boundary and then
@@ -120,6 +130,15 @@ interface AutoCollectGroups {
120
130
  errors: boolean;
121
131
  engagement: boolean;
122
132
  }
133
+ /** True when `rawUrl` resolves to the page's own origin (relative URLs included). */
134
+ declare function sameOrigin(rawUrl: string): boolean;
135
+ /**
136
+ * Return a new `fetch` args tuple with `X-SE-Correlation` added, preserving any
137
+ * existing headers across all three arg shapes (string / URL / Request). Never
138
+ * mutates the caller's objects. Best-effort: on any failure the original args
139
+ * pass through unchanged (correlation is optional, never breaks the fetch).
140
+ */
141
+ declare function injectCorrelationHeader(args: Parameters<typeof fetch>, corr: string): Parameters<typeof fetch>;
123
142
  type FlagsClientBrowserEnv = "dev" | "staging" | "prod";
124
143
  interface FlagsClientBrowserOptions {
125
144
  sdkKey: string;
@@ -176,7 +195,7 @@ declare class FlagsClientBrowser {
176
195
  * (beacon-first) — error occurrences are near-real-time, never queued behind
177
196
  * the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
178
197
  */
179
- reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind): void;
198
+ reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind, correlationId?: string): void;
180
199
  get ready(): boolean;
181
200
  private notify;
182
201
  initFromBootstrap(data: EvalResponse): void;
@@ -478,4 +497,4 @@ interface I18nFacade {
478
497
  }
479
498
  declare const i18n: I18nFacade;
480
499
 
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 };
500
+ 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, injectCorrelationHeader, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, sameOrigin, see, shipeasy, version };
@@ -51,6 +51,16 @@ interface SeeErrorEvent {
51
51
  env?: string;
52
52
  sdk_version: string;
53
53
  ts: number;
54
+ /**
55
+ * Per-request correlation token. The client mints one per same-origin fetch
56
+ * and ships it on both the request header (`X-SE-Correlation`) and any 5xx
57
+ * occurrence it reports; the server safety net reports the matching uncaught
58
+ * error under the same token. The backend joins the two issues by it —
59
+ * populating `caused_by` across the network boundary, where the in-process
60
+ * `.cause`-chain stamp (see `findCausedBy`) cannot reach. Join-only metadata,
61
+ * never persisted as an issue field.
62
+ */
63
+ correlation_id?: string;
54
64
  /**
55
65
  * The earlier reported problem this occurrence descends from — present when
56
66
  * the same error was caught + reported at an inner boundary and then
@@ -120,6 +130,15 @@ interface AutoCollectGroups {
120
130
  errors: boolean;
121
131
  engagement: boolean;
122
132
  }
133
+ /** True when `rawUrl` resolves to the page's own origin (relative URLs included). */
134
+ declare function sameOrigin(rawUrl: string): boolean;
135
+ /**
136
+ * Return a new `fetch` args tuple with `X-SE-Correlation` added, preserving any
137
+ * existing headers across all three arg shapes (string / URL / Request). Never
138
+ * mutates the caller's objects. Best-effort: on any failure the original args
139
+ * pass through unchanged (correlation is optional, never breaks the fetch).
140
+ */
141
+ declare function injectCorrelationHeader(args: Parameters<typeof fetch>, corr: string): Parameters<typeof fetch>;
123
142
  type FlagsClientBrowserEnv = "dev" | "staging" | "prod";
124
143
  interface FlagsClientBrowserOptions {
125
144
  sdkKey: string;
@@ -176,7 +195,7 @@ declare class FlagsClientBrowser {
176
195
  * (beacon-first) — error occurrences are near-real-time, never queued behind
177
196
  * the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
178
197
  */
179
- reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind): void;
198
+ reportError(problem: unknown, consequence: Consequence, extras?: SeeExtras, kind?: SeeKind, correlationId?: string): void;
180
199
  get ready(): boolean;
181
200
  private notify;
182
201
  initFromBootstrap(data: EvalResponse): void;
@@ -478,4 +497,4 @@ interface I18nFacade {
478
497
  }
479
498
  declare const i18n: I18nFacade;
480
499
 
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 };
500
+ 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, injectCorrelationHeader, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, sameOrigin, see, shipeasy, version };
@@ -32,12 +32,14 @@ __export(client_exports, {
32
32
  flags: () => flags,
33
33
  getShipeasyClient: () => getShipeasyClient,
34
34
  i18n: () => i18n,
35
+ injectCorrelationHeader: () => injectCorrelationHeader,
35
36
  isDevtoolsRequested: () => isDevtoolsRequested,
36
37
  labelAttrs: () => labelAttrs,
37
38
  loadDevtools: () => loadDevtools,
38
39
  readConfigOverride: () => readConfigOverride,
39
40
  readExpOverride: () => readExpOverride,
40
41
  readGateOverride: () => readGateOverride,
42
+ sameOrigin: () => sameOrigin,
41
43
  see: () => see,
42
44
  shipeasy: () => shipeasy,
43
45
  version: () => version
@@ -109,6 +111,7 @@ var SEE_MAX_STACK = 8e3;
109
111
  var SEE_MAX_SUBJECT = 200;
110
112
  var SEE_MAX_EXTRA_VALUE = 200;
111
113
  var SEE_MAX_EXTRA_KEYS = 20;
114
+ var SEE_MAX_CORRELATION = 64;
112
115
  var SEE_DEDUP_WINDOW_MS = 3e4;
113
116
  var SEE_MAX_PER_SESSION = 25;
114
117
  function causesThe(subject) {
@@ -215,7 +218,7 @@ function captureCallsiteStack() {
215
218
  const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
216
219
  return kept.length ? kept.join("\n") : void 0;
217
220
  }
218
- function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
221
+ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
219
222
  let errorType;
220
223
  let message;
221
224
  let stack;
@@ -248,6 +251,7 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
248
251
  ts: Date.now()
249
252
  };
250
253
  if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
254
+ if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
251
255
  const causedBy = findCausedBy(problem);
252
256
  if (causedBy) ev.caused_by = causedBy;
253
257
  const cleanExtras = sanitizeExtras(extras);
@@ -501,8 +505,33 @@ function endpointTemplate(rawUrl) {
501
505
  return (rawUrl.split(/[?#]/)[0] ?? "").slice(0, 120);
502
506
  }
503
507
  const path = u.pathname.split("/").map((seg) => seg && isIdSegment(seg) ? ":id" : seg).join("/");
504
- const sameOrigin = typeof location !== "undefined" && u.origin === location.origin;
505
- return ((sameOrigin ? "" : u.host) + path).slice(0, 120);
508
+ const sameOrigin2 = typeof location !== "undefined" && u.origin === location.origin;
509
+ return ((sameOrigin2 ? "" : u.host) + path).slice(0, 120);
510
+ }
511
+ function sameOrigin(rawUrl) {
512
+ if (typeof location === "undefined") return false;
513
+ try {
514
+ return new URL(rawUrl, location.href).origin === location.origin;
515
+ } catch {
516
+ return false;
517
+ }
518
+ }
519
+ function injectCorrelationHeader(args, corr) {
520
+ try {
521
+ const input = args[0];
522
+ if (typeof Request !== "undefined" && input instanceof Request) {
523
+ const headers2 = new Headers(input.headers);
524
+ headers2.set("X-SE-Correlation", corr);
525
+ return [new Request(input, { headers: headers2 }), ...args.slice(1)];
526
+ }
527
+ const init = { ...args[1] ?? {} };
528
+ const headers = new Headers(init.headers ?? void 0);
529
+ headers.set("X-SE-Correlation", corr);
530
+ init.headers = headers;
531
+ return [input, init];
532
+ } catch {
533
+ return args;
534
+ }
506
535
  }
507
536
  function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignoreUrlPrefixes, always = false) {
508
537
  if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
@@ -579,6 +608,11 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
579
608
  const url = typeof args[0] === "string" ? args[0] : args[0].toString();
580
609
  const ignored = ignoreUrlPrefixes.some((p) => p && url.startsWith(p));
581
610
  const bareUrl = url.split("?")[0].slice(0, 200);
611
+ let corr;
612
+ if (!ignored && sameOrigin(url) && typeof crypto !== "undefined" && crypto.randomUUID) {
613
+ corr = crypto.randomUUID();
614
+ args = injectCorrelationHeader(args, corr);
615
+ }
582
616
  let res;
583
617
  try {
584
618
  res = await origFetch.apply(this, args);
@@ -599,7 +633,8 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
599
633
  violation("Http5xx").message(`request to ${bareUrl} returned ${res.status}`),
600
634
  causesThe(`request to ${endpointTemplate(url)}`).to("fail with a server error"),
601
635
  { status: res.status, url: url.slice(0, 200), duration_ms: Math.round(elapsed) },
602
- "network"
636
+ "network",
637
+ corr
603
638
  );
604
639
  }
605
640
  return res;
@@ -911,7 +946,7 @@ var FlagsClientBrowser = class {
911
946
  this.userId,
912
947
  this.anonId,
913
948
  this.autoGuardrailGroups,
914
- (problem, consequence, extras, kind) => this.reportError(problem, consequence, extras, kind),
949
+ (problem, consequence, extras, kind, correlationId) => this.reportError(problem, consequence, extras, kind, correlationId),
915
950
  [`${this.baseUrl}/`, DEFAULT_TELEMETRY_URL],
916
951
  this.autoCollectAlways
917
952
  );
@@ -923,7 +958,7 @@ var FlagsClientBrowser = class {
923
958
  * (beacon-first) — error occurrences are near-real-time, never queued behind
924
959
  * the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
925
960
  */
926
- reportError(problem, consequence, extras, kind) {
961
+ reportError(problem, consequence, extras, kind, correlationId) {
927
962
  try {
928
963
  const enriched = { ...collectSeeEnv(), ...extras };
929
964
  const ev = buildSeeEvent(problem, consequence, enriched, {
@@ -933,7 +968,7 @@ var FlagsClientBrowser = class {
933
968
  url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
934
969
  userId: this.userId || void 0,
935
970
  anonId: this.anonId
936
- }, kind);
971
+ }, kind, correlationId);
937
972
  if (!this.seeLimiter.shouldSend(ev)) return;
938
973
  this.buffer.sendNow([ev]);
939
974
  } catch {
@@ -1600,12 +1635,14 @@ var i18n = {
1600
1635
  flags,
1601
1636
  getShipeasyClient,
1602
1637
  i18n,
1638
+ injectCorrelationHeader,
1603
1639
  isDevtoolsRequested,
1604
1640
  labelAttrs,
1605
1641
  loadDevtools,
1606
1642
  readConfigOverride,
1607
1643
  readExpOverride,
1608
1644
  readGateOverride,
1645
+ sameOrigin,
1609
1646
  see,
1610
1647
  shipeasy,
1611
1648
  version
@@ -63,6 +63,7 @@ var SEE_MAX_STACK = 8e3;
63
63
  var SEE_MAX_SUBJECT = 200;
64
64
  var SEE_MAX_EXTRA_VALUE = 200;
65
65
  var SEE_MAX_EXTRA_KEYS = 20;
66
+ var SEE_MAX_CORRELATION = 64;
66
67
  var SEE_DEDUP_WINDOW_MS = 3e4;
67
68
  var SEE_MAX_PER_SESSION = 25;
68
69
  function causesThe(subject) {
@@ -169,7 +170,7 @@ function captureCallsiteStack() {
169
170
  const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
170
171
  return kept.length ? kept.join("\n") : void 0;
171
172
  }
172
- function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
173
+ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
173
174
  let errorType;
174
175
  let message;
175
176
  let stack;
@@ -202,6 +203,7 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
202
203
  ts: Date.now()
203
204
  };
204
205
  if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
206
+ if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
205
207
  const causedBy = findCausedBy(problem);
206
208
  if (causedBy) ev.caused_by = causedBy;
207
209
  const cleanExtras = sanitizeExtras(extras);
@@ -455,8 +457,33 @@ function endpointTemplate(rawUrl) {
455
457
  return (rawUrl.split(/[?#]/)[0] ?? "").slice(0, 120);
456
458
  }
457
459
  const path = u.pathname.split("/").map((seg) => seg && isIdSegment(seg) ? ":id" : seg).join("/");
458
- const sameOrigin = typeof location !== "undefined" && u.origin === location.origin;
459
- return ((sameOrigin ? "" : u.host) + path).slice(0, 120);
460
+ const sameOrigin2 = typeof location !== "undefined" && u.origin === location.origin;
461
+ return ((sameOrigin2 ? "" : u.host) + path).slice(0, 120);
462
+ }
463
+ function sameOrigin(rawUrl) {
464
+ if (typeof location === "undefined") return false;
465
+ try {
466
+ return new URL(rawUrl, location.href).origin === location.origin;
467
+ } catch {
468
+ return false;
469
+ }
470
+ }
471
+ function injectCorrelationHeader(args, corr) {
472
+ try {
473
+ const input = args[0];
474
+ if (typeof Request !== "undefined" && input instanceof Request) {
475
+ const headers2 = new Headers(input.headers);
476
+ headers2.set("X-SE-Correlation", corr);
477
+ return [new Request(input, { headers: headers2 }), ...args.slice(1)];
478
+ }
479
+ const init = { ...args[1] ?? {} };
480
+ const headers = new Headers(init.headers ?? void 0);
481
+ headers.set("X-SE-Correlation", corr);
482
+ init.headers = headers;
483
+ return [input, init];
484
+ } catch {
485
+ return args;
486
+ }
460
487
  }
461
488
  function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignoreUrlPrefixes, always = false) {
462
489
  if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
@@ -533,6 +560,11 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
533
560
  const url = typeof args[0] === "string" ? args[0] : args[0].toString();
534
561
  const ignored = ignoreUrlPrefixes.some((p) => p && url.startsWith(p));
535
562
  const bareUrl = url.split("?")[0].slice(0, 200);
563
+ let corr;
564
+ if (!ignored && sameOrigin(url) && typeof crypto !== "undefined" && crypto.randomUUID) {
565
+ corr = crypto.randomUUID();
566
+ args = injectCorrelationHeader(args, corr);
567
+ }
536
568
  let res;
537
569
  try {
538
570
  res = await origFetch.apply(this, args);
@@ -553,7 +585,8 @@ function installAutoGuardrails(buffer, userId, anonId, groups, reportSee, ignore
553
585
  violation("Http5xx").message(`request to ${bareUrl} returned ${res.status}`),
554
586
  causesThe(`request to ${endpointTemplate(url)}`).to("fail with a server error"),
555
587
  { status: res.status, url: url.slice(0, 200), duration_ms: Math.round(elapsed) },
556
- "network"
588
+ "network",
589
+ corr
557
590
  );
558
591
  }
559
592
  return res;
@@ -865,7 +898,7 @@ var FlagsClientBrowser = class {
865
898
  this.userId,
866
899
  this.anonId,
867
900
  this.autoGuardrailGroups,
868
- (problem, consequence, extras, kind) => this.reportError(problem, consequence, extras, kind),
901
+ (problem, consequence, extras, kind, correlationId) => this.reportError(problem, consequence, extras, kind, correlationId),
869
902
  [`${this.baseUrl}/`, DEFAULT_TELEMETRY_URL],
870
903
  this.autoCollectAlways
871
904
  );
@@ -877,7 +910,7 @@ var FlagsClientBrowser = class {
877
910
  * (beacon-first) — error occurrences are near-real-time, never queued behind
878
911
  * the 5s metric batch. Spam-guarded by a 30s dedup window + per-session cap.
879
912
  */
880
- reportError(problem, consequence, extras, kind) {
913
+ reportError(problem, consequence, extras, kind, correlationId) {
881
914
  try {
882
915
  const enriched = { ...collectSeeEnv(), ...extras };
883
916
  const ev = buildSeeEvent(problem, consequence, enriched, {
@@ -887,7 +920,7 @@ var FlagsClientBrowser = class {
887
920
  url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
888
921
  userId: this.userId || void 0,
889
922
  anonId: this.anonId
890
- }, kind);
923
+ }, kind, correlationId);
891
924
  if (!this.seeLimiter.shouldSend(ev)) return;
892
925
  this.buffer.sendNow([ev]);
893
926
  } catch {
@@ -1553,12 +1586,14 @@ export {
1553
1586
  flags,
1554
1587
  getShipeasyClient,
1555
1588
  i18n,
1589
+ injectCorrelationHeader,
1556
1590
  isDevtoolsRequested,
1557
1591
  labelAttrs,
1558
1592
  loadDevtools,
1559
1593
  readConfigOverride,
1560
1594
  readExpOverride,
1561
1595
  readGateOverride,
1596
+ sameOrigin,
1562
1597
  see,
1563
1598
  shipeasy,
1564
1599
  version
@@ -1,3 +1,5 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+
1
3
  type SeeExtras = Record<string, string | number | boolean | null | undefined>;
2
4
  type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
3
5
  /** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
@@ -51,6 +53,16 @@ interface SeeErrorEvent {
51
53
  env?: string;
52
54
  sdk_version: string;
53
55
  ts: number;
56
+ /**
57
+ * Per-request correlation token. The client mints one per same-origin fetch
58
+ * and ships it on both the request header (`X-SE-Correlation`) and any 5xx
59
+ * occurrence it reports; the server safety net reports the matching uncaught
60
+ * error under the same token. The backend joins the two issues by it —
61
+ * populating `caused_by` across the network boundary, where the in-process
62
+ * `.cause`-chain stamp (see `findCausedBy`) cannot reach. Join-only metadata,
63
+ * never persisted as an issue field.
64
+ */
65
+ correlation_id?: string;
54
66
  /**
55
67
  * The earlier reported problem this occurrence descends from — present when
56
68
  * the same error was caught + reported at an inner boundary and then
@@ -60,6 +72,7 @@ interface SeeErrorEvent {
60
72
  */
61
73
  caused_by?: SeeCausedBy;
62
74
  }
75
+ declare function isExpected(err: unknown): boolean;
63
76
  interface SeeExtrasTail {
64
77
  /** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
65
78
  extras(extras: SeeExtras): SeeExtrasTail;
@@ -187,6 +200,11 @@ declare class FlagsClient {
187
200
  evaluate(user: User, rawUrl?: string): BootstrapPayload;
188
201
  getKillswitch(name: string, switchKey?: string): boolean;
189
202
  }
203
+ interface SeeCorrelationStore {
204
+ correlationId?: string;
205
+ }
206
+ declare const seeContext: AsyncLocalStorage<SeeCorrelationStore>;
207
+
190
208
  interface I18nForRequest {
191
209
  strings: Record<string, string>;
192
210
  locale: string;
@@ -362,4 +380,4 @@ interface SeeApi {
362
380
  */
363
381
  declare const see: SeeApi;
364
382
 
365
- export { type BootstrapHtmlOptions, type BootstrapPayload, type Consequence, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, type Violation, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, see, shipeasy, version };
383
+ export { type BootstrapHtmlOptions, type BootstrapPayload, type Consequence, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, type Violation, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, isExpected, see, seeContext, shipeasy, version };
@@ -1,3 +1,5 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+
1
3
  type SeeExtras = Record<string, string | number | boolean | null | undefined>;
2
4
  type SeeKind = "caught" | "uncaught" | "unhandled_rejection" | "network" | "violation";
3
5
  /** Built by `causesThe(subject).to(outcome)` — never constructed by hand. */
@@ -51,6 +53,16 @@ interface SeeErrorEvent {
51
53
  env?: string;
52
54
  sdk_version: string;
53
55
  ts: number;
56
+ /**
57
+ * Per-request correlation token. The client mints one per same-origin fetch
58
+ * and ships it on both the request header (`X-SE-Correlation`) and any 5xx
59
+ * occurrence it reports; the server safety net reports the matching uncaught
60
+ * error under the same token. The backend joins the two issues by it —
61
+ * populating `caused_by` across the network boundary, where the in-process
62
+ * `.cause`-chain stamp (see `findCausedBy`) cannot reach. Join-only metadata,
63
+ * never persisted as an issue field.
64
+ */
65
+ correlation_id?: string;
54
66
  /**
55
67
  * The earlier reported problem this occurrence descends from — present when
56
68
  * the same error was caught + reported at an inner boundary and then
@@ -60,6 +72,7 @@ interface SeeErrorEvent {
60
72
  */
61
73
  caused_by?: SeeCausedBy;
62
74
  }
75
+ declare function isExpected(err: unknown): boolean;
63
76
  interface SeeExtrasTail {
64
77
  /** Attach debugging metadata. Callable repeatedly — keys merge, later wins. */
65
78
  extras(extras: SeeExtras): SeeExtrasTail;
@@ -187,6 +200,11 @@ declare class FlagsClient {
187
200
  evaluate(user: User, rawUrl?: string): BootstrapPayload;
188
201
  getKillswitch(name: string, switchKey?: string): boolean;
189
202
  }
203
+ interface SeeCorrelationStore {
204
+ correlationId?: string;
205
+ }
206
+ declare const seeContext: AsyncLocalStorage<SeeCorrelationStore>;
207
+
190
208
  interface I18nForRequest {
191
209
  strings: Record<string, string>;
192
210
  locale: string;
@@ -362,4 +380,4 @@ interface SeeApi {
362
380
  */
363
381
  declare const see: SeeApi;
364
382
 
365
- export { type BootstrapHtmlOptions, type BootstrapPayload, type Consequence, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, type Violation, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, see, shipeasy, version };
383
+ export { type BootstrapHtmlOptions, type BootstrapPayload, type Consequence, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type SeeApi, type SeeChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, type Violation, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, isExpected, see, seeContext, shipeasy, version };
@@ -38,7 +38,9 @@ __export(server_exports, {
38
38
  getBootstrapHtml: () => getBootstrapHtml,
39
39
  getShipeasyServerClient: () => getShipeasyServerClient,
40
40
  i18n: () => i18n,
41
+ isExpected: () => isExpected,
41
42
  see: () => see,
43
+ seeContext: () => seeContext,
42
44
  shipeasy: () => shipeasy,
43
45
  version: () => version
44
46
  });
@@ -110,6 +112,7 @@ var SEE_MAX_STACK = 8e3;
110
112
  var SEE_MAX_SUBJECT = 200;
111
113
  var SEE_MAX_EXTRA_VALUE = 200;
112
114
  var SEE_MAX_EXTRA_KEYS = 20;
115
+ var SEE_MAX_CORRELATION = 64;
113
116
  var SEE_DEDUP_WINDOW_MS = 3e4;
114
117
  var SEE_MAX_PER_SESSION = 25;
115
118
  function causesThe(subject) {
@@ -149,6 +152,10 @@ function markExpected(err, because) {
149
152
  } catch {
150
153
  }
151
154
  }
155
+ function isExpected(err) {
156
+ if (typeof err !== "object" || err === null) return false;
157
+ return err[EXPECTED_SYM] !== void 0;
158
+ }
152
159
  var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
153
160
  var SEE_MAX_CAUSE_DEPTH = 8;
154
161
  function readReportStamp(err) {
@@ -212,7 +219,7 @@ function captureCallsiteStack() {
212
219
  const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
213
220
  return kept.length ? kept.join("\n") : void 0;
214
221
  }
215
- function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
222
+ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
216
223
  let errorType;
217
224
  let message;
218
225
  let stack;
@@ -245,6 +252,7 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
245
252
  ts: Date.now()
246
253
  };
247
254
  if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
255
+ if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
248
256
  const causedBy = findCausedBy(problem);
249
257
  if (causedBy) ev.caused_by = causedBy;
250
258
  const cleanExtras = sanitizeExtras(extras);
@@ -654,11 +662,12 @@ var FlagsClient = class {
654
662
  */
655
663
  reportError(problem, consequence, extras, kind) {
656
664
  try {
665
+ const correlationId = seeContext.getStore()?.correlationId;
657
666
  const ev = buildSeeEvent(problem, consequence, extras, {
658
667
  side: "server",
659
668
  sdkVersion: version,
660
669
  env: this.env
661
- }, kind);
670
+ }, kind, correlationId);
662
671
  if (!this.seeLimiter.shouldSend(ev)) return;
663
672
  globalThis.fetch(`${this.baseUrl}/collect`, {
664
673
  method: "POST",
@@ -753,6 +762,8 @@ Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
753
762
  },
754
763
  configurable: true
755
764
  });
765
+ var _SEE_CORR_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-correlation-als");
766
+ var seeContext = globalThis[_SEE_CORR_ALS_SYM] ?? (globalThis[_SEE_CORR_ALS_SYM] = new import_node_async_hooks.AsyncLocalStorage());
756
767
  var i18n = {
757
768
  /**
758
769
  * Fetch translation labels for the current request and store them in an
@@ -1000,7 +1011,9 @@ var see = Object.assign(
1000
1011
  getBootstrapHtml,
1001
1012
  getShipeasyServerClient,
1002
1013
  i18n,
1014
+ isExpected,
1003
1015
  see,
1016
+ seeContext,
1004
1017
  shipeasy,
1005
1018
  version
1006
1019
  });
@@ -66,6 +66,7 @@ var SEE_MAX_STACK = 8e3;
66
66
  var SEE_MAX_SUBJECT = 200;
67
67
  var SEE_MAX_EXTRA_VALUE = 200;
68
68
  var SEE_MAX_EXTRA_KEYS = 20;
69
+ var SEE_MAX_CORRELATION = 64;
69
70
  var SEE_DEDUP_WINDOW_MS = 3e4;
70
71
  var SEE_MAX_PER_SESSION = 25;
71
72
  function causesThe(subject) {
@@ -105,6 +106,10 @@ function markExpected(err, because) {
105
106
  } catch {
106
107
  }
107
108
  }
109
+ function isExpected(err) {
110
+ if (typeof err !== "object" || err === null) return false;
111
+ return err[EXPECTED_SYM] !== void 0;
112
+ }
108
113
  var REPORTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-reported");
109
114
  var SEE_MAX_CAUSE_DEPTH = 8;
110
115
  function readReportStamp(err) {
@@ -168,7 +173,7 @@ function captureCallsiteStack() {
168
173
  const kept = lines.slice(1).filter((l) => !/@shipeasy[\\/]sdk|see[\\/]core|captureCallsiteStack|\bsee\b\s*\(/.test(l));
169
174
  return kept.length ? kept.join("\n") : void 0;
170
175
  }
171
- function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
176
+ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride, correlationId) {
172
177
  let errorType;
173
178
  let message;
174
179
  let stack;
@@ -201,6 +206,7 @@ function buildSeeEvent(problem, consequence, extras, ctx, kindOverride) {
201
206
  ts: Date.now()
202
207
  };
203
208
  if (stack) ev.stack = truncate(stack, SEE_MAX_STACK);
209
+ if (correlationId) ev.correlation_id = truncate(String(correlationId), SEE_MAX_CORRELATION);
204
210
  const causedBy = findCausedBy(problem);
205
211
  if (causedBy) ev.caused_by = causedBy;
206
212
  const cleanExtras = sanitizeExtras(extras);
@@ -610,11 +616,12 @@ var FlagsClient = class {
610
616
  */
611
617
  reportError(problem, consequence, extras, kind) {
612
618
  try {
619
+ const correlationId = seeContext.getStore()?.correlationId;
613
620
  const ev = buildSeeEvent(problem, consequence, extras, {
614
621
  side: "server",
615
622
  sdkVersion: version,
616
623
  env: this.env
617
- }, kind);
624
+ }, kind, correlationId);
618
625
  if (!this.seeLimiter.shouldSend(ev)) return;
619
626
  globalThis.fetch(`${this.baseUrl}/collect`, {
620
627
  method: "POST",
@@ -709,6 +716,8 @@ Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
709
716
  },
710
717
  configurable: true
711
718
  });
719
+ var _SEE_CORR_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-correlation-als");
720
+ var seeContext = globalThis[_SEE_CORR_ALS_SYM] ?? (globalThis[_SEE_CORR_ALS_SYM] = new AsyncLocalStorage());
712
721
  var i18n = {
713
722
  /**
714
723
  * Fetch translation labels for the current request and store them in an
@@ -955,7 +964,9 @@ export {
955
964
  getBootstrapHtml,
956
965
  getShipeasyServerClient,
957
966
  i18n,
967
+ isExpected,
958
968
  see,
969
+ seeContext,
959
970
  shipeasy,
960
971
  version
961
972
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipeasy/sdk",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "Shipeasy SDK — feature gates, runtime configs, experiments, and metrics for the Shipeasy hosted service.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://shipeasy.ai",