@glasstrace/sdk 1.7.0 → 1.9.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.
Files changed (75) hide show
  1. package/dist/async-context/index.cjs +15005 -0
  2. package/dist/async-context/index.cjs.map +1 -0
  3. package/dist/async-context/index.d.cts +174 -0
  4. package/dist/async-context/index.d.ts +174 -0
  5. package/dist/async-context/index.js +13 -0
  6. package/dist/{capture-error-CTgSYxek.d.ts → capture-error-BeuEXXJO.d.cts} +40 -2
  7. package/dist/{capture-error-BmQz7xF6.d.cts → capture-error-D02pzB7q.d.ts} +40 -2
  8. package/dist/chunk-CL3OVHPO.js +23 -0
  9. package/dist/chunk-CL3OVHPO.js.map +1 -0
  10. package/dist/{chunk-WL6BXEJ5.js → chunk-DKV53A2C.js} +2 -2
  11. package/dist/{chunk-3PJP5Y3U.js → chunk-GWIEUBFR.js} +3 -3
  12. package/dist/{chunk-H57MQGNU.js → chunk-H6WJ63X2.js} +2 -2
  13. package/dist/{chunk-NN5YCETI.js → chunk-HD6JIFKN.js} +2 -2
  14. package/dist/{chunk-P45NZR4J.js → chunk-JHUNLPSS.js} +35 -1
  15. package/dist/{chunk-P45NZR4J.js.map → chunk-JHUNLPSS.js.map} +1 -1
  16. package/dist/{chunk-UQKI476D.js → chunk-M6EWJCAT.js} +2 -2
  17. package/dist/chunk-QHV7NFON.js +130 -0
  18. package/dist/chunk-QHV7NFON.js.map +1 -0
  19. package/dist/{chunk-M2TLX6NM.js → chunk-QXITSNYM.js} +3 -3
  20. package/dist/chunk-RQ5BIWDT.js +119 -0
  21. package/dist/chunk-RQ5BIWDT.js.map +1 -0
  22. package/dist/{chunk-OWPA7GHV.js → chunk-XEPC4NFL.js} +1398 -1037
  23. package/dist/chunk-XEPC4NFL.js.map +1 -0
  24. package/dist/cli/init.cjs +4 -4
  25. package/dist/cli/init.cjs.map +1 -1
  26. package/dist/cli/init.js +7 -7
  27. package/dist/cli/mcp-add.cjs +1 -1
  28. package/dist/cli/mcp-add.cjs.map +1 -1
  29. package/dist/cli/mcp-add.js +3 -3
  30. package/dist/cli/uninit.js +3 -3
  31. package/dist/cli/upgrade-instructions.cjs +1 -1
  32. package/dist/cli/upgrade-instructions.js +3 -3
  33. package/dist/cli/validate.cjs.map +1 -1
  34. package/dist/cli/validate.js +2 -2
  35. package/dist/{edge-entry-AWO70gje.d.ts → correlation-id-B_K8adD6.d.ts} +1 -1
  36. package/dist/{edge-entry-DaeG7D7S.d.cts → correlation-id-NAapJ5jn.d.cts} +1 -1
  37. package/dist/edge-entry.cjs +295 -26
  38. package/dist/edge-entry.cjs.map +1 -1
  39. package/dist/edge-entry.d.cts +5 -2
  40. package/dist/edge-entry.d.ts +5 -2
  41. package/dist/edge-entry.js +12 -3
  42. package/dist/index.cjs +3613 -3211
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/{index.d-Dq33YwFT.d.cts → index.d-CkTf_boH.d.cts} +1 -1
  45. package/dist/{index.d-Dq33YwFT.d.ts → index.d-CkTf_boH.d.ts} +1 -1
  46. package/dist/index.d.cts +7 -4
  47. package/dist/index.d.ts +7 -4
  48. package/dist/index.js +6 -5
  49. package/dist/index.js.map +1 -1
  50. package/dist/middleware/index.cjs +15014 -0
  51. package/dist/middleware/index.cjs.map +1 -0
  52. package/dist/middleware/index.d.cts +183 -0
  53. package/dist/middleware/index.d.ts +183 -0
  54. package/dist/middleware/index.js +13 -0
  55. package/dist/middleware/index.js.map +1 -0
  56. package/dist/node-entry.cjs +3613 -3211
  57. package/dist/node-entry.cjs.map +1 -1
  58. package/dist/node-entry.d.cts +3 -3
  59. package/dist/node-entry.d.ts +3 -3
  60. package/dist/node-entry.js +8 -7
  61. package/dist/node-subpath.cjs.map +1 -1
  62. package/dist/node-subpath.d.cts +1 -1
  63. package/dist/node-subpath.d.ts +1 -1
  64. package/dist/node-subpath.js +3 -3
  65. package/dist/{source-map-uploader-XFUEVV7I.js → source-map-uploader-MMJ2WCL4.js} +3 -3
  66. package/dist/source-map-uploader-MMJ2WCL4.js.map +1 -0
  67. package/package.json +13 -1
  68. package/dist/chunk-OWPA7GHV.js.map +0 -1
  69. /package/dist/{source-map-uploader-XFUEVV7I.js.map → async-context/index.js.map} +0 -0
  70. /package/dist/{chunk-WL6BXEJ5.js.map → chunk-DKV53A2C.js.map} +0 -0
  71. /package/dist/{chunk-3PJP5Y3U.js.map → chunk-GWIEUBFR.js.map} +0 -0
  72. /package/dist/{chunk-H57MQGNU.js.map → chunk-H6WJ63X2.js.map} +0 -0
  73. /package/dist/{chunk-NN5YCETI.js.map → chunk-HD6JIFKN.js.map} +0 -0
  74. /package/dist/{chunk-UQKI476D.js.map → chunk-M6EWJCAT.js.map} +0 -0
  75. /package/dist/{chunk-M2TLX6NM.js.map → chunk-QXITSNYM.js.map} +0 -0
@@ -1,3 +1,6 @@
1
+ import {
2
+ _registerLifecycleEmitForBridge
3
+ } from "./chunk-CL3OVHPO.js";
1
4
  import {
2
5
  DiagLogLevel,
3
6
  INVALID_SPAN_CONTEXT,
@@ -33,7 +36,7 @@ import {
33
36
  performInit,
34
37
  recordSpansDropped,
35
38
  recordSpansExported
36
- } from "./chunk-M2TLX6NM.js";
39
+ } from "./chunk-QXITSNYM.js";
37
40
  import {
38
41
  isAnonymousMode,
39
42
  isProductionDisabled,
@@ -44,11 +47,11 @@ import {
44
47
  getOrCreateAnonKey,
45
48
  isSyncFsAvailable,
46
49
  readAnonKey
47
- } from "./chunk-WL6BXEJ5.js";
50
+ } from "./chunk-DKV53A2C.js";
48
51
  import {
49
52
  GLASSTRACE_ATTRIBUTE_NAMES,
50
53
  deriveSessionId
51
- } from "./chunk-P45NZR4J.js";
54
+ } from "./chunk-JHUNLPSS.js";
52
55
  import {
53
56
  isEndMarkerLine,
54
57
  parseStartMarkerLine
@@ -143,1114 +146,1428 @@ function classifyFetchTarget(url) {
143
146
  return "unknown";
144
147
  }
145
148
 
146
- // src/error-response-body.ts
147
- var ERROR_RESPONSE_BODY_MAX_BYTES = 4096;
148
- var ERROR_RESPONSE_BODY_TRUNCATION_MARKER = "...[truncated]";
149
- var REDACTED = "[REDACTED]";
150
- var ERROR_STATUS_MIN = 400;
151
- var ERROR_STATUS_MAX = 599;
152
- function coerceHttpStatus(value) {
153
- let numeric;
154
- if (typeof value === "number") {
155
- numeric = value;
156
- } else if (typeof value === "string") {
157
- const trimmed = value.trim();
158
- if (trimmed.length === 0) return void 0;
159
- numeric = Number(trimmed);
160
- } else {
161
- return void 0;
162
- }
163
- return Number.isFinite(numeric) ? numeric : void 0;
149
+ // src/lifecycle.ts
150
+ import { EventEmitter } from "node:events";
151
+
152
+ // src/signal-handler.ts
153
+ var coexistenceState = "unknown";
154
+ function setCoexistenceState(s) {
155
+ coexistenceState = s;
164
156
  }
165
- function isHttpErrorStatus(status) {
166
- const numeric = coerceHttpStatus(status);
167
- if (numeric === void 0) return false;
168
- return numeric >= ERROR_STATUS_MIN && numeric <= ERROR_STATUS_MAX;
157
+ function getCoexistenceState() {
158
+ return coexistenceState;
169
159
  }
170
- var REDACTION_PATTERNS = [
171
- // Order matters: redact specific token shapes BEFORE the generic
172
- // key=value catcher so a literal `Bearer eyJ…` collapses into a single
173
- // [REDACTED] and the JWT regex does not separately match the suffix.
174
- {
175
- name: "bearer",
176
- // Case-insensitive on the scheme: HTTP frameworks and proxies
177
- // round-trip the auth scheme with inconsistent casing
178
- // (`Bearer`, `bearer`, `BEARER`), and a real token leaks just as
179
- // badly under any of them.
180
- pattern: /\bBearer\s+[A-Za-z0-9._\-+/=]+/gi
181
- },
182
- {
183
- name: "jwt",
184
- // Three base64url segments separated by dots. Real JWTs encode at
185
- // minimum a small JSON header in the first segment, which alone is
186
- // typically ≥10 chars after base64url; a 16-char floor avoids false
187
- // positives on dotted text like a stack-trace frame
188
- // (`react.dom.server`) while still catching every real JWT we have
189
- // seen in the wild. Anchored with word boundaries on both sides so
190
- // a 3-dot semantic version like "next@15.4.1.2" does not match.
191
- pattern: /\b[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g
192
- },
193
- {
194
- name: "glasstrace-api-key",
195
- // gt_dev_* and gt_anon_* keys are >=24 chars of [A-Za-z0-9].
196
- pattern: /\bgt_(?:dev|anon)_[A-Za-z0-9]{16,}\b/g
197
- },
198
- {
199
- name: "aws-access-key",
200
- // 20-char prefix-fixed identifier.
201
- pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g
202
- },
203
- {
204
- name: "key-value-secret-quoted",
205
- // Quoted-string variant: (key) [:=] "<value>". The value runs to
206
- // the next unescaped closing quote so a multi-word secret like
207
- // `password="my secret phrase"` is fully consumed instead of
208
- // splitting at the first space and leaving the tail visible.
209
- // The leading `(?<![A-Za-z0-9_])` prevents matching inside
210
- // identifiers like `passwordless`. The trailing `"?` after the
211
- // keyword absorbs the closing quote in JSON-style `"apikey":
212
- // "value"` so the colon is still seen as the separator.
213
- pattern: /(?<![A-Za-z0-9_])(?:api[_-]?key|apikey|secret|password|token)"?\s*[:=]\s*"(?:[^"\\]|\\.)*"/gi
214
- },
215
- {
216
- name: "key-value-secret-bare",
217
- // Unquoted variant: (key) [:=] <bare-value>. The bare value
218
- // capture stops at common JSON/text delimiters so we redact only
219
- // the value, not surrounding structure. Listed AFTER the quoted
220
- // variant so a quoted value's surrounding `"` are consumed by
221
- // the first pattern and we never fall through here for a quoted
222
- // secret.
223
- pattern: /(?<![A-Za-z0-9_])(?:api[_-]?key|apikey|secret|password|token)"?\s*[:=]\s*[^\s,;}\]"]+/gi
224
- }
225
- ];
226
- function sanitizeErrorResponseBody(body) {
227
- let out = body;
228
- for (const { pattern } of REDACTION_PATTERNS) {
229
- out = out.replace(pattern, REDACTED);
160
+
161
+ // src/lifecycle.ts
162
+ var CoreState = {
163
+ IDLE: "IDLE",
164
+ REGISTERING: "REGISTERING",
165
+ KEY_PENDING: "KEY_PENDING",
166
+ KEY_RESOLVED: "KEY_RESOLVED",
167
+ ACTIVE: "ACTIVE",
168
+ ACTIVE_DEGRADED: "ACTIVE_DEGRADED",
169
+ SHUTTING_DOWN: "SHUTTING_DOWN",
170
+ SHUTDOWN: "SHUTDOWN",
171
+ PRODUCTION_DISABLED: "PRODUCTION_DISABLED",
172
+ REGISTRATION_FAILED: "REGISTRATION_FAILED"
173
+ };
174
+ var AuthState = {
175
+ ANONYMOUS: "ANONYMOUS",
176
+ AUTHENTICATED: "AUTHENTICATED",
177
+ CLAIMING: "CLAIMING",
178
+ CLAIMED: "CLAIMED"
179
+ };
180
+ var OtelState = {
181
+ UNCONFIGURED: "UNCONFIGURED",
182
+ CONFIGURING: "CONFIGURING",
183
+ OWNS_PROVIDER: "OWNS_PROVIDER",
184
+ AUTO_ATTACHED: "AUTO_ATTACHED",
185
+ PROCESSOR_PRESENT: "PROCESSOR_PRESENT",
186
+ COEXISTENCE_FAILED: "COEXISTENCE_FAILED"
187
+ };
188
+ var VALID_CORE_TRANSITIONS = {
189
+ [CoreState.IDLE]: [CoreState.REGISTERING, CoreState.REGISTRATION_FAILED, CoreState.SHUTTING_DOWN],
190
+ [CoreState.REGISTERING]: [
191
+ CoreState.KEY_PENDING,
192
+ CoreState.PRODUCTION_DISABLED,
193
+ CoreState.REGISTRATION_FAILED,
194
+ CoreState.SHUTTING_DOWN
195
+ ],
196
+ [CoreState.KEY_PENDING]: [
197
+ CoreState.KEY_RESOLVED,
198
+ CoreState.REGISTRATION_FAILED,
199
+ CoreState.SHUTTING_DOWN
200
+ ],
201
+ [CoreState.KEY_RESOLVED]: [
202
+ CoreState.ACTIVE,
203
+ CoreState.ACTIVE_DEGRADED,
204
+ CoreState.SHUTTING_DOWN
205
+ ],
206
+ [CoreState.ACTIVE]: [
207
+ CoreState.ACTIVE_DEGRADED,
208
+ CoreState.SHUTTING_DOWN
209
+ ],
210
+ [CoreState.ACTIVE_DEGRADED]: [
211
+ CoreState.ACTIVE,
212
+ CoreState.SHUTTING_DOWN
213
+ ],
214
+ [CoreState.SHUTTING_DOWN]: [CoreState.SHUTDOWN],
215
+ [CoreState.SHUTDOWN]: [],
216
+ [CoreState.PRODUCTION_DISABLED]: [],
217
+ [CoreState.REGISTRATION_FAILED]: []
218
+ };
219
+ var VALID_AUTH_TRANSITIONS = {
220
+ [AuthState.ANONYMOUS]: [AuthState.CLAIMING],
221
+ [AuthState.AUTHENTICATED]: [AuthState.CLAIMING],
222
+ [AuthState.CLAIMING]: [AuthState.CLAIMED],
223
+ [AuthState.CLAIMED]: [AuthState.CLAIMING]
224
+ };
225
+ var VALID_OTEL_TRANSITIONS = {
226
+ [OtelState.UNCONFIGURED]: [OtelState.CONFIGURING],
227
+ [OtelState.CONFIGURING]: [
228
+ OtelState.OWNS_PROVIDER,
229
+ OtelState.AUTO_ATTACHED,
230
+ OtelState.PROCESSOR_PRESENT,
231
+ OtelState.COEXISTENCE_FAILED
232
+ ],
233
+ [OtelState.OWNS_PROVIDER]: [],
234
+ [OtelState.AUTO_ATTACHED]: [],
235
+ [OtelState.PROCESSOR_PRESENT]: [],
236
+ [OtelState.COEXISTENCE_FAILED]: []
237
+ };
238
+ var _coreState = CoreState.IDLE;
239
+ var _authState = AuthState.ANONYMOUS;
240
+ var _otelState = OtelState.UNCONFIGURED;
241
+ var _emitter = new EventEmitter();
242
+ var _logger = null;
243
+ var _initialized = false;
244
+ var _initWarned = false;
245
+ var _coreReadyEmitted = false;
246
+ var _authInitialized = false;
247
+ var _emitting = false;
248
+ function initLifecycle(options) {
249
+ if (_initialized) {
250
+ options.logger("warn", "[glasstrace] initLifecycle() called twice \u2014 ignored.");
251
+ return;
230
252
  }
231
- return out;
253
+ _logger = options.logger;
254
+ _initialized = true;
255
+ _registerLifecycleEmitForBridge((event, payload) => {
256
+ emitLifecycleEvent(
257
+ event,
258
+ payload
259
+ );
260
+ });
232
261
  }
233
- function truncateErrorResponseBody(body) {
234
- const encoder = new TextEncoder();
235
- const encoded = encoder.encode(body);
236
- if (encoded.byteLength <= ERROR_RESPONSE_BODY_MAX_BYTES) {
237
- return body;
262
+ function warnIfNotInitialized() {
263
+ if (!_initialized && !_initWarned) {
264
+ _initWarned = true;
265
+ console.warn(
266
+ "[glasstrace] Lifecycle state changed before initLifecycle() was called. Logger not available \u2014 errors will be silent."
267
+ );
238
268
  }
239
- let cut = ERROR_RESPONSE_BODY_MAX_BYTES;
240
- let scan = cut - 1;
241
- while (scan >= 0 && (encoded[scan] & 192) === 128) {
242
- scan -= 1;
269
+ }
270
+ function setCoreState(to) {
271
+ warnIfNotInitialized();
272
+ const from = _coreState;
273
+ if (from === to) return;
274
+ const valid = VALID_CORE_TRANSITIONS[from];
275
+ if (!valid.includes(to)) {
276
+ _logger?.(
277
+ "warn",
278
+ `[glasstrace] Invalid core state transition: ${from} \u2192 ${to}. Ignored.`
279
+ );
280
+ return;
243
281
  }
244
- if (scan >= 0) {
245
- const leading = encoded[scan];
246
- let expected = 1;
247
- if ((leading & 128) === 0) {
248
- expected = 1;
249
- } else if ((leading & 224) === 192) {
250
- expected = 2;
251
- } else if ((leading & 240) === 224) {
252
- expected = 3;
253
- } else if ((leading & 248) === 240) {
254
- expected = 4;
282
+ _coreState = to;
283
+ if (_emitting) return;
284
+ _emitting = true;
285
+ try {
286
+ emitSafe("core:state_changed", { from, to });
287
+ const current = _coreState;
288
+ if (!_coreReadyEmitted && (current === CoreState.ACTIVE || current === CoreState.ACTIVE_DEGRADED)) {
289
+ _coreReadyEmitted = true;
290
+ emitSafe("core:ready", {});
255
291
  }
256
- if (scan + expected > cut) {
257
- cut = scan;
292
+ if (current === CoreState.SHUTTING_DOWN) {
293
+ emitSafe("core:shutdown_started", {});
258
294
  }
295
+ if (current === CoreState.SHUTDOWN) {
296
+ emitSafe("core:shutdown_completed", {});
297
+ }
298
+ } finally {
299
+ _emitting = false;
300
+ }
301
+ if (to === CoreState.ACTIVE && _degradationSources.size > 0) {
302
+ recomputeCoreFromDegradationSources();
259
303
  }
260
- const decoder = new TextDecoder("utf-8", { fatal: false });
261
- const sliced = encoded.subarray(0, cut);
262
- const decoded = decoder.decode(sliced);
263
- return decoded + ERROR_RESPONSE_BODY_TRUNCATION_MARKER;
264
- }
265
- function prepareErrorResponseBody(body) {
266
- if (body.length === 0) return null;
267
- if (body.trim().length === 0) return null;
268
- const sanitized = sanitizeErrorResponseBody(body);
269
- return truncateErrorResponseBody(sanitized);
270
304
  }
271
-
272
- // src/error-stack.ts
273
- var ERROR_STACK_MAX_BYTES = 8192;
274
- var ERROR_STACK_TRUNCATION_MARKER = "...[stack truncated]";
275
- var PATH_REDACTED = "<path>";
276
- var PATH_KEEP_MARKERS = [
277
- "node_modules",
278
- ".next",
279
- ".glasstrace",
280
- "src",
281
- "dist",
282
- "build",
283
- "lib",
284
- "app",
285
- "pages"
286
- ];
287
- var PATH_TOKEN_RE = /(?<=^|[\s(])(\/[^\s()<>]+|[A-Za-z]:\\[^\s()<>]+|file:\/\/\/[^\s()<>]+|webpack-internal:\/\/[^\s()<>]+|node:[^\s()<>]+)/g;
288
- var URL_QUERY_FRAGMENT_RE = /(\bhttps?:\/\/[^\s?#()<>]+)([?#][^\s()<>]*)/g;
289
- function normalizePathToken(token) {
290
- let work = token;
291
- if (work.startsWith("file:///")) {
292
- work = work.slice("file://".length);
293
- }
294
- if (work.startsWith("webpack-internal:") || work.startsWith("node:")) {
295
- return { token, changed: false };
296
- }
297
- const isPosixAbs = work.startsWith("/");
298
- const isWinAbs = /^[A-Za-z]:\\/.test(work);
299
- if (!isPosixAbs && !isWinAbs) {
300
- return { token, changed: false };
305
+ function initAuthState(state) {
306
+ if (_authInitialized) {
307
+ _logger?.(
308
+ "warn",
309
+ "[glasstrace] initAuthState() called after auth state already initialized. Ignored."
310
+ );
311
+ return;
301
312
  }
302
- const sep = isWinAbs ? "\\" : "/";
303
- let bestIdx = -1;
304
- for (const marker of PATH_KEEP_MARKERS) {
305
- const needle = `${sep}${marker}${sep}`;
306
- const idx = work.lastIndexOf(needle);
307
- if (idx >= 0) {
308
- bestIdx = idx;
309
- break;
310
- }
313
+ _authInitialized = true;
314
+ _authState = state;
315
+ }
316
+ function setAuthState(to) {
317
+ warnIfNotInitialized();
318
+ const from = _authState;
319
+ if (from === to) return;
320
+ const valid = VALID_AUTH_TRANSITIONS[from];
321
+ if (!valid.includes(to)) {
322
+ _logger?.(
323
+ "warn",
324
+ `[glasstrace] Invalid auth state transition: ${from} \u2192 ${to}. Ignored.`
325
+ );
326
+ return;
311
327
  }
312
- if (bestIdx >= 0) {
313
- const kept = work.slice(bestIdx + sep.length);
314
- const rebuilt2 = `${PATH_REDACTED}/${kept.replace(/\\/g, "/")}`;
315
- return { token: rebuilt2, changed: true };
328
+ _authState = to;
329
+ }
330
+ function setOtelState(to) {
331
+ warnIfNotInitialized();
332
+ const from = _otelState;
333
+ if (from === to) return;
334
+ const valid = VALID_OTEL_TRANSITIONS[from];
335
+ if (!valid.includes(to)) {
336
+ _logger?.(
337
+ "warn",
338
+ `[glasstrace] Invalid OTel state transition: ${from} \u2192 ${to}. Ignored.`
339
+ );
340
+ return;
316
341
  }
317
- const colonLineRe = /:\d+(?::\d+)?$/;
318
- const lineMatch = colonLineRe.exec(work);
319
- const pathBody = lineMatch ? work.slice(0, lineMatch.index) : work;
320
- const lineSuffix = lineMatch ? work.slice(lineMatch.index) : "";
321
- const lastSep = Math.max(pathBody.lastIndexOf("/"), pathBody.lastIndexOf("\\"));
322
- const basename = lastSep >= 0 ? pathBody.slice(lastSep + 1) : pathBody;
323
- const rebuilt = `${PATH_REDACTED}/${basename}${lineSuffix}`;
324
- return { token: rebuilt, changed: true };
342
+ _otelState = to;
325
343
  }
326
- function sanitizeStack(stack) {
327
- let changed = false;
328
- const pathNormalized = stack.replace(PATH_TOKEN_RE, (token) => {
329
- const out = normalizePathToken(token);
330
- if (out.changed) changed = true;
331
- return out.token;
332
- });
333
- const urlStripped = pathNormalized.replace(URL_QUERY_FRAGMENT_RE, (match, prefix) => {
334
- if (match !== prefix) changed = true;
335
- return prefix;
336
- });
337
- const credentialRedacted = sanitizeErrorResponseBody(urlStripped);
338
- if (credentialRedacted !== urlStripped) changed = true;
339
- return { stack: credentialRedacted, redacted: changed };
344
+ var _degradationSources = /* @__PURE__ */ new Set();
345
+ function pushDegradationSource(key) {
346
+ _degradationSources.add(key);
347
+ recomputeCoreFromDegradationSources();
340
348
  }
341
- function truncateStack(stack) {
342
- const encoder = new TextEncoder();
343
- const encoded = encoder.encode(stack);
344
- if (encoded.byteLength <= ERROR_STACK_MAX_BYTES) {
345
- return { stack, truncated: false };
346
- }
347
- let cut = ERROR_STACK_MAX_BYTES;
348
- let scan = cut - 1;
349
- while (scan >= 0 && (encoded[scan] & 192) === 128) {
350
- scan -= 1;
349
+ function clearDegradationSource(key) {
350
+ _degradationSources.delete(key);
351
+ recomputeCoreFromDegradationSources();
352
+ }
353
+ function recomputeCoreFromDegradationSources() {
354
+ const hasDegradation = _degradationSources.size > 0;
355
+ if (hasDegradation && _coreState === CoreState.ACTIVE) {
356
+ setCoreState(CoreState.ACTIVE_DEGRADED);
357
+ return;
351
358
  }
352
- if (scan >= 0) {
353
- const leading = encoded[scan];
354
- let expected = 1;
355
- if ((leading & 128) === 0) {
356
- expected = 1;
357
- } else if ((leading & 224) === 192) {
358
- expected = 2;
359
- } else if ((leading & 240) === 224) {
360
- expected = 3;
361
- } else if ((leading & 248) === 240) {
362
- expected = 4;
363
- }
364
- if (scan + expected > cut) {
365
- cut = scan;
366
- }
359
+ if (!hasDegradation && _coreState === CoreState.ACTIVE_DEGRADED) {
360
+ setCoreState(CoreState.ACTIVE);
367
361
  }
368
- const decoder = new TextDecoder("utf-8", { fatal: false });
369
- const sliced = encoded.subarray(0, cut);
370
- const decoded = decoder.decode(sliced);
371
- return { stack: decoded + ERROR_STACK_TRUNCATION_MARKER, truncated: true };
372
362
  }
373
- function prepareStack(stack) {
374
- if (stack.length === 0) return null;
375
- if (stack.trim().length === 0) return null;
376
- const sanitized = sanitizeStack(stack);
377
- const truncated = truncateStack(sanitized.stack);
363
+ function getCoreState() {
364
+ return _coreState;
365
+ }
366
+ function getSdkState() {
378
367
  return {
379
- stack: truncated.stack,
380
- truncated: truncated.truncated,
381
- redacted: sanitized.redacted
368
+ core: _coreState,
369
+ auth: _authState,
370
+ otel: _otelState
382
371
  };
383
372
  }
384
-
385
- // src/build-info.ts
386
- var UNSET = "";
387
- var SHA_SHAPE = /^[0-9a-f]{7,64}$/i;
388
- function redactBuildHash(value) {
389
- const sanitize = (s) => s.replace(/[\x00-\x1F\x7F]/g, "?");
390
- if (value.length <= 12) return sanitize(value.slice(0, 4)) + "...";
391
- return sanitize(value.slice(0, 8)) + "..." + sanitize(value.slice(-4));
373
+ function onLifecycleEvent(event, listener) {
374
+ _emitter.on(event, listener);
392
375
  }
393
- function readBuildHashFromEnv() {
394
- const raw = process.env.GLASSTRACE_BUILD_HASH;
395
- if (typeof raw !== "string") return UNSET;
396
- const trimmed = raw.trim();
397
- if (trimmed.length === 0) return UNSET;
398
- if (!SHA_SHAPE.test(trimmed)) {
399
- sdkLog(
400
- "warn",
401
- `[glasstrace] warning: GLASSTRACE_BUILD_HASH=${redactBuildHash(trimmed)} does not match expected SHA shape (7-64 hex characters); source-map enrichment may not work as expected.`
402
- );
403
- }
404
- return trimmed;
376
+ function emitLifecycleEvent(event, payload) {
377
+ emitSafe(event, payload);
405
378
  }
406
- var cachedBuildHash = null;
407
- function getBuildHash() {
408
- if (cachedBuildHash === null) {
409
- cachedBuildHash = readBuildHashFromEnv();
379
+ function offLifecycleEvent(event, listener) {
380
+ _emitter.off(event, listener);
381
+ }
382
+ function emitSafe(event, payload) {
383
+ const listeners = _emitter.listeners(event);
384
+ for (const listener of listeners) {
385
+ try {
386
+ const result = listener(payload);
387
+ if (result && typeof result.catch === "function") {
388
+ result.catch((err) => {
389
+ _logger?.(
390
+ "error",
391
+ `[glasstrace] Async error in lifecycle event listener for "${event}": ${err instanceof Error ? err.message : String(err)}`
392
+ );
393
+ });
394
+ }
395
+ } catch (err) {
396
+ _logger?.(
397
+ "error",
398
+ `[glasstrace] Error in lifecycle event listener for "${event}": ${err instanceof Error ? err.message : String(err)}`
399
+ );
400
+ }
410
401
  }
411
- return cachedBuildHash === UNSET ? void 0 : cachedBuildHash;
412
402
  }
413
-
414
- // src/enriching-exporter.ts
415
- var ATTR = GLASSTRACE_ATTRIBUTE_NAMES;
416
- var API_KEY_PENDING = "pending";
417
- var MAX_PENDING_SPANS = 1024;
418
- var GlasstraceExporter = class {
419
- getApiKey;
420
- sessionManager;
421
- getConfig;
422
- environment;
423
- endpointUrl;
424
- createDelegateFn;
425
- verbose;
426
- delegate = null;
427
- delegateKey = null;
428
- pendingBatches = [];
429
- pendingSpanCount = 0;
430
- overflowLogged = false;
431
- constructor(options) {
432
- this.getApiKey = options.getApiKey;
433
- this.sessionManager = options.sessionManager;
434
- this.getConfig = options.getConfig;
435
- this.environment = options.environment;
436
- this.endpointUrl = options.endpointUrl;
437
- this.createDelegateFn = options.createDelegate;
438
- this.verbose = options.verbose ?? false;
439
- this[/* @__PURE__ */ Symbol.for("glasstrace.exporter")] = true;
403
+ function isReady() {
404
+ return _coreState === CoreState.ACTIVE || _coreState === CoreState.ACTIVE_DEGRADED;
405
+ }
406
+ function waitForReady(timeoutMs = 3e4) {
407
+ if (isReady()) {
408
+ return Promise.resolve();
440
409
  }
441
- export(spans, resultCallback) {
442
- const currentKey = this.getApiKey();
443
- if (currentKey === API_KEY_PENDING) {
444
- this.bufferSpans(spans, resultCallback);
445
- return;
446
- }
447
- const enrichedSpans = spans.map((span) => this.enrichSpan(span));
448
- const exporter = this.ensureDelegate();
449
- if (exporter) {
450
- exporter.export(enrichedSpans, (result) => {
451
- if (result.code !== 0) {
452
- sdkLog("warn", `[glasstrace] Span export failed: ${result.error?.message ?? "unknown error"}`);
453
- }
454
- resultCallback(result);
455
- });
456
- recordSpansExported(enrichedSpans.length);
457
- } else {
458
- recordSpansDropped(enrichedSpans.length);
459
- resultCallback({ code: 0 });
460
- }
410
+ if (_coreState === CoreState.PRODUCTION_DISABLED || _coreState === CoreState.REGISTRATION_FAILED || _coreState === CoreState.SHUTTING_DOWN || _coreState === CoreState.SHUTDOWN) {
411
+ return Promise.reject(new Error(`SDK is in terminal state: ${_coreState}`));
461
412
  }
462
- /**
463
- * Called when the API key transitions from "pending" to a resolved value.
464
- * Creates the delegate exporter and flushes all buffered spans.
465
- */
466
- notifyKeyResolved() {
467
- this.flushPending();
468
- }
469
- async shutdown() {
470
- const currentKey = this.getApiKey();
471
- if (currentKey !== API_KEY_PENDING && this.pendingBatches.length > 0) {
472
- this.flushPending();
473
- } else if (this.pendingBatches.length > 0) {
474
- console.warn(
475
- `[glasstrace] Shutdown with ${this.pendingSpanCount} buffered spans \u2014 API key never resolved, spans lost.`
476
- );
477
- recordSpansDropped(this.pendingSpanCount);
478
- for (const batch of this.pendingBatches) {
479
- batch.resultCallback({ code: 0 });
413
+ return new Promise((resolve2, reject) => {
414
+ let settled = false;
415
+ const listener = ({ to }) => {
416
+ if (settled) return;
417
+ if (to === CoreState.ACTIVE || to === CoreState.ACTIVE_DEGRADED) {
418
+ settled = true;
419
+ offLifecycleEvent("core:state_changed", listener);
420
+ resolve2();
421
+ } else if (to === CoreState.PRODUCTION_DISABLED || to === CoreState.REGISTRATION_FAILED || to === CoreState.SHUTTING_DOWN || to === CoreState.SHUTDOWN) {
422
+ settled = true;
423
+ offLifecycleEvent("core:state_changed", listener);
424
+ reject(new Error(`SDK reached terminal state: ${to}`));
425
+ }
426
+ };
427
+ onLifecycleEvent("core:state_changed", listener);
428
+ if (timeoutMs > 0) {
429
+ const timer = setTimeout(() => {
430
+ if (settled) return;
431
+ settled = true;
432
+ offLifecycleEvent("core:state_changed", listener);
433
+ reject(new Error(`waitForReady timed out after ${timeoutMs}ms (state: ${_coreState})`));
434
+ }, timeoutMs);
435
+ if (typeof timer === "object" && "unref" in timer) {
436
+ timer.unref();
480
437
  }
481
- this.pendingBatches = [];
482
- this.pendingSpanCount = 0;
483
- }
484
- if (this.delegate) {
485
- return this.delegate.shutdown();
486
438
  }
439
+ });
440
+ }
441
+ function getStatus() {
442
+ let mode;
443
+ if (_coreState === CoreState.PRODUCTION_DISABLED) {
444
+ mode = "disabled";
445
+ } else if (_authState === AuthState.CLAIMING || _authState === AuthState.CLAIMED) {
446
+ mode = "claiming";
447
+ } else if (_authState === AuthState.AUTHENTICATED) {
448
+ mode = "authenticated";
449
+ } else {
450
+ mode = "anonymous";
487
451
  }
488
- /**
489
- * Flushes any pending buffered spans (if the API key has resolved) and
490
- * delegates to the underlying exporter's forceFlush to drain its queue.
491
- */
492
- forceFlush() {
493
- if (this.getApiKey() !== API_KEY_PENDING && this.pendingBatches.length > 0) {
494
- this.flushPending();
495
- }
496
- if (this.delegate?.forceFlush) {
497
- return this.delegate.forceFlush();
498
- }
499
- return Promise.resolve();
452
+ let tracing;
453
+ if (_otelState === OtelState.COEXISTENCE_FAILED || _otelState === OtelState.UNCONFIGURED || _otelState === OtelState.CONFIGURING) {
454
+ tracing = "not-configured";
455
+ } else if (_coreState === CoreState.ACTIVE_DEGRADED) {
456
+ tracing = "degraded";
457
+ } else if (_otelState === OtelState.AUTO_ATTACHED || _otelState === OtelState.PROCESSOR_PRESENT) {
458
+ tracing = "coexistence";
459
+ } else {
460
+ tracing = "active";
500
461
  }
501
- /**
502
- * Enriches a ReadableSpan with all glasstrace.* attributes.
503
- * Returns a new ReadableSpan wrapper; the original span is not mutated.
504
- *
505
- * Only {@link SessionManager.getSessionId} is individually guarded because
506
- * it calls into crypto and schema validation — a session ID failure should
507
- * not prevent the rest of enrichment. The other helper calls
508
- * ({@link deriveErrorCategory}, {@link deriveOrmProvider},
509
- * {@link classifyFetchTarget}) are pure functions on typed string inputs
510
- * and rely on the outer catch for any unexpected failure.
511
- *
512
- * On total failure, returns the original span unchanged.
513
- */
514
- enrichSpan(span) {
462
+ return {
463
+ ready: isReady(),
464
+ mode,
465
+ tracing
466
+ };
467
+ }
468
+ var _shutdownHooks = [];
469
+ var _signalHandlersRegistered = false;
470
+ var _signalHandler = null;
471
+ var _beforeExitRegistered = false;
472
+ var _beforeExitHandler = null;
473
+ var _shutdownExecuted = false;
474
+ function registerShutdownHook(hook) {
475
+ _shutdownHooks.push(hook);
476
+ _shutdownHooks.sort((a, b) => a.priority - b.priority);
477
+ }
478
+ async function executeShutdown(timeoutMs = 5e3) {
479
+ if (_shutdownExecuted) return;
480
+ _shutdownExecuted = true;
481
+ setCoreState(CoreState.SHUTTING_DOWN);
482
+ for (const hook of _shutdownHooks) {
515
483
  try {
516
- const attrs = span.attributes ?? {};
517
- const name = span.name ?? "";
518
- const extra = {};
519
- extra[ATTR.TRACE_TYPE] = "server";
520
- try {
521
- const sessionId = this.sessionManager.getSessionId(this.getApiKey());
522
- extra[ATTR.SESSION_ID] = sessionId;
523
- } catch {
524
- }
525
- const env = this.environment ?? process.env.GLASSTRACE_ENV;
526
- if (env) {
527
- extra[ATTR.ENVIRONMENT] = env;
528
- }
529
- const buildHash = getBuildHash();
530
- if (buildHash) {
531
- extra[ATTR.BUILD_HASH] = buildHash;
532
- }
533
- const existingCid = attrs["glasstrace.correlation.id"];
534
- if (typeof existingCid === "string") {
535
- extra[ATTR.CORRELATION_ID] = existingCid;
536
- }
537
- const rawRoute = attrs["http.route"];
538
- const route = typeof rawRoute === "string" ? rawRoute : name;
539
- if (route) {
540
- extra[ATTR.ROUTE] = route;
541
- }
542
- const rawUrlAttr = attrs["http.url"] ?? attrs["url.full"] ?? attrs["http.target"];
543
- const rawHttpUrl = typeof rawUrlAttr === "string" ? rawUrlAttr : void 0;
544
- if (rawHttpUrl) {
545
- const trpcMatch = rawHttpUrl.match(/\/api\/trpc\/([^/?#]+)/);
546
- if (trpcMatch) {
547
- let procedure;
548
- try {
549
- procedure = decodeURIComponent(trpcMatch[1]);
550
- } catch {
551
- procedure = trpcMatch[1];
552
- }
553
- if (procedure) {
554
- extra[ATTR.TRPC_PROCEDURE] = procedure;
555
- }
556
- }
557
- }
558
- const method = attrs["http.method"] ?? attrs["http.request.method"];
559
- if (method) {
560
- extra[ATTR.HTTP_METHOD] = method;
561
- }
562
- const actionRoute = extractLeadingPath(route);
563
- if (method === "POST" && actionRoute) {
564
- const isApiRoute = actionRoute === "/api" || actionRoute.startsWith("/api/");
565
- const isInternalRoute = actionRoute.startsWith("/_next/");
566
- if (!isApiRoute && !isInternalRoute) {
567
- extra[ATTR.NEXT_ACTION_DETECTED] = true;
568
- if (typeof extra[ATTR.CORRELATION_ID] !== "string") {
569
- maybeShowServerActionNudge();
570
- }
571
- }
572
- }
573
- const statusCode = coerceHttpStatus(attrs["http.status_code"]) ?? coerceHttpStatus(attrs["http.response.status_code"]);
574
- if (statusCode !== void 0) {
575
- extra[ATTR.HTTP_STATUS_CODE] = statusCode;
576
- }
577
- const isErrorByStatus = span.status?.code === SpanStatusCode.ERROR;
578
- const isErrorByEvent = hasExceptionEvent(span);
579
- const isErrorByAttrs = typeof attrs["exception.type"] === "string" || typeof attrs["exception.message"] === "string";
580
- const statusNotExplicitlyOK = span.status?.code !== SpanStatusCode.OK;
581
- if (this.verbose && method) {
582
- sdkLog(
583
- "info",
584
- `[glasstrace] enrichSpan "${name}": status.code=${span.status?.code}, http.status_code=${statusCode}, isErrorByStatus=${isErrorByStatus}, isErrorByEvent=${isErrorByEvent}, isErrorByAttrs=${isErrorByAttrs}`
585
- );
586
- }
587
- if (method && statusNotExplicitlyOK && (isErrorByStatus || isErrorByEvent || isErrorByAttrs)) {
588
- if (statusCode === void 0 || statusCode === 0 || statusCode === 200) {
589
- const httpErrorType = attrs["error.type"];
590
- if (typeof httpErrorType === "string") {
591
- const parsed = parseInt(httpErrorType, 10);
592
- if (!isNaN(parsed) && parsed >= 400 && parsed <= 599) {
593
- extra[ATTR.HTTP_STATUS_CODE] = parsed;
594
- } else {
595
- extra[ATTR.HTTP_STATUS_CODE] = 500;
596
- }
597
- } else {
598
- extra[ATTR.HTTP_STATUS_CODE] = 500;
599
- }
600
- if (this.verbose) {
601
- sdkLog(
602
- "info",
603
- `[glasstrace] enrichSpan "${name}": inferred status_code=${extra[ATTR.HTTP_STATUS_CODE]} (was ${statusCode}), error.type=${attrs["error.type"]}`
604
- );
484
+ const hookPromise = hook.fn();
485
+ hookPromise.catch(() => {
486
+ });
487
+ await Promise.race([
488
+ hookPromise,
489
+ new Promise((_, reject) => {
490
+ const timer = setTimeout(() => reject(new Error(`Shutdown hook "${hook.name}" timed out`)), timeoutMs);
491
+ if (typeof timer === "object" && "unref" in timer) {
492
+ timer.unref();
605
493
  }
606
- }
607
- }
608
- if (span.startTime && span.endTime) {
609
- const [startSec, startNano] = span.startTime;
610
- const [endSec, endNano] = span.endTime;
611
- const durationMs = (endSec - startSec) * 1e3 + (endNano - startNano) / 1e6;
612
- if (durationMs >= 0) {
613
- extra[ATTR.HTTP_DURATION_MS] = durationMs;
614
- }
494
+ })
495
+ ]);
496
+ } catch (err) {
497
+ _logger?.(
498
+ "warn",
499
+ `[glasstrace] Shutdown hook "${hook.name}" failed: ${err instanceof Error ? err.message : String(err)}`
500
+ );
501
+ }
502
+ }
503
+ setCoreState(CoreState.SHUTDOWN);
504
+ }
505
+ function registerSignalHandlers() {
506
+ if (_signalHandlersRegistered) return;
507
+ if (typeof process === "undefined" || typeof process.once !== "function") return;
508
+ _signalHandlersRegistered = true;
509
+ const otherSigtermListeners = process.listenerCount("SIGTERM");
510
+ const otherSigintListeners = process.listenerCount("SIGINT");
511
+ const handler = (signal) => {
512
+ void executeShutdown().finally(() => {
513
+ if (_signalHandler) {
514
+ process.removeListener("SIGTERM", _signalHandler);
515
+ process.removeListener("SIGINT", _signalHandler);
615
516
  }
616
- const eventDetails = statusNotExplicitlyOK ? getExceptionEventDetails(span) : { type: void 0, message: void 0, stacktrace: void 0 };
617
- let errorSource;
618
- const attrMessage = attrs["exception.message"];
619
- if (eventDetails.message) {
620
- extra[ATTR.ERROR_MESSAGE] = eventDetails.message;
621
- errorSource = "otel_exception";
622
- } else if (typeof attrMessage === "string") {
623
- extra[ATTR.ERROR_MESSAGE] = attrMessage;
624
- errorSource = "otel_event";
517
+ const otherListeners = signal === "SIGTERM" ? otherSigtermListeners : otherSigintListeners;
518
+ const otherProviderOwnsSignal = getCoexistenceState() === "coexisting" && otherListeners > 0;
519
+ if (!otherProviderOwnsSignal) {
520
+ process.kill(process.pid, signal);
625
521
  }
626
- const attrType = attrs["exception.type"];
627
- if (eventDetails.type) {
628
- extra[ATTR.ERROR_CODE] = eventDetails.type;
629
- extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(eventDetails.type);
630
- errorSource = errorSource ?? "otel_exception";
631
- } else if (typeof attrType === "string") {
632
- extra[ATTR.ERROR_CODE] = attrType;
633
- extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(attrType);
634
- errorSource = errorSource ?? "otel_event";
635
- }
636
- if (statusNotExplicitlyOK) {
637
- const rawStack = eventDetails.stacktrace ?? (typeof attrs["exception.stacktrace"] === "string" ? attrs["exception.stacktrace"] : void 0);
638
- if (rawStack) {
639
- const prepared = prepareStack(rawStack);
640
- if (prepared !== null) {
641
- extra[ATTR.ERROR_STACK] = prepared.stack;
642
- extra[ATTR.ERROR_STACK_TRUNCATED] = prepared.truncated;
643
- extra[ATTR.ERROR_STACK_REDACTED] = prepared.redacted;
644
- errorSource = errorSource ?? (eventDetails.stacktrace ? "otel_exception" : "otel_event");
645
- }
646
- }
647
- }
648
- const routeIsFallback = route === "/_error" || route === "/_not-found" || route === "/_404" || route === "/_500";
649
- if (routeIsFallback && rawHttpUrl) {
650
- const originalPath = extractPathOnly(rawHttpUrl);
651
- const normOriginal = stripTrailingSlash(originalPath);
652
- const normRoute = stripTrailingSlash(route);
653
- if (normOriginal && normOriginal !== normRoute) {
654
- extra[ATTR.ERROR_ORIGINAL_PATH] = normOriginal;
655
- extra[ATTR.ERROR_FALLBACK_ROUTE] = route;
656
- extra[ATTR.ERROR_FRAMEWORK_KIND] = "fallback";
657
- errorSource = errorSource ?? "framework_fallback";
658
- }
659
- }
660
- if (errorSource !== void 0) {
661
- extra[ATTR.ERROR_SOURCE] = errorSource;
662
- }
663
- if (this.verbose && (extra[ATTR.ERROR_MESSAGE] || extra[ATTR.ERROR_CODE])) {
664
- const msgSource = eventDetails.message ? "event" : typeof attrMessage === "string" ? "attrs" : "none";
665
- const typeSource = eventDetails.type ? "event" : typeof attrType === "string" ? "attrs" : "none";
666
- sdkLog(
667
- "info",
668
- `[glasstrace] enrichSpan "${name}": error.message source=${msgSource}, error.code source=${typeSource}`
669
- );
670
- }
671
- const errorField = attrs["error.field"];
672
- if (typeof errorField === "string") {
673
- extra[ATTR.ERROR_FIELD] = errorField;
674
- }
675
- if (this.getConfig().errorResponseBodies) {
676
- const responseBody = attrs["glasstrace.internal.response_body"];
677
- if (typeof responseBody === "string") {
678
- const enrichedStatus = extra[ATTR.HTTP_STATUS_CODE];
679
- const effectiveStatus = typeof enrichedStatus === "number" ? enrichedStatus : statusCode;
680
- if (isHttpErrorStatus(effectiveStatus)) {
681
- const prepared = prepareErrorResponseBody(responseBody);
682
- if (prepared !== null) {
683
- extra[ATTR.ERROR_RESPONSE_BODY] = prepared;
684
- }
685
- }
686
- }
687
- }
688
- const spanAny = span;
689
- const instrumentationName = spanAny.instrumentationScope?.name ?? spanAny.instrumentationLibrary?.name ?? "";
690
- const ormProvider = deriveOrmProvider(instrumentationName);
691
- if (ormProvider) {
692
- extra[ATTR.ORM_PROVIDER] = ormProvider;
693
- const table = attrs["db.sql.table"];
694
- const prismaModel = attrs["db.prisma.model"];
695
- const model = typeof table === "string" ? table : typeof prismaModel === "string" ? prismaModel : void 0;
696
- if (model) {
697
- extra[ATTR.ORM_MODEL] = model;
698
- }
699
- const operation = attrs["db.operation"];
700
- if (typeof operation === "string") {
701
- extra[ATTR.ORM_OPERATION] = operation;
702
- }
703
- }
704
- const httpUrl = attrs["http.url"];
705
- const fullUrl = attrs["url.full"];
706
- const url = typeof httpUrl === "string" ? httpUrl : typeof fullUrl === "string" ? fullUrl : void 0;
707
- if (url && span.kind === SpanKind.CLIENT) {
708
- extra[ATTR.FETCH_TARGET] = classifyFetchTarget(url);
709
- }
710
- return createEnrichedSpan(span, extra);
711
- } catch {
712
- return span;
713
- }
714
- }
715
- /**
716
- * Lazily creates the delegate OTLP exporter once the API key is resolved.
717
- * Recreates the delegate if the key has changed (e.g., after key rotation)
718
- * so the Authorization header stays current.
719
- */
720
- ensureDelegate() {
721
- if (!this.createDelegateFn) return null;
722
- const currentKey = this.getApiKey();
723
- if (currentKey === API_KEY_PENDING) return null;
724
- if (this.delegate && this.delegateKey === currentKey) {
725
- return this.delegate;
726
- }
727
- if (this.delegate) {
728
- void this.delegate.shutdown?.().catch(() => {
729
- });
730
- }
731
- this.delegate = this.createDelegateFn(this.endpointUrl, {
732
- Authorization: `Bearer ${currentKey}`
733
522
  });
734
- this.delegateKey = currentKey;
735
- return this.delegate;
736
- }
737
- /**
738
- * Buffers raw (unenriched) spans while the API key is pending.
739
- * Evicts oldest batches if the buffer exceeds MAX_PENDING_SPANS.
740
- * Re-checks the key after buffering to close the race window where
741
- * the key resolves between the caller's check and this buffer call.
742
- */
743
- bufferSpans(spans, resultCallback) {
744
- this.pendingBatches.push({ spans, resultCallback });
745
- this.pendingSpanCount += spans.length;
746
- while (this.pendingSpanCount > MAX_PENDING_SPANS && this.pendingBatches.length > 1) {
747
- const evicted = this.pendingBatches.shift();
748
- this.pendingSpanCount -= evicted.spans.length;
749
- recordSpansDropped(evicted.spans.length);
750
- evicted.resultCallback({ code: 0 });
751
- if (!this.overflowLogged) {
752
- this.overflowLogged = true;
753
- console.warn(
754
- "[glasstrace] Pending span buffer overflow \u2014 oldest spans evicted. This usually means the API key is taking too long to resolve."
755
- );
756
- }
757
- }
758
- if (this.getApiKey() !== API_KEY_PENDING) {
759
- this.flushPending();
760
- }
761
- }
762
- /**
763
- * Flushes all buffered spans through the delegate exporter.
764
- * Enriches spans at flush time (not buffer time) so that session IDs
765
- * are computed with the resolved API key instead of the "pending" sentinel.
766
- */
767
- flushPending() {
768
- if (this.pendingBatches.length === 0) return;
769
- const exporter = this.ensureDelegate();
770
- if (!exporter) {
771
- let discardedCount = 0;
772
- for (const batch of this.pendingBatches) {
773
- discardedCount += batch.spans.length;
774
- batch.resultCallback({ code: 0 });
775
- }
776
- recordSpansDropped(discardedCount);
777
- this.pendingBatches = [];
778
- this.pendingSpanCount = 0;
779
- return;
780
- }
781
- const batches = this.pendingBatches;
782
- this.pendingBatches = [];
783
- this.pendingSpanCount = 0;
784
- for (const batch of batches) {
785
- const enriched = batch.spans.map((span) => this.enrichSpan(span));
786
- exporter.export(enriched, (result) => {
787
- if (result.code !== 0) {
788
- sdkLog("warn", `[glasstrace] Span export failed: ${result.error?.message ?? "unknown error"}`);
789
- }
790
- batch.resultCallback(result);
791
- });
792
- recordSpansExported(enriched.length);
793
- }
794
- }
795
- };
796
- function createEnrichedSpan(span, extra) {
797
- const enrichedAttributes = { ...span.attributes, ...extra };
798
- return Object.create(span, {
799
- attributes: {
800
- value: enrichedAttributes,
801
- enumerable: true
802
- }
803
- });
804
- }
805
- function hasExceptionEvent(span) {
806
- return span.events?.some((e) => e.name === "exception") ?? false;
523
+ };
524
+ _signalHandler = handler;
525
+ process.once("SIGTERM", handler);
526
+ process.once("SIGINT", handler);
807
527
  }
808
- function getExceptionEventDetails(span) {
809
- const event = span.events?.find((e) => e.name === "exception");
810
- if (!event?.attributes) {
811
- return { type: void 0, message: void 0, stacktrace: void 0 };
812
- }
813
- const type = event.attributes["exception.type"];
814
- const message = event.attributes["exception.message"];
815
- const stacktrace = event.attributes["exception.stacktrace"];
816
- return {
817
- type: typeof type === "string" ? type : void 0,
818
- message: typeof message === "string" ? message : void 0,
819
- stacktrace: typeof stacktrace === "string" ? stacktrace : void 0
528
+ function registerBeforeExitTrigger() {
529
+ if (_beforeExitRegistered) return;
530
+ if (typeof process === "undefined" || typeof process.once !== "function") return;
531
+ _beforeExitRegistered = true;
532
+ const handler = () => {
533
+ void executeShutdown();
820
534
  };
535
+ _beforeExitHandler = handler;
536
+ process.once("beforeExit", handler);
821
537
  }
822
- function extractLeadingPath(raw) {
823
- if (!raw) return void 0;
824
- const trimmed = raw.trim();
825
- if (trimmed.length === 0) return void 0;
826
- if (trimmed.startsWith("/")) {
827
- const firstSpace = trimmed.indexOf(" ");
828
- return firstSpace === -1 ? trimmed : trimmed.slice(0, firstSpace);
829
- }
830
- for (const token of trimmed.split(/\s+/)) {
831
- if (token.startsWith("/")) {
832
- return token;
833
- }
538
+
539
+ // src/error-response-body.ts
540
+ var ERROR_RESPONSE_BODY_MAX_BYTES = 4096;
541
+ var ERROR_RESPONSE_BODY_TRUNCATION_MARKER = "...[truncated]";
542
+ var REDACTED = "[REDACTED]";
543
+ var ERROR_STATUS_MIN = 400;
544
+ var ERROR_STATUS_MAX = 599;
545
+ function coerceHttpStatus(value) {
546
+ let numeric;
547
+ if (typeof value === "number") {
548
+ numeric = value;
549
+ } else if (typeof value === "string") {
550
+ const trimmed = value.trim();
551
+ if (trimmed.length === 0) return void 0;
552
+ numeric = Number(trimmed);
553
+ } else {
554
+ return void 0;
834
555
  }
835
- return void 0;
556
+ return Number.isFinite(numeric) ? numeric : void 0;
836
557
  }
837
- function stripTrailingSlash(path3) {
838
- if (!path3) return path3;
839
- if (path3 === "/") return path3;
840
- return path3.endsWith("/") ? path3.slice(0, -1) : path3;
558
+ function isHttpErrorStatus(status) {
559
+ const numeric = coerceHttpStatus(status);
560
+ if (numeric === void 0) return false;
561
+ return numeric >= ERROR_STATUS_MIN && numeric <= ERROR_STATUS_MAX;
841
562
  }
842
- function extractPathOnly(raw) {
843
- if (!raw) return void 0;
844
- const trimmed = raw.trim();
845
- if (trimmed.length === 0) return void 0;
846
- const isAbsoluteUrl = /^https?:\/\//i.test(trimmed);
847
- const isProtocolRelative = trimmed.startsWith("//");
848
- if (isAbsoluteUrl || isProtocolRelative) {
849
- try {
850
- const parsed = new URL(trimmed, "http://_/");
851
- if (parsed.pathname && parsed.pathname.startsWith("/")) {
852
- return parsed.pathname;
853
- }
854
- } catch {
855
- }
856
- }
857
- if (trimmed.startsWith("/")) {
858
- const queryIdx = trimmed.indexOf("?");
859
- const fragIdx = trimmed.indexOf("#");
860
- let cut = trimmed.length;
861
- if (queryIdx >= 0) cut = Math.min(cut, queryIdx);
862
- if (fragIdx >= 0) cut = Math.min(cut, fragIdx);
863
- return trimmed.slice(0, cut);
563
+ var REDACTION_PATTERNS = [
564
+ // Order matters: redact specific token shapes BEFORE the generic
565
+ // key=value catcher so a literal `Bearer eyJ…` collapses into a single
566
+ // [REDACTED] and the JWT regex does not separately match the suffix.
567
+ {
568
+ name: "bearer",
569
+ // Case-insensitive on the scheme: HTTP frameworks and proxies
570
+ // round-trip the auth scheme with inconsistent casing
571
+ // (`Bearer`, `bearer`, `BEARER`), and a real token leaks just as
572
+ // badly under any of them.
573
+ pattern: /\bBearer\s+[A-Za-z0-9._\-+/=]+/gi
574
+ },
575
+ {
576
+ name: "jwt",
577
+ // Three base64url segments separated by dots. Real JWTs encode at
578
+ // minimum a small JSON header in the first segment, which alone is
579
+ // typically ≥10 chars after base64url; a 16-char floor avoids false
580
+ // positives on dotted text like a stack-trace frame
581
+ // (`react.dom.server`) while still catching every real JWT we have
582
+ // seen in the wild. Anchored with word boundaries on both sides so
583
+ // a 3-dot semantic version like "next@15.4.1.2" does not match.
584
+ pattern: /\b[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g
585
+ },
586
+ {
587
+ name: "glasstrace-api-key",
588
+ // gt_dev_* and gt_anon_* keys are >=24 chars of [A-Za-z0-9].
589
+ pattern: /\bgt_(?:dev|anon)_[A-Za-z0-9]{16,}\b/g
590
+ },
591
+ {
592
+ name: "aws-access-key",
593
+ // 20-char prefix-fixed identifier.
594
+ pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g
595
+ },
596
+ {
597
+ name: "key-value-secret-quoted",
598
+ // Quoted-string variant: (key) [:=] "<value>". The value runs to
599
+ // the next unescaped closing quote so a multi-word secret like
600
+ // `password="my secret phrase"` is fully consumed instead of
601
+ // splitting at the first space and leaving the tail visible.
602
+ // The leading `(?<![A-Za-z0-9_])` prevents matching inside
603
+ // identifiers like `passwordless`. The trailing `"?` after the
604
+ // keyword absorbs the closing quote in JSON-style `"apikey":
605
+ // "value"` so the colon is still seen as the separator.
606
+ pattern: /(?<![A-Za-z0-9_])(?:api[_-]?key|apikey|secret|password|token)"?\s*[:=]\s*"(?:[^"\\]|\\.)*"/gi
607
+ },
608
+ {
609
+ name: "key-value-secret-bare",
610
+ // Unquoted variant: (key) [:=] <bare-value>. The bare value
611
+ // capture stops at common JSON/text delimiters so we redact only
612
+ // the value, not surrounding structure. Listed AFTER the quoted
613
+ // variant so a quoted value's surrounding `"` are consumed by
614
+ // the first pattern and we never fall through here for a quoted
615
+ // secret.
616
+ pattern: /(?<![A-Za-z0-9_])(?:api[_-]?key|apikey|secret|password|token)"?\s*[:=]\s*[^\s,;}\]"]+/gi
864
617
  }
865
- return void 0;
618
+ ];
619
+ function sanitizeErrorResponseBody(body) {
620
+ let out = body;
621
+ for (const { pattern } of REDACTION_PATTERNS) {
622
+ out = out.replace(pattern, REDACTED);
623
+ }
624
+ return out;
866
625
  }
867
- function deriveOrmProvider(instrumentationName) {
868
- const lower = instrumentationName.toLowerCase();
869
- if (lower.includes("prisma")) {
870
- return "prisma";
626
+ function truncateErrorResponseBody(body) {
627
+ const encoder = new TextEncoder();
628
+ const encoded = encoder.encode(body);
629
+ if (encoded.byteLength <= ERROR_RESPONSE_BODY_MAX_BYTES) {
630
+ return body;
871
631
  }
872
- if (lower.includes("drizzle")) {
873
- return "drizzle";
632
+ let cut = ERROR_RESPONSE_BODY_MAX_BYTES;
633
+ let scan = cut - 1;
634
+ while (scan >= 0 && (encoded[scan] & 192) === 128) {
635
+ scan -= 1;
874
636
  }
875
- return null;
637
+ if (scan >= 0) {
638
+ const leading = encoded[scan];
639
+ let expected = 1;
640
+ if ((leading & 128) === 0) {
641
+ expected = 1;
642
+ } else if ((leading & 224) === 192) {
643
+ expected = 2;
644
+ } else if ((leading & 240) === 224) {
645
+ expected = 3;
646
+ } else if ((leading & 248) === 240) {
647
+ expected = 4;
648
+ }
649
+ if (scan + expected > cut) {
650
+ cut = scan;
651
+ }
652
+ }
653
+ const decoder = new TextDecoder("utf-8", { fatal: false });
654
+ const sliced = encoded.subarray(0, cut);
655
+ const decoded = decoder.decode(sliced);
656
+ return decoded + ERROR_RESPONSE_BODY_TRUNCATION_MARKER;
876
657
  }
877
- function deriveErrorCategory(errorType) {
878
- const lower = errorType.toLowerCase();
879
- if (lower.includes("validation") || lower.includes("zod")) {
880
- return "validation";
658
+ function prepareErrorResponseBody(body) {
659
+ if (body.length === 0) return null;
660
+ if (body.trim().length === 0) return null;
661
+ const sanitized = sanitizeErrorResponseBody(body);
662
+ return truncateErrorResponseBody(sanitized);
663
+ }
664
+
665
+ // src/error-stack.ts
666
+ var ERROR_STACK_MAX_BYTES = 8192;
667
+ var ERROR_STACK_TRUNCATION_MARKER = "...[stack truncated]";
668
+ var PATH_REDACTED = "<path>";
669
+ var PATH_KEEP_MARKERS = [
670
+ "node_modules",
671
+ ".next",
672
+ ".glasstrace",
673
+ "src",
674
+ "dist",
675
+ "build",
676
+ "lib",
677
+ "app",
678
+ "pages"
679
+ ];
680
+ var PATH_TOKEN_RE = /(?<=^|[\s(])(\/[^\s()<>]+|[A-Za-z]:\\[^\s()<>]+|file:\/\/\/[^\s()<>]+|webpack-internal:\/\/[^\s()<>]+|node:[^\s()<>]+)/g;
681
+ var URL_QUERY_FRAGMENT_RE = /(\bhttps?:\/\/[^\s?#()<>]+)([?#][^\s()<>]*)/g;
682
+ function normalizePathToken(token) {
683
+ let work = token;
684
+ if (work.startsWith("file:///")) {
685
+ work = work.slice("file://".length);
881
686
  }
882
- if (lower.includes("network") || lower.includes("econnrefused") || lower.includes("fetch") || lower.includes("timeout")) {
883
- return "network";
687
+ if (work.startsWith("webpack-internal:") || work.startsWith("node:")) {
688
+ return { token, changed: false };
884
689
  }
885
- if (lower.includes("auth") || lower.includes("unauthorized") || lower.includes("forbidden")) {
886
- return "auth";
690
+ const isPosixAbs = work.startsWith("/");
691
+ const isWinAbs = /^[A-Za-z]:\\/.test(work);
692
+ if (!isPosixAbs && !isWinAbs) {
693
+ return { token, changed: false };
887
694
  }
888
- if (lower.includes("notfound") || lower.includes("not_found")) {
889
- return "not-found";
695
+ const sep = isWinAbs ? "\\" : "/";
696
+ let bestIdx = -1;
697
+ for (const marker of PATH_KEEP_MARKERS) {
698
+ const needle = `${sep}${marker}${sep}`;
699
+ const idx = work.lastIndexOf(needle);
700
+ if (idx >= 0) {
701
+ bestIdx = idx;
702
+ break;
703
+ }
890
704
  }
891
- return "internal";
705
+ if (bestIdx >= 0) {
706
+ const kept = work.slice(bestIdx + sep.length);
707
+ const rebuilt2 = `${PATH_REDACTED}/${kept.replace(/\\/g, "/")}`;
708
+ return { token: rebuilt2, changed: true };
709
+ }
710
+ const colonLineRe = /:\d+(?::\d+)?$/;
711
+ const lineMatch = colonLineRe.exec(work);
712
+ const pathBody = lineMatch ? work.slice(0, lineMatch.index) : work;
713
+ const lineSuffix = lineMatch ? work.slice(lineMatch.index) : "";
714
+ const lastSep = Math.max(pathBody.lastIndexOf("/"), pathBody.lastIndexOf("\\"));
715
+ const basename = lastSep >= 0 ? pathBody.slice(lastSep + 1) : pathBody;
716
+ const rebuilt = `${PATH_REDACTED}/${basename}${lineSuffix}`;
717
+ return { token: rebuilt, changed: true };
892
718
  }
893
-
894
- // src/lifecycle.ts
895
- import { EventEmitter } from "node:events";
896
-
897
- // src/signal-handler.ts
898
- var coexistenceState = "unknown";
899
- function setCoexistenceState(s) {
900
- coexistenceState = s;
719
+ function sanitizeStack(stack) {
720
+ let changed = false;
721
+ const pathNormalized = stack.replace(PATH_TOKEN_RE, (token) => {
722
+ const out = normalizePathToken(token);
723
+ if (out.changed) changed = true;
724
+ return out.token;
725
+ });
726
+ const urlStripped = pathNormalized.replace(URL_QUERY_FRAGMENT_RE, (match, prefix) => {
727
+ if (match !== prefix) changed = true;
728
+ return prefix;
729
+ });
730
+ const credentialRedacted = sanitizeErrorResponseBody(urlStripped);
731
+ if (credentialRedacted !== urlStripped) changed = true;
732
+ return { stack: credentialRedacted, redacted: changed };
901
733
  }
902
- function getCoexistenceState() {
903
- return coexistenceState;
734
+ function truncateStack(stack) {
735
+ const encoder = new TextEncoder();
736
+ const encoded = encoder.encode(stack);
737
+ if (encoded.byteLength <= ERROR_STACK_MAX_BYTES) {
738
+ return { stack, truncated: false };
739
+ }
740
+ let cut = ERROR_STACK_MAX_BYTES;
741
+ let scan = cut - 1;
742
+ while (scan >= 0 && (encoded[scan] & 192) === 128) {
743
+ scan -= 1;
744
+ }
745
+ if (scan >= 0) {
746
+ const leading = encoded[scan];
747
+ let expected = 1;
748
+ if ((leading & 128) === 0) {
749
+ expected = 1;
750
+ } else if ((leading & 224) === 192) {
751
+ expected = 2;
752
+ } else if ((leading & 240) === 224) {
753
+ expected = 3;
754
+ } else if ((leading & 248) === 240) {
755
+ expected = 4;
756
+ }
757
+ if (scan + expected > cut) {
758
+ cut = scan;
759
+ }
760
+ }
761
+ const decoder = new TextDecoder("utf-8", { fatal: false });
762
+ const sliced = encoded.subarray(0, cut);
763
+ const decoded = decoder.decode(sliced);
764
+ return { stack: decoded + ERROR_STACK_TRUNCATION_MARKER, truncated: true };
765
+ }
766
+ function prepareStack(stack) {
767
+ if (stack.length === 0) return null;
768
+ if (stack.trim().length === 0) return null;
769
+ const sanitized = sanitizeStack(stack);
770
+ const truncated = truncateStack(sanitized.stack);
771
+ return {
772
+ stack: truncated.stack,
773
+ truncated: truncated.truncated,
774
+ redacted: sanitized.redacted
775
+ };
904
776
  }
905
777
 
906
- // src/lifecycle.ts
907
- var CoreState = {
908
- IDLE: "IDLE",
909
- REGISTERING: "REGISTERING",
910
- KEY_PENDING: "KEY_PENDING",
911
- KEY_RESOLVED: "KEY_RESOLVED",
912
- ACTIVE: "ACTIVE",
913
- ACTIVE_DEGRADED: "ACTIVE_DEGRADED",
914
- SHUTTING_DOWN: "SHUTTING_DOWN",
915
- SHUTDOWN: "SHUTDOWN",
916
- PRODUCTION_DISABLED: "PRODUCTION_DISABLED",
917
- REGISTRATION_FAILED: "REGISTRATION_FAILED"
918
- };
919
- var AuthState = {
920
- ANONYMOUS: "ANONYMOUS",
921
- AUTHENTICATED: "AUTHENTICATED",
922
- CLAIMING: "CLAIMING",
923
- CLAIMED: "CLAIMED"
924
- };
925
- var OtelState = {
926
- UNCONFIGURED: "UNCONFIGURED",
927
- CONFIGURING: "CONFIGURING",
928
- OWNS_PROVIDER: "OWNS_PROVIDER",
929
- AUTO_ATTACHED: "AUTO_ATTACHED",
930
- PROCESSOR_PRESENT: "PROCESSOR_PRESENT",
931
- COEXISTENCE_FAILED: "COEXISTENCE_FAILED"
932
- };
933
- var VALID_CORE_TRANSITIONS = {
934
- [CoreState.IDLE]: [CoreState.REGISTERING, CoreState.REGISTRATION_FAILED, CoreState.SHUTTING_DOWN],
935
- [CoreState.REGISTERING]: [
936
- CoreState.KEY_PENDING,
937
- CoreState.PRODUCTION_DISABLED,
938
- CoreState.REGISTRATION_FAILED,
939
- CoreState.SHUTTING_DOWN
940
- ],
941
- [CoreState.KEY_PENDING]: [
942
- CoreState.KEY_RESOLVED,
943
- CoreState.REGISTRATION_FAILED,
944
- CoreState.SHUTTING_DOWN
945
- ],
946
- [CoreState.KEY_RESOLVED]: [
947
- CoreState.ACTIVE,
948
- CoreState.ACTIVE_DEGRADED,
949
- CoreState.SHUTTING_DOWN
950
- ],
951
- [CoreState.ACTIVE]: [
952
- CoreState.ACTIVE_DEGRADED,
953
- CoreState.SHUTTING_DOWN
954
- ],
955
- [CoreState.ACTIVE_DEGRADED]: [
956
- CoreState.ACTIVE,
957
- CoreState.SHUTTING_DOWN
958
- ],
959
- [CoreState.SHUTTING_DOWN]: [CoreState.SHUTDOWN],
960
- [CoreState.SHUTDOWN]: [],
961
- [CoreState.PRODUCTION_DISABLED]: [],
962
- [CoreState.REGISTRATION_FAILED]: []
963
- };
964
- var VALID_AUTH_TRANSITIONS = {
965
- [AuthState.ANONYMOUS]: [AuthState.CLAIMING],
966
- [AuthState.AUTHENTICATED]: [AuthState.CLAIMING],
967
- [AuthState.CLAIMING]: [AuthState.CLAIMED],
968
- [AuthState.CLAIMED]: [AuthState.CLAIMING]
969
- };
970
- var VALID_OTEL_TRANSITIONS = {
971
- [OtelState.UNCONFIGURED]: [OtelState.CONFIGURING],
972
- [OtelState.CONFIGURING]: [
973
- OtelState.OWNS_PROVIDER,
974
- OtelState.AUTO_ATTACHED,
975
- OtelState.PROCESSOR_PRESENT,
976
- OtelState.COEXISTENCE_FAILED
977
- ],
978
- [OtelState.OWNS_PROVIDER]: [],
979
- [OtelState.AUTO_ATTACHED]: [],
980
- [OtelState.PROCESSOR_PRESENT]: [],
981
- [OtelState.COEXISTENCE_FAILED]: []
982
- };
983
- var _coreState = CoreState.IDLE;
984
- var _authState = AuthState.ANONYMOUS;
985
- var _otelState = OtelState.UNCONFIGURED;
986
- var _emitter = new EventEmitter();
987
- var _logger = null;
988
- var _initialized = false;
989
- var _initWarned = false;
990
- var _coreReadyEmitted = false;
991
- var _authInitialized = false;
992
- var _emitting = false;
993
- function initLifecycle(options) {
994
- if (_initialized) {
995
- options.logger("warn", "[glasstrace] initLifecycle() called twice \u2014 ignored.");
996
- return;
997
- }
998
- _logger = options.logger;
999
- _initialized = true;
778
+ // src/build-info.ts
779
+ var UNSET = "";
780
+ var SHA_SHAPE = /^[0-9a-f]{7,64}$/i;
781
+ function redactBuildHash(value) {
782
+ const sanitize = (s) => s.replace(/[\x00-\x1F\x7F]/g, "?");
783
+ if (value.length <= 12) return sanitize(value.slice(0, 4)) + "...";
784
+ return sanitize(value.slice(0, 8)) + "..." + sanitize(value.slice(-4));
1000
785
  }
1001
- function warnIfNotInitialized() {
1002
- if (!_initialized && !_initWarned) {
1003
- _initWarned = true;
1004
- console.warn(
1005
- "[glasstrace] Lifecycle state changed before initLifecycle() was called. Logger not available \u2014 errors will be silent."
786
+ function readBuildHashFromEnv() {
787
+ const raw = process.env.GLASSTRACE_BUILD_HASH;
788
+ if (typeof raw !== "string") return UNSET;
789
+ const trimmed = raw.trim();
790
+ if (trimmed.length === 0) return UNSET;
791
+ if (!SHA_SHAPE.test(trimmed)) {
792
+ sdkLog(
793
+ "warn",
794
+ `[glasstrace] warning: GLASSTRACE_BUILD_HASH=${redactBuildHash(trimmed)} does not match expected SHA shape (7-64 hex characters); source-map enrichment may not work as expected.`
1006
795
  );
1007
796
  }
797
+ return trimmed;
1008
798
  }
1009
- function setCoreState(to) {
1010
- warnIfNotInitialized();
1011
- const from = _coreState;
1012
- if (from === to) return;
1013
- const valid = VALID_CORE_TRANSITIONS[from];
1014
- if (!valid.includes(to)) {
1015
- _logger?.(
1016
- "warn",
1017
- `[glasstrace] Invalid core state transition: ${from} \u2192 ${to}. Ignored.`
1018
- );
1019
- return;
799
+ var cachedBuildHash = null;
800
+ function getBuildHash() {
801
+ if (cachedBuildHash === null) {
802
+ cachedBuildHash = readBuildHashFromEnv();
1020
803
  }
1021
- _coreState = to;
1022
- if (_emitting) return;
1023
- _emitting = true;
1024
- try {
1025
- emitSafe("core:state_changed", { from, to });
1026
- const current = _coreState;
1027
- if (!_coreReadyEmitted && (current === CoreState.ACTIVE || current === CoreState.ACTIVE_DEGRADED)) {
1028
- _coreReadyEmitted = true;
1029
- emitSafe("core:ready", {});
1030
- }
1031
- if (current === CoreState.SHUTTING_DOWN) {
1032
- emitSafe("core:shutdown_started", {});
1033
- }
1034
- if (current === CoreState.SHUTDOWN) {
1035
- emitSafe("core:shutdown_completed", {});
804
+ return cachedBuildHash === UNSET ? void 0 : cachedBuildHash;
805
+ }
806
+
807
+ // src/export-circuit-breaker.ts
808
+ var INITIAL_BACKOFF_MS = 3e4;
809
+ var BACKOFF_FACTOR = 2;
810
+ var MAX_BACKOFF_MS = 30 * 60 * 1e3;
811
+ var FAILURE_THRESHOLD = 5;
812
+ function classifyExportFailure(info) {
813
+ const status = readStatus(info);
814
+ if (status === 401 || status === 403) return "auth";
815
+ if (status === 429) return "rate_limit";
816
+ if (typeof status === "number" && status >= 500 && status <= 599) return "server_error";
817
+ if (typeof status === "number" && status >= 400 && status <= 499) return "client_error";
818
+ return "network";
819
+ }
820
+ function readStatus(info) {
821
+ if (typeof info.status === "number") return info.status;
822
+ const err = info.error;
823
+ if (!err || typeof err !== "object") return void 0;
824
+ const record = err;
825
+ const direct = record.status;
826
+ if (typeof direct === "number") return direct;
827
+ if (typeof direct === "string") {
828
+ const parsed = Number.parseInt(direct, 10);
829
+ if (Number.isFinite(parsed)) return parsed;
830
+ }
831
+ const nested = record.response;
832
+ if (nested && typeof nested === "object") {
833
+ const nestedStatus = nested.status;
834
+ if (typeof nestedStatus === "number") return nestedStatus;
835
+ if (typeof nestedStatus === "string") {
836
+ const parsed = Number.parseInt(nestedStatus, 10);
837
+ if (Number.isFinite(parsed)) return parsed;
1036
838
  }
1037
- } finally {
1038
- _emitting = false;
1039
839
  }
840
+ return void 0;
1040
841
  }
1041
- function initAuthState(state) {
1042
- if (_authInitialized) {
1043
- _logger?.(
1044
- "warn",
1045
- "[glasstrace] initAuthState() called after auth state already initialized. Ignored."
1046
- );
1047
- return;
842
+ function buildOpenedMessage(category, count) {
843
+ return `[glasstrace] Export circuit opened after ${count} consecutive failures (category: ${category}). Subsequent spans dropped until probe succeeds.`;
844
+ }
845
+ var _singleton = null;
846
+ function getExportCircuitBreaker(options) {
847
+ if (_singleton === null) {
848
+ _singleton = createExportCircuitBreaker(options);
1048
849
  }
1049
- _authInitialized = true;
1050
- _authState = state;
850
+ return _singleton;
1051
851
  }
1052
- function setAuthState(to) {
1053
- warnIfNotInitialized();
1054
- const from = _authState;
1055
- if (from === to) return;
1056
- const valid = VALID_AUTH_TRANSITIONS[from];
1057
- if (!valid.includes(to)) {
1058
- _logger?.(
1059
- "warn",
1060
- `[glasstrace] Invalid auth state transition: ${from} \u2192 ${to}. Ignored.`
1061
- );
1062
- return;
852
+ function peekExportCircuitBreaker() {
853
+ return _singleton;
854
+ }
855
+ function createExportCircuitBreaker(options) {
856
+ const events = options.events;
857
+ const recordDropped = options.recordDropped;
858
+ const fsm = options.fsm;
859
+ const now = options.now ?? (() => Date.now());
860
+ const setTimer = options.setTimer ?? ((fn, delayMs) => {
861
+ const handle = setTimeout(fn, delayMs);
862
+ if (typeof handle === "object" && handle && "unref" in handle) {
863
+ handle.unref();
864
+ }
865
+ return handle;
866
+ });
867
+ const clearTimer = options.clearTimer ?? ((handle) => clearTimeout(handle));
868
+ let state = "CLOSED";
869
+ let consecutiveFailures = 0;
870
+ let currentBackoffMs = INITIAL_BACKOFF_MS;
871
+ let openedAtMs = null;
872
+ let pendingTimer = null;
873
+ let halfOpenProbeInFlight = false;
874
+ let generation = 0;
875
+ function clearPendingTimer() {
876
+ if (pendingTimer !== null) {
877
+ clearTimer(pendingTimer);
878
+ pendingTimer = null;
879
+ }
880
+ }
881
+ function scheduleHalfOpen(delayMs) {
882
+ clearPendingTimer();
883
+ pendingTimer = setTimer(() => {
884
+ pendingTimer = null;
885
+ if (state !== "OPEN") return;
886
+ transitionToHalfOpen(delayMs);
887
+ }, delayMs);
888
+ }
889
+ function transitionToOpen(category) {
890
+ const wasNonOpen = state !== "OPEN";
891
+ state = "OPEN";
892
+ halfOpenProbeInFlight = false;
893
+ if (openedAtMs === null) {
894
+ openedAtMs = now();
895
+ }
896
+ if (wasNonOpen) {
897
+ const timestamp = new Date(now()).toISOString();
898
+ const message = buildOpenedMessage(category, consecutiveFailures);
899
+ try {
900
+ events.emitOpened({
901
+ category,
902
+ message,
903
+ timestamp,
904
+ consecutiveFailures,
905
+ nextProbeMs: currentBackoffMs
906
+ });
907
+ } catch {
908
+ }
909
+ try {
910
+ fsm?.onCircuitOpened();
911
+ } catch {
912
+ }
913
+ }
914
+ scheduleHalfOpen(currentBackoffMs);
1063
915
  }
1064
- _authState = to;
916
+ function transitionToHalfOpen(previousTimerMs) {
917
+ state = "HALF_OPEN";
918
+ halfOpenProbeInFlight = false;
919
+ try {
920
+ events.emitHalfOpen({
921
+ timestamp: new Date(now()).toISOString(),
922
+ previousTimerMs
923
+ });
924
+ } catch {
925
+ }
926
+ }
927
+ function transitionToClosed() {
928
+ const startedAt = openedAtMs;
929
+ state = "CLOSED";
930
+ consecutiveFailures = 0;
931
+ currentBackoffMs = INITIAL_BACKOFF_MS;
932
+ openedAtMs = null;
933
+ halfOpenProbeInFlight = false;
934
+ clearPendingTimer();
935
+ try {
936
+ events.emitClosed({
937
+ timestamp: new Date(now()).toISOString(),
938
+ outageDurationMs: startedAt === null ? 0 : Math.max(0, now() - startedAt)
939
+ });
940
+ } catch {
941
+ }
942
+ try {
943
+ fsm?.onCircuitClosed();
944
+ } catch {
945
+ }
946
+ }
947
+ return {
948
+ shouldExport() {
949
+ if (state === "OPEN") return false;
950
+ if (state === "HALF_OPEN") {
951
+ if (halfOpenProbeInFlight) return false;
952
+ halfOpenProbeInFlight = true;
953
+ return true;
954
+ }
955
+ return true;
956
+ },
957
+ recordSuccess() {
958
+ if (state === "HALF_OPEN") {
959
+ halfOpenProbeInFlight = false;
960
+ transitionToClosed();
961
+ return;
962
+ }
963
+ consecutiveFailures = 0;
964
+ },
965
+ recordFailure(info) {
966
+ const category = classifyExportFailure(info);
967
+ if (state === "HALF_OPEN") {
968
+ currentBackoffMs = Math.min(currentBackoffMs * BACKOFF_FACTOR, MAX_BACKOFF_MS);
969
+ halfOpenProbeInFlight = false;
970
+ state = "OPEN";
971
+ scheduleHalfOpen(currentBackoffMs);
972
+ return;
973
+ }
974
+ if (state === "CLOSED") {
975
+ consecutiveFailures += 1;
976
+ if (consecutiveFailures >= FAILURE_THRESHOLD) {
977
+ transitionToOpen(category);
978
+ }
979
+ return;
980
+ }
981
+ },
982
+ onSpansDropped(count) {
983
+ if (!Number.isFinite(count) || count <= 0) return;
984
+ try {
985
+ recordDropped(count);
986
+ } catch {
987
+ }
988
+ },
989
+ getState() {
990
+ return state;
991
+ },
992
+ resetForKeyRotation() {
993
+ generation += 1;
994
+ const wasNonClosed = state !== "CLOSED";
995
+ consecutiveFailures = 0;
996
+ currentBackoffMs = INITIAL_BACKOFF_MS;
997
+ clearPendingTimer();
998
+ if (wasNonClosed) {
999
+ transitionToClosed();
1000
+ }
1001
+ },
1002
+ getGeneration() {
1003
+ return generation;
1004
+ }
1005
+ };
1065
1006
  }
1066
- function setOtelState(to) {
1067
- warnIfNotInitialized();
1068
- const from = _otelState;
1069
- if (from === to) return;
1070
- const valid = VALID_OTEL_TRANSITIONS[from];
1071
- if (!valid.includes(to)) {
1072
- _logger?.(
1073
- "warn",
1074
- `[glasstrace] Invalid OTel state transition: ${from} \u2192 ${to}. Ignored.`
1075
- );
1076
- return;
1007
+
1008
+ // src/enriching-exporter.ts
1009
+ var ATTR = GLASSTRACE_ATTRIBUTE_NAMES;
1010
+ var API_KEY_PENDING = "pending";
1011
+ var MAX_PENDING_SPANS = 1024;
1012
+ var GlasstraceExporter = class {
1013
+ getApiKey;
1014
+ sessionManager;
1015
+ getConfig;
1016
+ environment;
1017
+ endpointUrl;
1018
+ createDelegateFn;
1019
+ verbose;
1020
+ delegate = null;
1021
+ delegateKey = null;
1022
+ pendingBatches = [];
1023
+ pendingSpanCount = 0;
1024
+ overflowLogged = false;
1025
+ /**
1026
+ * Lazily-bound reference to the export-path circuit breaker
1027
+ * (DISC-1568 / Wave 15C). Resolved on first export so this
1028
+ * constructor stays side-effect-free. The breaker is a module-
1029
+ * singleton — every `GlasstraceExporter` instance shares the same
1030
+ * one so a rotation event observed in `init-client.ts` reaches
1031
+ * every active exporter.
1032
+ */
1033
+ circuitBreaker = null;
1034
+ constructor(options) {
1035
+ this.getApiKey = options.getApiKey;
1036
+ this.sessionManager = options.sessionManager;
1037
+ this.getConfig = options.getConfig;
1038
+ this.environment = options.environment;
1039
+ this.endpointUrl = options.endpointUrl;
1040
+ this.createDelegateFn = options.createDelegate;
1041
+ this.verbose = options.verbose ?? false;
1042
+ this[/* @__PURE__ */ Symbol.for("glasstrace.exporter")] = true;
1043
+ }
1044
+ /**
1045
+ * Returns the export-path circuit breaker, lazily wiring it on
1046
+ * first call. The breaker is a module-singleton so all exporter
1047
+ * instances share state — a credential rotation observed once
1048
+ * resets the breaker for every active exporter, and a single
1049
+ * outage at the OTLP endpoint trips a single breaker rather than
1050
+ * one per exporter copy.
1051
+ *
1052
+ * The wiring binds:
1053
+ * - the lifecycle event sink to the SDK's lifecycle bus
1054
+ * (`emitLifecycleEvent`) so the `otel:circuit_*` events surface
1055
+ * to runtime-state, the CLI bridge, and any user-installed
1056
+ * subscribers.
1057
+ * - the dropped-span counter to {@link recordSpansDropped} so OPEN-
1058
+ * state drops show up in the existing health surface.
1059
+ * - the FSM hooks to {@link pushDegradationSource} /
1060
+ * {@link clearDegradationSource} keyed on `"export-circuit"`,
1061
+ * which routes the OPEN/CLOSED transitions through the
1062
+ * centralised `recomputeCoreFromDegradationSources()` helper.
1063
+ * That helper guards `ACTIVE ↔ ACTIVE_DEGRADED` so a circuit
1064
+ * recovery never clobbers an unrelated `OtelState.COEXISTENCE_FAILED`
1065
+ * degradation source.
1066
+ */
1067
+ getCircuitBreaker() {
1068
+ if (this.circuitBreaker !== null) return this.circuitBreaker;
1069
+ this.circuitBreaker = getExportCircuitBreaker({
1070
+ events: {
1071
+ emitOpened: (payload) => emitLifecycleEvent("otel:circuit_opened", payload),
1072
+ emitHalfOpen: (payload) => emitLifecycleEvent("otel:circuit_half_open", payload),
1073
+ emitClosed: (payload) => emitLifecycleEvent("otel:circuit_closed", payload)
1074
+ },
1075
+ recordDropped: (count) => recordSpansDropped(count),
1076
+ fsm: {
1077
+ onCircuitOpened: () => pushDegradationSource("export-circuit"),
1078
+ onCircuitClosed: () => clearDegradationSource("export-circuit")
1079
+ }
1080
+ });
1081
+ return this.circuitBreaker;
1082
+ }
1083
+ export(spans, resultCallback) {
1084
+ const currentKey = this.getApiKey();
1085
+ if (currentKey === API_KEY_PENDING) {
1086
+ this.bufferSpans(spans, resultCallback);
1087
+ return;
1088
+ }
1089
+ const breaker = this.getCircuitBreaker();
1090
+ if (!breaker.shouldExport()) {
1091
+ breaker.onSpansDropped(spans.length);
1092
+ resultCallback({ code: 0 });
1093
+ return;
1094
+ }
1095
+ const enrichedSpans = spans.map((span) => this.enrichSpan(span));
1096
+ const exporter = this.ensureDelegate();
1097
+ if (exporter) {
1098
+ const generationAtIssue = breaker.getGeneration();
1099
+ exporter.export(enrichedSpans, (result) => {
1100
+ if (result.code !== 0) {
1101
+ sdkLog("warn", `[glasstrace] Span export failed: ${result.error?.message ?? "unknown error"}`);
1102
+ }
1103
+ if (breaker.getGeneration() !== generationAtIssue) {
1104
+ resultCallback(result);
1105
+ return;
1106
+ }
1107
+ if (result.code === 0) {
1108
+ breaker.recordSuccess();
1109
+ } else {
1110
+ breaker.recordFailure({ error: result.error });
1111
+ }
1112
+ resultCallback(result);
1113
+ });
1114
+ recordSpansExported(enrichedSpans.length);
1115
+ } else {
1116
+ recordSpansDropped(enrichedSpans.length);
1117
+ resultCallback({ code: 0 });
1118
+ }
1119
+ }
1120
+ /**
1121
+ * Called when the API key transitions from "pending" to a resolved value.
1122
+ * Creates the delegate exporter and flushes all buffered spans.
1123
+ */
1124
+ notifyKeyResolved() {
1125
+ this.flushPending();
1126
+ }
1127
+ async shutdown() {
1128
+ const currentKey = this.getApiKey();
1129
+ if (currentKey !== API_KEY_PENDING && this.pendingBatches.length > 0) {
1130
+ this.flushPending();
1131
+ } else if (this.pendingBatches.length > 0) {
1132
+ console.warn(
1133
+ `[glasstrace] Shutdown with ${this.pendingSpanCount} buffered spans \u2014 API key never resolved, spans lost.`
1134
+ );
1135
+ recordSpansDropped(this.pendingSpanCount);
1136
+ for (const batch of this.pendingBatches) {
1137
+ batch.resultCallback({ code: 0 });
1138
+ }
1139
+ this.pendingBatches = [];
1140
+ this.pendingSpanCount = 0;
1141
+ }
1142
+ if (this.delegate) {
1143
+ return this.delegate.shutdown();
1144
+ }
1145
+ }
1146
+ /**
1147
+ * Flushes any pending buffered spans (if the API key has resolved) and
1148
+ * delegates to the underlying exporter's forceFlush to drain its queue.
1149
+ */
1150
+ forceFlush() {
1151
+ if (this.getApiKey() !== API_KEY_PENDING && this.pendingBatches.length > 0) {
1152
+ this.flushPending();
1153
+ }
1154
+ if (this.delegate?.forceFlush) {
1155
+ return this.delegate.forceFlush();
1156
+ }
1157
+ return Promise.resolve();
1158
+ }
1159
+ /**
1160
+ * Enriches a ReadableSpan with all glasstrace.* attributes.
1161
+ * Returns a new ReadableSpan wrapper; the original span is not mutated.
1162
+ *
1163
+ * Only {@link SessionManager.getSessionId} is individually guarded because
1164
+ * it calls into crypto and schema validation — a session ID failure should
1165
+ * not prevent the rest of enrichment. The other helper calls
1166
+ * ({@link deriveErrorCategory}, {@link deriveOrmProvider},
1167
+ * {@link classifyFetchTarget}) are pure functions on typed string inputs
1168
+ * and rely on the outer catch for any unexpected failure.
1169
+ *
1170
+ * On total failure, returns the original span unchanged.
1171
+ */
1172
+ enrichSpan(span) {
1173
+ try {
1174
+ const attrs = span.attributes ?? {};
1175
+ const name = span.name ?? "";
1176
+ const extra = {};
1177
+ extra[ATTR.TRACE_TYPE] = "server";
1178
+ try {
1179
+ const sessionId = this.sessionManager.getSessionId(this.getApiKey());
1180
+ extra[ATTR.SESSION_ID] = sessionId;
1181
+ } catch {
1182
+ }
1183
+ const env = this.environment ?? process.env.GLASSTRACE_ENV;
1184
+ if (env) {
1185
+ extra[ATTR.ENVIRONMENT] = env;
1186
+ }
1187
+ const buildHash = getBuildHash();
1188
+ if (buildHash) {
1189
+ extra[ATTR.BUILD_HASH] = buildHash;
1190
+ }
1191
+ const existingCid = attrs["glasstrace.correlation.id"];
1192
+ if (typeof existingCid === "string") {
1193
+ extra[ATTR.CORRELATION_ID] = existingCid;
1194
+ }
1195
+ const rawRoute = attrs["http.route"];
1196
+ const route = typeof rawRoute === "string" ? rawRoute : name;
1197
+ if (route) {
1198
+ extra[ATTR.ROUTE] = route;
1199
+ }
1200
+ const rawUrlAttr = attrs["http.url"] ?? attrs["url.full"] ?? attrs["http.target"];
1201
+ const rawHttpUrl = typeof rawUrlAttr === "string" ? rawUrlAttr : void 0;
1202
+ if (rawHttpUrl) {
1203
+ const trpcMatch = rawHttpUrl.match(/\/api\/trpc\/([^/?#]+)/);
1204
+ if (trpcMatch) {
1205
+ let procedure;
1206
+ try {
1207
+ procedure = decodeURIComponent(trpcMatch[1]);
1208
+ } catch {
1209
+ procedure = trpcMatch[1];
1210
+ }
1211
+ if (procedure) {
1212
+ extra[ATTR.TRPC_PROCEDURE] = procedure;
1213
+ }
1214
+ }
1215
+ }
1216
+ const method = attrs["http.method"] ?? attrs["http.request.method"];
1217
+ if (method) {
1218
+ extra[ATTR.HTTP_METHOD] = method;
1219
+ }
1220
+ const actionRoute = extractLeadingPath(route);
1221
+ if (method === "POST" && actionRoute) {
1222
+ const isApiRoute = actionRoute === "/api" || actionRoute.startsWith("/api/");
1223
+ const isInternalRoute = actionRoute.startsWith("/_next/");
1224
+ if (!isApiRoute && !isInternalRoute) {
1225
+ extra[ATTR.NEXT_ACTION_DETECTED] = true;
1226
+ if (typeof extra[ATTR.CORRELATION_ID] !== "string") {
1227
+ maybeShowServerActionNudge();
1228
+ }
1229
+ }
1230
+ }
1231
+ const statusCode = coerceHttpStatus(attrs["http.status_code"]) ?? coerceHttpStatus(attrs["http.response.status_code"]);
1232
+ if (statusCode !== void 0) {
1233
+ extra[ATTR.HTTP_STATUS_CODE] = statusCode;
1234
+ }
1235
+ const isErrorByStatus = span.status?.code === SpanStatusCode.ERROR;
1236
+ const isErrorByEvent = hasExceptionEvent(span);
1237
+ const isErrorByAttrs = typeof attrs["exception.type"] === "string" || typeof attrs["exception.message"] === "string";
1238
+ const statusNotExplicitlyOK = span.status?.code !== SpanStatusCode.OK;
1239
+ if (this.verbose && method) {
1240
+ sdkLog(
1241
+ "info",
1242
+ `[glasstrace] enrichSpan "${name}": status.code=${span.status?.code}, http.status_code=${statusCode}, isErrorByStatus=${isErrorByStatus}, isErrorByEvent=${isErrorByEvent}, isErrorByAttrs=${isErrorByAttrs}`
1243
+ );
1244
+ }
1245
+ if (method && statusNotExplicitlyOK && (isErrorByStatus || isErrorByEvent || isErrorByAttrs)) {
1246
+ if (statusCode === void 0 || statusCode === 0 || statusCode === 200) {
1247
+ const httpErrorType = attrs["error.type"];
1248
+ if (typeof httpErrorType === "string") {
1249
+ const parsed = parseInt(httpErrorType, 10);
1250
+ if (!isNaN(parsed) && parsed >= 400 && parsed <= 599) {
1251
+ extra[ATTR.HTTP_STATUS_CODE] = parsed;
1252
+ } else {
1253
+ extra[ATTR.HTTP_STATUS_CODE] = 500;
1254
+ }
1255
+ } else {
1256
+ extra[ATTR.HTTP_STATUS_CODE] = 500;
1257
+ }
1258
+ if (this.verbose) {
1259
+ sdkLog(
1260
+ "info",
1261
+ `[glasstrace] enrichSpan "${name}": inferred status_code=${extra[ATTR.HTTP_STATUS_CODE]} (was ${statusCode}), error.type=${attrs["error.type"]}`
1262
+ );
1263
+ }
1264
+ }
1265
+ }
1266
+ if (span.startTime && span.endTime) {
1267
+ const [startSec, startNano] = span.startTime;
1268
+ const [endSec, endNano] = span.endTime;
1269
+ const durationMs = (endSec - startSec) * 1e3 + (endNano - startNano) / 1e6;
1270
+ if (durationMs >= 0) {
1271
+ extra[ATTR.HTTP_DURATION_MS] = durationMs;
1272
+ }
1273
+ }
1274
+ const eventDetails = statusNotExplicitlyOK ? getExceptionEventDetails(span) : { type: void 0, message: void 0, stacktrace: void 0 };
1275
+ let errorSource;
1276
+ const attrMessage = attrs["exception.message"];
1277
+ if (eventDetails.message) {
1278
+ extra[ATTR.ERROR_MESSAGE] = eventDetails.message;
1279
+ errorSource = "otel_exception";
1280
+ } else if (typeof attrMessage === "string") {
1281
+ extra[ATTR.ERROR_MESSAGE] = attrMessage;
1282
+ errorSource = "otel_event";
1283
+ }
1284
+ const attrType = attrs["exception.type"];
1285
+ if (eventDetails.type) {
1286
+ extra[ATTR.ERROR_CODE] = eventDetails.type;
1287
+ extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(eventDetails.type);
1288
+ errorSource = errorSource ?? "otel_exception";
1289
+ } else if (typeof attrType === "string") {
1290
+ extra[ATTR.ERROR_CODE] = attrType;
1291
+ extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(attrType);
1292
+ errorSource = errorSource ?? "otel_event";
1293
+ }
1294
+ if (statusNotExplicitlyOK) {
1295
+ const rawStack = eventDetails.stacktrace ?? (typeof attrs["exception.stacktrace"] === "string" ? attrs["exception.stacktrace"] : void 0);
1296
+ if (rawStack) {
1297
+ const prepared = prepareStack(rawStack);
1298
+ if (prepared !== null) {
1299
+ extra[ATTR.ERROR_STACK] = prepared.stack;
1300
+ extra[ATTR.ERROR_STACK_TRUNCATED] = prepared.truncated;
1301
+ extra[ATTR.ERROR_STACK_REDACTED] = prepared.redacted;
1302
+ errorSource = errorSource ?? (eventDetails.stacktrace ? "otel_exception" : "otel_event");
1303
+ }
1304
+ }
1305
+ }
1306
+ const routeIsFallback = route === "/_error" || route === "/_not-found" || route === "/_404" || route === "/_500";
1307
+ if (routeIsFallback && rawHttpUrl) {
1308
+ const originalPath = extractPathOnly(rawHttpUrl);
1309
+ const normOriginal = stripTrailingSlash(originalPath);
1310
+ const normRoute = stripTrailingSlash(route);
1311
+ if (normOriginal && normOriginal !== normRoute) {
1312
+ extra[ATTR.ERROR_ORIGINAL_PATH] = normOriginal;
1313
+ extra[ATTR.ERROR_FALLBACK_ROUTE] = route;
1314
+ extra[ATTR.ERROR_FRAMEWORK_KIND] = "fallback";
1315
+ errorSource = errorSource ?? "framework_fallback";
1316
+ }
1317
+ }
1318
+ if (errorSource !== void 0) {
1319
+ extra[ATTR.ERROR_SOURCE] = errorSource;
1320
+ }
1321
+ if (this.verbose && (extra[ATTR.ERROR_MESSAGE] || extra[ATTR.ERROR_CODE])) {
1322
+ const msgSource = eventDetails.message ? "event" : typeof attrMessage === "string" ? "attrs" : "none";
1323
+ const typeSource = eventDetails.type ? "event" : typeof attrType === "string" ? "attrs" : "none";
1324
+ sdkLog(
1325
+ "info",
1326
+ `[glasstrace] enrichSpan "${name}": error.message source=${msgSource}, error.code source=${typeSource}`
1327
+ );
1328
+ }
1329
+ const errorField = attrs["error.field"];
1330
+ if (typeof errorField === "string") {
1331
+ extra[ATTR.ERROR_FIELD] = errorField;
1332
+ }
1333
+ if (this.getConfig().errorResponseBodies) {
1334
+ const responseBody = attrs["glasstrace.internal.response_body"];
1335
+ if (typeof responseBody === "string") {
1336
+ const enrichedStatus = extra[ATTR.HTTP_STATUS_CODE];
1337
+ const effectiveStatus = typeof enrichedStatus === "number" ? enrichedStatus : statusCode;
1338
+ if (isHttpErrorStatus(effectiveStatus)) {
1339
+ const prepared = prepareErrorResponseBody(responseBody);
1340
+ if (prepared !== null) {
1341
+ extra[ATTR.ERROR_RESPONSE_BODY] = prepared;
1342
+ }
1343
+ }
1344
+ }
1345
+ }
1346
+ const spanAny = span;
1347
+ const instrumentationName = spanAny.instrumentationScope?.name ?? spanAny.instrumentationLibrary?.name ?? "";
1348
+ const ormProvider = deriveOrmProvider(instrumentationName);
1349
+ if (ormProvider) {
1350
+ extra[ATTR.ORM_PROVIDER] = ormProvider;
1351
+ const table = attrs["db.sql.table"];
1352
+ const prismaModel = attrs["db.prisma.model"];
1353
+ const model = typeof table === "string" ? table : typeof prismaModel === "string" ? prismaModel : void 0;
1354
+ if (model) {
1355
+ extra[ATTR.ORM_MODEL] = model;
1356
+ }
1357
+ const operation = attrs["db.operation"];
1358
+ if (typeof operation === "string") {
1359
+ extra[ATTR.ORM_OPERATION] = operation;
1360
+ }
1361
+ }
1362
+ const httpUrl = attrs["http.url"];
1363
+ const fullUrl = attrs["url.full"];
1364
+ const url = typeof httpUrl === "string" ? httpUrl : typeof fullUrl === "string" ? fullUrl : void 0;
1365
+ if (url && span.kind === SpanKind.CLIENT) {
1366
+ extra[ATTR.FETCH_TARGET] = classifyFetchTarget(url);
1367
+ }
1368
+ return createEnrichedSpan(span, extra);
1369
+ } catch {
1370
+ return span;
1371
+ }
1372
+ }
1373
+ /**
1374
+ * Lazily creates the delegate OTLP exporter once the API key is resolved.
1375
+ * Recreates the delegate if the key has changed (e.g., after key rotation)
1376
+ * so the Authorization header stays current.
1377
+ */
1378
+ ensureDelegate() {
1379
+ if (!this.createDelegateFn) return null;
1380
+ const currentKey = this.getApiKey();
1381
+ if (currentKey === API_KEY_PENDING) return null;
1382
+ if (this.delegate && this.delegateKey === currentKey) {
1383
+ return this.delegate;
1384
+ }
1385
+ if (this.delegate) {
1386
+ void this.delegate.shutdown?.().catch(() => {
1387
+ });
1388
+ }
1389
+ this.delegate = this.createDelegateFn(this.endpointUrl, {
1390
+ Authorization: `Bearer ${currentKey}`
1391
+ });
1392
+ this.delegateKey = currentKey;
1393
+ return this.delegate;
1394
+ }
1395
+ /**
1396
+ * Buffers raw (unenriched) spans while the API key is pending.
1397
+ * Evicts oldest batches if the buffer exceeds MAX_PENDING_SPANS.
1398
+ * Re-checks the key after buffering to close the race window where
1399
+ * the key resolves between the caller's check and this buffer call.
1400
+ */
1401
+ bufferSpans(spans, resultCallback) {
1402
+ this.pendingBatches.push({ spans, resultCallback });
1403
+ this.pendingSpanCount += spans.length;
1404
+ while (this.pendingSpanCount > MAX_PENDING_SPANS && this.pendingBatches.length > 1) {
1405
+ const evicted = this.pendingBatches.shift();
1406
+ this.pendingSpanCount -= evicted.spans.length;
1407
+ recordSpansDropped(evicted.spans.length);
1408
+ evicted.resultCallback({ code: 0 });
1409
+ if (!this.overflowLogged) {
1410
+ this.overflowLogged = true;
1411
+ console.warn(
1412
+ "[glasstrace] Pending span buffer overflow \u2014 oldest spans evicted. This usually means the API key is taking too long to resolve."
1413
+ );
1414
+ }
1415
+ }
1416
+ if (this.getApiKey() !== API_KEY_PENDING) {
1417
+ this.flushPending();
1418
+ }
1419
+ }
1420
+ /**
1421
+ * Flushes all buffered spans through the delegate exporter.
1422
+ * Enriches spans at flush time (not buffer time) so that session IDs
1423
+ * are computed with the resolved API key instead of the "pending" sentinel.
1424
+ *
1425
+ * Honors the circuit breaker symmetrically with {@link export}: if the
1426
+ * breaker is OPEN at flush time, every buffered batch is dropped via
1427
+ * `recordSpansDropped` and its callback completed with `{ code: 0 }`,
1428
+ * preserving the bounded-memory contract during outages.
1429
+ */
1430
+ flushPending() {
1431
+ if (this.pendingBatches.length === 0) return;
1432
+ const exporter = this.ensureDelegate();
1433
+ if (!exporter) {
1434
+ let discardedCount = 0;
1435
+ for (const batch of this.pendingBatches) {
1436
+ discardedCount += batch.spans.length;
1437
+ batch.resultCallback({ code: 0 });
1438
+ }
1439
+ recordSpansDropped(discardedCount);
1440
+ this.pendingBatches = [];
1441
+ this.pendingSpanCount = 0;
1442
+ return;
1443
+ }
1444
+ const breaker = this.getCircuitBreaker();
1445
+ const batches = this.pendingBatches;
1446
+ this.pendingBatches = [];
1447
+ this.pendingSpanCount = 0;
1448
+ for (const batch of batches) {
1449
+ if (!breaker.shouldExport()) {
1450
+ breaker.onSpansDropped(batch.spans.length);
1451
+ batch.resultCallback({ code: 0 });
1452
+ continue;
1453
+ }
1454
+ const enriched = batch.spans.map((span) => this.enrichSpan(span));
1455
+ const generationAtIssue = breaker.getGeneration();
1456
+ exporter.export(enriched, (result) => {
1457
+ if (result.code !== 0) {
1458
+ sdkLog("warn", `[glasstrace] Span export failed: ${result.error?.message ?? "unknown error"}`);
1459
+ }
1460
+ if (breaker.getGeneration() !== generationAtIssue) {
1461
+ batch.resultCallback(result);
1462
+ return;
1463
+ }
1464
+ if (result.code === 0) {
1465
+ breaker.recordSuccess();
1466
+ } else {
1467
+ breaker.recordFailure({ error: result.error });
1468
+ }
1469
+ batch.resultCallback(result);
1470
+ });
1471
+ recordSpansExported(enriched.length);
1472
+ }
1077
1473
  }
1078
- _otelState = to;
1474
+ };
1475
+ function createEnrichedSpan(span, extra) {
1476
+ const enrichedAttributes = { ...span.attributes, ...extra };
1477
+ return Object.create(span, {
1478
+ attributes: {
1479
+ value: enrichedAttributes,
1480
+ enumerable: true
1481
+ }
1482
+ });
1079
1483
  }
1080
- function getCoreState() {
1081
- return _coreState;
1484
+ function hasExceptionEvent(span) {
1485
+ return span.events?.some((e) => e.name === "exception") ?? false;
1082
1486
  }
1083
- function getSdkState() {
1487
+ function getExceptionEventDetails(span) {
1488
+ const event = span.events?.find((e) => e.name === "exception");
1489
+ if (!event?.attributes) {
1490
+ return { type: void 0, message: void 0, stacktrace: void 0 };
1491
+ }
1492
+ const type = event.attributes["exception.type"];
1493
+ const message = event.attributes["exception.message"];
1494
+ const stacktrace = event.attributes["exception.stacktrace"];
1084
1495
  return {
1085
- core: _coreState,
1086
- auth: _authState,
1087
- otel: _otelState
1496
+ type: typeof type === "string" ? type : void 0,
1497
+ message: typeof message === "string" ? message : void 0,
1498
+ stacktrace: typeof stacktrace === "string" ? stacktrace : void 0
1088
1499
  };
1089
1500
  }
1090
- function onLifecycleEvent(event, listener) {
1091
- _emitter.on(event, listener);
1092
- }
1093
- function emitLifecycleEvent(event, payload) {
1094
- emitSafe(event, payload);
1501
+ function extractLeadingPath(raw) {
1502
+ if (!raw) return void 0;
1503
+ const trimmed = raw.trim();
1504
+ if (trimmed.length === 0) return void 0;
1505
+ if (trimmed.startsWith("/")) {
1506
+ const firstSpace = trimmed.indexOf(" ");
1507
+ return firstSpace === -1 ? trimmed : trimmed.slice(0, firstSpace);
1508
+ }
1509
+ for (const token of trimmed.split(/\s+/)) {
1510
+ if (token.startsWith("/")) {
1511
+ return token;
1512
+ }
1513
+ }
1514
+ return void 0;
1095
1515
  }
1096
- function offLifecycleEvent(event, listener) {
1097
- _emitter.off(event, listener);
1516
+ function stripTrailingSlash(path3) {
1517
+ if (!path3) return path3;
1518
+ if (path3 === "/") return path3;
1519
+ return path3.endsWith("/") ? path3.slice(0, -1) : path3;
1098
1520
  }
1099
- function emitSafe(event, payload) {
1100
- const listeners = _emitter.listeners(event);
1101
- for (const listener of listeners) {
1521
+ function extractPathOnly(raw) {
1522
+ if (!raw) return void 0;
1523
+ const trimmed = raw.trim();
1524
+ if (trimmed.length === 0) return void 0;
1525
+ const isAbsoluteUrl = /^https?:\/\//i.test(trimmed);
1526
+ const isProtocolRelative = trimmed.startsWith("//");
1527
+ if (isAbsoluteUrl || isProtocolRelative) {
1102
1528
  try {
1103
- const result = listener(payload);
1104
- if (result && typeof result.catch === "function") {
1105
- result.catch((err) => {
1106
- _logger?.(
1107
- "error",
1108
- `[glasstrace] Async error in lifecycle event listener for "${event}": ${err instanceof Error ? err.message : String(err)}`
1109
- );
1110
- });
1529
+ const parsed = new URL(trimmed, "http://_/");
1530
+ if (parsed.pathname && parsed.pathname.startsWith("/")) {
1531
+ return parsed.pathname;
1111
1532
  }
1112
- } catch (err) {
1113
- _logger?.(
1114
- "error",
1115
- `[glasstrace] Error in lifecycle event listener for "${event}": ${err instanceof Error ? err.message : String(err)}`
1116
- );
1533
+ } catch {
1117
1534
  }
1118
1535
  }
1536
+ if (trimmed.startsWith("/")) {
1537
+ const queryIdx = trimmed.indexOf("?");
1538
+ const fragIdx = trimmed.indexOf("#");
1539
+ let cut = trimmed.length;
1540
+ if (queryIdx >= 0) cut = Math.min(cut, queryIdx);
1541
+ if (fragIdx >= 0) cut = Math.min(cut, fragIdx);
1542
+ return trimmed.slice(0, cut);
1543
+ }
1544
+ return void 0;
1119
1545
  }
1120
- function isReady() {
1121
- return _coreState === CoreState.ACTIVE || _coreState === CoreState.ACTIVE_DEGRADED;
1122
- }
1123
- function waitForReady(timeoutMs = 3e4) {
1124
- if (isReady()) {
1125
- return Promise.resolve();
1546
+ function deriveOrmProvider(instrumentationName) {
1547
+ const lower = instrumentationName.toLowerCase();
1548
+ if (lower.includes("prisma")) {
1549
+ return "prisma";
1126
1550
  }
1127
- if (_coreState === CoreState.PRODUCTION_DISABLED || _coreState === CoreState.REGISTRATION_FAILED || _coreState === CoreState.SHUTTING_DOWN || _coreState === CoreState.SHUTDOWN) {
1128
- return Promise.reject(new Error(`SDK is in terminal state: ${_coreState}`));
1551
+ if (lower.includes("drizzle")) {
1552
+ return "drizzle";
1129
1553
  }
1130
- return new Promise((resolve2, reject) => {
1131
- let settled = false;
1132
- const listener = ({ to }) => {
1133
- if (settled) return;
1134
- if (to === CoreState.ACTIVE || to === CoreState.ACTIVE_DEGRADED) {
1135
- settled = true;
1136
- offLifecycleEvent("core:state_changed", listener);
1137
- resolve2();
1138
- } else if (to === CoreState.PRODUCTION_DISABLED || to === CoreState.REGISTRATION_FAILED || to === CoreState.SHUTTING_DOWN || to === CoreState.SHUTDOWN) {
1139
- settled = true;
1140
- offLifecycleEvent("core:state_changed", listener);
1141
- reject(new Error(`SDK reached terminal state: ${to}`));
1142
- }
1143
- };
1144
- onLifecycleEvent("core:state_changed", listener);
1145
- if (timeoutMs > 0) {
1146
- const timer = setTimeout(() => {
1147
- if (settled) return;
1148
- settled = true;
1149
- offLifecycleEvent("core:state_changed", listener);
1150
- reject(new Error(`waitForReady timed out after ${timeoutMs}ms (state: ${_coreState})`));
1151
- }, timeoutMs);
1152
- if (typeof timer === "object" && "unref" in timer) {
1153
- timer.unref();
1154
- }
1155
- }
1156
- });
1554
+ return null;
1157
1555
  }
1158
- function getStatus() {
1159
- let mode;
1160
- if (_coreState === CoreState.PRODUCTION_DISABLED) {
1161
- mode = "disabled";
1162
- } else if (_authState === AuthState.CLAIMING || _authState === AuthState.CLAIMED) {
1163
- mode = "claiming";
1164
- } else if (_authState === AuthState.AUTHENTICATED) {
1165
- mode = "authenticated";
1166
- } else {
1167
- mode = "anonymous";
1556
+ function deriveErrorCategory(errorType) {
1557
+ const lower = errorType.toLowerCase();
1558
+ if (lower.includes("validation") || lower.includes("zod")) {
1559
+ return "validation";
1168
1560
  }
1169
- let tracing;
1170
- if (_otelState === OtelState.COEXISTENCE_FAILED || _otelState === OtelState.UNCONFIGURED || _otelState === OtelState.CONFIGURING) {
1171
- tracing = "not-configured";
1172
- } else if (_coreState === CoreState.ACTIVE_DEGRADED) {
1173
- tracing = "degraded";
1174
- } else if (_otelState === OtelState.AUTO_ATTACHED || _otelState === OtelState.PROCESSOR_PRESENT) {
1175
- tracing = "coexistence";
1176
- } else {
1177
- tracing = "active";
1561
+ if (lower.includes("network") || lower.includes("econnrefused") || lower.includes("fetch") || lower.includes("timeout")) {
1562
+ return "network";
1178
1563
  }
1179
- return {
1180
- ready: isReady(),
1181
- mode,
1182
- tracing
1183
- };
1184
- }
1185
- var _shutdownHooks = [];
1186
- var _signalHandlersRegistered = false;
1187
- var _signalHandler = null;
1188
- var _beforeExitRegistered = false;
1189
- var _beforeExitHandler = null;
1190
- var _shutdownExecuted = false;
1191
- function registerShutdownHook(hook) {
1192
- _shutdownHooks.push(hook);
1193
- _shutdownHooks.sort((a, b) => a.priority - b.priority);
1194
- }
1195
- async function executeShutdown(timeoutMs = 5e3) {
1196
- if (_shutdownExecuted) return;
1197
- _shutdownExecuted = true;
1198
- setCoreState(CoreState.SHUTTING_DOWN);
1199
- for (const hook of _shutdownHooks) {
1200
- try {
1201
- const hookPromise = hook.fn();
1202
- hookPromise.catch(() => {
1203
- });
1204
- await Promise.race([
1205
- hookPromise,
1206
- new Promise((_, reject) => {
1207
- const timer = setTimeout(() => reject(new Error(`Shutdown hook "${hook.name}" timed out`)), timeoutMs);
1208
- if (typeof timer === "object" && "unref" in timer) {
1209
- timer.unref();
1210
- }
1211
- })
1212
- ]);
1213
- } catch (err) {
1214
- _logger?.(
1215
- "warn",
1216
- `[glasstrace] Shutdown hook "${hook.name}" failed: ${err instanceof Error ? err.message : String(err)}`
1217
- );
1218
- }
1564
+ if (lower.includes("auth") || lower.includes("unauthorized") || lower.includes("forbidden")) {
1565
+ return "auth";
1219
1566
  }
1220
- setCoreState(CoreState.SHUTDOWN);
1221
- }
1222
- function registerSignalHandlers() {
1223
- if (_signalHandlersRegistered) return;
1224
- if (typeof process === "undefined" || typeof process.once !== "function") return;
1225
- _signalHandlersRegistered = true;
1226
- const otherSigtermListeners = process.listenerCount("SIGTERM");
1227
- const otherSigintListeners = process.listenerCount("SIGINT");
1228
- const handler = (signal) => {
1229
- void executeShutdown().finally(() => {
1230
- if (_signalHandler) {
1231
- process.removeListener("SIGTERM", _signalHandler);
1232
- process.removeListener("SIGINT", _signalHandler);
1233
- }
1234
- const otherListeners = signal === "SIGTERM" ? otherSigtermListeners : otherSigintListeners;
1235
- const otherProviderOwnsSignal = getCoexistenceState() === "coexisting" && otherListeners > 0;
1236
- if (!otherProviderOwnsSignal) {
1237
- process.kill(process.pid, signal);
1238
- }
1239
- });
1240
- };
1241
- _signalHandler = handler;
1242
- process.once("SIGTERM", handler);
1243
- process.once("SIGINT", handler);
1244
- }
1245
- function registerBeforeExitTrigger() {
1246
- if (_beforeExitRegistered) return;
1247
- if (typeof process === "undefined" || typeof process.once !== "function") return;
1248
- _beforeExitRegistered = true;
1249
- const handler = () => {
1250
- void executeShutdown();
1251
- };
1252
- _beforeExitHandler = handler;
1253
- process.once("beforeExit", handler);
1567
+ if (lower.includes("notfound") || lower.includes("not_found")) {
1568
+ return "not-found";
1569
+ }
1570
+ return "internal";
1254
1571
  }
1255
1572
 
1256
1573
  // ../../node_modules/@opentelemetry/core/build/esm/trace/suppress-tracing.js
@@ -3852,6 +4169,31 @@ var OTLPTraceExporter = class extends OTLPExporterBase {
3852
4169
  }
3853
4170
  };
3854
4171
 
4172
+ // src/api-key-hash.ts
4173
+ var cryptoModule;
4174
+ function loadCrypto() {
4175
+ if (cryptoModule !== void 0) return cryptoModule;
4176
+ try {
4177
+ cryptoModule = __require("node:crypto");
4178
+ } catch {
4179
+ cryptoModule = null;
4180
+ }
4181
+ return cryptoModule;
4182
+ }
4183
+ function hashApiKey(key) {
4184
+ if (typeof key !== "string" || key.length === 0) return "";
4185
+ const crypto = loadCrypto();
4186
+ if (crypto !== null) {
4187
+ return crypto.createHash("sha256").update(key, "utf8").digest("hex").slice(0, 32);
4188
+ }
4189
+ let hash = 2166136261;
4190
+ for (let i = 0; i < key.length; i++) {
4191
+ hash ^= key.charCodeAt(i);
4192
+ hash = hash + ((hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)) >>> 0;
4193
+ }
4194
+ return `fnv:${hash.toString(16).padStart(8, "0")}`;
4195
+ }
4196
+
3855
4197
  // src/proxy-detection.ts
3856
4198
  function isProxyTracerProvider(value) {
3857
4199
  if (value === null || value === void 0 || typeof value !== "object") {
@@ -3878,8 +4220,15 @@ var resolvedApiKey = API_KEY_PENDING;
3878
4220
  var activeExporter = null;
3879
4221
  var additionalExporters = [];
3880
4222
  var injectedProcessor = null;
4223
+ var resolvedApiKeyHash = "";
3881
4224
  function setResolvedApiKey(key) {
4225
+ const newHash = hashApiKey(key);
4226
+ const isRotation = resolvedApiKeyHash !== "" && resolvedApiKeyHash !== newHash;
3882
4227
  resolvedApiKey = key;
4228
+ resolvedApiKeyHash = newHash;
4229
+ if (isRotation) {
4230
+ peekExportCircuitBreaker()?.resetForKeyRotation();
4231
+ }
3883
4232
  }
3884
4233
  function getResolvedApiKey() {
3885
4234
  return resolvedApiKey;
@@ -3971,10 +4320,7 @@ async function runCoexistencePath(existingProvider, config) {
3971
4320
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3972
4321
  providerClass: readProviderClass(existingProvider)
3973
4322
  });
3974
- const coreState = getCoreState();
3975
- if (coreState === CoreState.ACTIVE || coreState === CoreState.KEY_RESOLVED) {
3976
- setCoreState(CoreState.ACTIVE_DEGRADED);
3977
- }
4323
+ pushDegradationSource("otel-coexistence-failed");
3978
4324
  }
3979
4325
  function readProviderClass(tracerProvider) {
3980
4326
  try {
@@ -4404,6 +4750,21 @@ function startRuntimeStateWriter(options) {
4404
4750
  _lastError = { ...payload };
4405
4751
  debouncedWrite();
4406
4752
  });
4753
+ onLifecycleEvent("otel:circuit_opened", (payload) => {
4754
+ _lastError = {
4755
+ category: "export-circuit-open",
4756
+ message: payload.message,
4757
+ timestamp: payload.timestamp,
4758
+ exportCircuitCategory: payload.category
4759
+ };
4760
+ debouncedWrite();
4761
+ });
4762
+ onLifecycleEvent("otel:circuit_closed", () => {
4763
+ if (_lastError?.category === "export-circuit-open") {
4764
+ _lastError = void 0;
4765
+ debouncedWrite();
4766
+ }
4767
+ });
4407
4768
  onLifecycleEvent("auth:key_resolved", () => debouncedWrite());
4408
4769
  onLifecycleEvent("auth:claim_started", () => debouncedWrite());
4409
4770
  onLifecycleEvent("auth:claim_completed", () => debouncedWrite());
@@ -4616,11 +4977,11 @@ function registerGlasstrace(options) {
4616
4977
  setCoreState(CoreState.REGISTERING);
4617
4978
  maybeWarnStaleAgentInstructions({
4618
4979
  projectRoot: process.cwd(),
4619
- sdkVersion: "1.7.0"
4980
+ sdkVersion: "1.9.0"
4620
4981
  });
4621
4982
  startRuntimeStateWriter({
4622
4983
  projectRoot: process.cwd(),
4623
- sdkVersion: "1.7.0"
4984
+ sdkVersion: "1.9.0"
4624
4985
  });
4625
4986
  const config = resolveConfig(options);
4626
4987
  if (config.verbose) {
@@ -4787,8 +5148,8 @@ async function backgroundInit(config, anonKeyForInit, generation) {
4787
5148
  if (config.verbose) {
4788
5149
  console.info("[glasstrace] Background init firing.");
4789
5150
  }
4790
- const healthReport = collectHealthReport("1.7.0");
4791
- const initResult = await performInit(config, anonKeyForInit, "1.7.0", healthReport);
5151
+ const healthReport = collectHealthReport("1.9.0");
5152
+ const initResult = await performInit(config, anonKeyForInit, "1.9.0", healthReport);
4792
5153
  if (generation !== registrationGeneration) return;
4793
5154
  const currentState = getCoreState();
4794
5155
  if (currentState === CoreState.SHUTTING_DOWN || currentState === CoreState.SHUTDOWN) {
@@ -4811,7 +5172,7 @@ async function backgroundInit(config, anonKeyForInit, generation) {
4811
5172
  }
4812
5173
  maybeInstallConsoleCapture();
4813
5174
  if (didLastInitSucceed()) {
4814
- startHeartbeat(config, anonKeyForInit, "1.7.0", generation, (newApiKey, accountId) => {
5175
+ startHeartbeat(config, anonKeyForInit, "1.9.0", generation, (newApiKey, accountId) => {
4815
5176
  setAuthState(AuthState.CLAIMING);
4816
5177
  emitLifecycleEvent("auth:claim_started", { accountId });
4817
5178
  setResolvedApiKey(newApiKey);
@@ -5108,7 +5469,7 @@ async function handleSourceMapUpload(distDir) {
5108
5469
  );
5109
5470
  return;
5110
5471
  }
5111
- const { discoverSourceMapFiles, computeBuildHash, uploadSourceMaps } = await import("./source-map-uploader-XFUEVV7I.js");
5472
+ const { discoverSourceMapFiles, computeBuildHash, uploadSourceMaps } = await import("./source-map-uploader-MMJ2WCL4.js");
5112
5473
  const files = await discoverSourceMapFiles(distDir);
5113
5474
  if (files.length === 0) {
5114
5475
  console.info("[glasstrace] No source map files found. Skipping upload.");
@@ -5201,14 +5562,14 @@ export {
5201
5562
  getDateString,
5202
5563
  SessionManager,
5203
5564
  classifyFetchTarget,
5204
- GlasstraceExporter,
5205
5565
  isReady,
5206
5566
  waitForReady,
5207
5567
  getStatus,
5568
+ GlasstraceExporter,
5208
5569
  createGlasstraceSpanProcessor,
5209
5570
  registerGlasstrace,
5210
5571
  getDiscoveryHandler,
5211
5572
  withGlasstraceConfig,
5212
5573
  captureError
5213
5574
  };
5214
- //# sourceMappingURL=chunk-OWPA7GHV.js.map
5575
+ //# sourceMappingURL=chunk-XEPC4NFL.js.map