vedatrace 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var transports_index = require('./transports/index.cjs');
5
+ var transports_index = require('./index-osD28Hs0.cjs');
6
6
 
7
7
  class VedaTraceBatcher {
8
8
  constructor(transports, config, immediateFlush = false) {
@@ -19,6 +19,12 @@ class VedaTraceBatcher {
19
19
  pendingFlush = null;
20
20
  context;
21
21
  waitUntilFn;
22
+ /**
23
+ * Set when the ingestion endpoint rejects our credentials. Every subsequent
24
+ * batch would fail identically, so we stop rather than spend a request (and a
25
+ * retry storm) per flush for the life of the process. start() clears it.
26
+ */
27
+ halted = false;
22
28
  setContext(ctx) {
23
29
  this.context = ctx;
24
30
  }
@@ -26,6 +32,7 @@ class VedaTraceBatcher {
26
32
  return this.context;
27
33
  }
28
34
  add(log) {
35
+ if (this.halted) return;
29
36
  this.queue.push(log);
30
37
  if (this.context || this.waitUntilFn) {
31
38
  this.debouncedFlush();
@@ -47,23 +54,12 @@ class VedaTraceBatcher {
47
54
  this.flushQueued = true;
48
55
  queueMicrotask(() => {
49
56
  this.flushQueued = false;
50
- this.flush().catch((error) => {
51
- if (this.config.onError) {
52
- this.config.onError(
53
- error instanceof Error ? error : new Error(String(error))
54
- );
55
- } else {
56
- console.error(
57
- "[VedaTrace] Debounced flush error:",
58
- error instanceof Error ? error.message : String(error)
59
- );
60
- }
61
- });
57
+ this.flush().catch((error) => this.reportError(error));
62
58
  });
63
59
  }
64
60
  async flush() {
65
61
  if (this.isFlushing) {
66
- return this.pendingFlush ?? await Promise.resolve();
62
+ return this.pendingFlush ?? Promise.resolve();
67
63
  }
68
64
  if (this.queue.length === 0) {
69
65
  return Promise.resolve();
@@ -74,6 +70,10 @@ class VedaTraceBatcher {
74
70
  const flushPromise = this.sendWithRetry(logsToSend).finally(() => {
75
71
  this.isFlushing = false;
76
72
  this.pendingFlush = null;
73
+ }).then(() => {
74
+ if (this.queue.length > 0 && !this.halted) {
75
+ return this.flush();
76
+ }
77
77
  });
78
78
  this.pendingFlush = flushPromise;
79
79
  if (this.context && typeof this.context.waitUntil === "function") {
@@ -89,26 +89,47 @@ class VedaTraceBatcher {
89
89
  try {
90
90
  await transport.send(logs);
91
91
  } catch (error) {
92
- errors.push(error instanceof Error ? error : new Error(String(error)));
92
+ errors.push(error);
93
93
  }
94
94
  }
95
- if (errors.length > 0 && errors.length === this.transports.length) {
96
- if (attempt < this.config.maxRetries) {
97
- await this.delay(this.config.retryDelay * (attempt + 1));
98
- return this.sendWithRetry(logs, attempt + 1);
99
- }
100
- const combinedError = new Error(
101
- `Failed to send logs after ${this.config.maxRetries} retries: ${errors.map((e) => e.message).join(", ")}`
102
- );
103
- if (this.config.onError) {
104
- this.config.onError(combinedError);
105
- } else {
106
- console.error("[VedaTrace]", combinedError.message);
107
- }
95
+ if (errors.length === 0 || errors.length < this.transports.length) {
96
+ this.config.onSuccess?.();
97
+ return;
98
+ }
99
+ const fatalError = errors.find(transports_index.isFatal);
100
+ if (fatalError) {
101
+ this.halt(fatalError);
108
102
  return;
109
103
  }
110
- if (this.config.onSuccess) {
111
- this.config.onSuccess();
104
+ if (errors.some(transports_index.isRetryable) && attempt < this.config.maxRetries) {
105
+ const backoff = this.config.retryDelay * (attempt + 1);
106
+ await this.delay(transports_index.retryAfterMs(errors) ?? backoff);
107
+ return this.sendWithRetry(logs, attempt + 1);
108
+ }
109
+ const detail = errors.map(describeError).join(", ");
110
+ this.reportError(
111
+ new Error(
112
+ errors.some(transports_index.isRetryable) ? `Failed to send ${logs.length} logs after ${this.config.maxRetries} retries: ${detail}` : `Failed to send ${logs.length} logs: ${detail}`
113
+ )
114
+ );
115
+ }
116
+ /** Stop accepting logs after an unrecoverable authentication failure. */
117
+ halt(error) {
118
+ this.halted = true;
119
+ this.queue = [];
120
+ this.stop();
121
+ this.reportError(
122
+ new Error(
123
+ `VedaTrace disabled: ${describeError(error)}. Check your API key and the key's allowed origins, then call logger.start() to resume.`
124
+ )
125
+ );
126
+ }
127
+ reportError(error) {
128
+ const normalized = error instanceof Error ? error : new Error(String(error));
129
+ if (this.config.onError) {
130
+ this.config.onError(normalized);
131
+ } else {
132
+ console.error("[VedaTrace]", normalized.message);
112
133
  }
113
134
  }
114
135
  startFlushTimer() {
@@ -117,22 +138,11 @@ class VedaTraceBatcher {
117
138
  }
118
139
  this.flushTimer = setInterval(() => {
119
140
  if (this.queue.length > 0) {
120
- this.flush().catch((error) => {
121
- if (this.config.onError) {
122
- this.config.onError(
123
- error instanceof Error ? error : new Error(String(error))
124
- );
125
- } else {
126
- console.error(
127
- "[VedaTrace] Flush error:",
128
- error instanceof Error ? error.message : String(error)
129
- );
130
- }
131
- });
141
+ this.flush().catch((error) => this.reportError(error));
132
142
  }
133
143
  }, this.config.flushInterval);
134
144
  if (this.config.unrefTimer === true) {
135
- this.flushTimer.unref();
145
+ this.flushTimer.unref?.();
136
146
  }
137
147
  }
138
148
  stop() {
@@ -143,6 +153,7 @@ class VedaTraceBatcher {
143
153
  this.flushQueued = false;
144
154
  }
145
155
  start() {
156
+ this.halted = false;
146
157
  if (!this.flushTimer && !this.immediateFlush) {
147
158
  this.startFlushTimer();
148
159
  }
@@ -153,10 +164,17 @@ class VedaTraceBatcher {
153
164
  getQueueSize() {
154
165
  return this.queue.length;
155
166
  }
167
+ /** True once an auth failure has shut the batcher down. */
168
+ isHalted() {
169
+ return this.halted;
170
+ }
156
171
  setExecutionContext(ctx) {
157
172
  this.context = ctx;
158
173
  }
159
174
  }
175
+ function describeError(error) {
176
+ return error instanceof Error ? error.message : String(error);
177
+ }
160
178
 
161
179
  function detectRuntime() {
162
180
  if (typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers") {
@@ -199,7 +217,8 @@ function isBrowser() {
199
217
  return detectRuntime() === "browser";
200
218
  }
201
219
 
202
- const SDK_VERSION = process.env.npm_package_version ?? "0.0.0";
220
+ const SDK_VERSION = "0.3.1";
221
+
203
222
  class VedaTraceLogger {
204
223
  batcher = null;
205
224
  runtime;
@@ -352,24 +371,17 @@ class BrowserLifecycle {
352
371
  this.config = config;
353
372
  this.boundVisibilityHandler = this.handleVisibilityChange.bind(this);
354
373
  this.boundPageHideHandler = this.handlePageHide.bind(this);
355
- this.boundBeforeUnloadHandler = this.handleBeforeUnload.bind(this);
356
- this.boundUnloadHandler = this.handleUnload.bind(this);
357
374
  }
358
375
  boundVisibilityHandler;
359
376
  boundPageHideHandler;
360
- boundBeforeUnloadHandler;
361
- boundUnloadHandler;
362
377
  isAttached = false;
363
378
  pendingFlush = null;
364
379
  /** Start listening for browser lifecycle events */
365
380
  attach() {
366
381
  if (this.isAttached) return;
367
- if (typeof document !== "undefined") {
368
- document.addEventListener("visibilitychange", this.boundVisibilityHandler);
369
- window.addEventListener("pagehide", this.boundPageHideHandler);
370
- window.addEventListener("beforeunload", this.boundBeforeUnloadHandler);
371
- window.addEventListener("unload", this.boundUnloadHandler);
372
- }
382
+ if (typeof document === "undefined" || typeof window === "undefined") return;
383
+ document.addEventListener("visibilitychange", this.boundVisibilityHandler);
384
+ window.addEventListener("pagehide", this.boundPageHideHandler);
373
385
  this.isAttached = true;
374
386
  if (this.config.debug) {
375
387
  console.log("[VedaTrace] Browser lifecycle handlers attached");
@@ -378,21 +390,25 @@ class BrowserLifecycle {
378
390
  /** Stop listening for browser lifecycle events */
379
391
  detach() {
380
392
  if (!this.isAttached) return;
381
- if (typeof document !== "undefined") {
393
+ if (typeof document !== "undefined" && typeof window !== "undefined") {
382
394
  document.removeEventListener(
383
395
  "visibilitychange",
384
396
  this.boundVisibilityHandler
385
397
  );
386
398
  window.removeEventListener("pagehide", this.boundPageHideHandler);
387
- window.removeEventListener("beforeunload", this.boundBeforeUnloadHandler);
388
- window.removeEventListener("unload", this.boundUnloadHandler);
389
399
  }
390
400
  this.isAttached = false;
391
401
  if (this.config.debug) {
392
402
  console.log("[VedaTrace] Browser lifecycle handlers detached");
393
403
  }
394
404
  }
395
- /** Handle visibility change - flush when page becomes hidden */
405
+ /**
406
+ * Flush when the page becomes hidden.
407
+ *
408
+ * This is the one that actually matters: on mobile, a backgrounded tab is
409
+ * often discarded without ever firing pagehide, so "hidden" is the last
410
+ * reliable moment to ship what we have.
411
+ */
396
412
  handleVisibilityChange() {
397
413
  if (document.visibilityState === "hidden") {
398
414
  if (this.config.debug) {
@@ -401,7 +417,7 @@ class BrowserLifecycle {
401
417
  this.scheduleFlush();
402
418
  }
403
419
  }
404
- /** Handle pagehide event - primary flush handler for Safari */
420
+ /** Final flush as the page is torn down or frozen into the bfcache. */
405
421
  handlePageHide(event) {
406
422
  if (this.config.debug) {
407
423
  console.log(
@@ -409,46 +425,16 @@ class BrowserLifecycle {
409
425
  event.persisted ? "(cached)" : "(navigation)"
410
426
  );
411
427
  }
412
- if (event.persisted) {
413
- this.scheduleFlush();
414
- } else {
415
- this.finalFlush();
416
- }
417
- }
418
- /** Handle beforeunload - backup flush mechanism */
419
- handleBeforeUnload(event) {
420
- if (this.config.debug) {
421
- console.log("[VedaTrace] Before unload event");
422
- }
423
- this.finalFlush();
424
- }
425
- /** Handle unload - fallback for older browsers */
426
- handleUnload() {
427
- if (this.config.debug) {
428
- console.log("[VedaTrace] Unload event");
429
- }
430
- this.finalFlush();
428
+ this.scheduleFlush();
431
429
  }
432
- /** Schedule a debounced flush (for visibility change) */
430
+ /** Flush at most once at a time; keepalive carries it past the document. */
433
431
  scheduleFlush() {
434
432
  if (this.pendingFlush) return;
435
- this.pendingFlush = this.config.flush().finally(() => {
433
+ this.pendingFlush = this.config.flush().catch(() => {
434
+ }).finally(() => {
436
435
  this.pendingFlush = null;
437
436
  });
438
437
  }
439
- /**
440
- * Final flush using keepalive fetch
441
- * For sending logs after the page context is destroyed
442
- */
443
- finalFlush() {
444
- for (const transport of this.config.transports) {
445
- if (transport.name === "http" && "flush" in transport) {
446
- transport.flush?.();
447
- }
448
- }
449
- this.config.flush().catch(() => {
450
- });
451
- }
452
438
  /** Check if handlers are attached */
453
439
  isActive() {
454
440
  return this.isAttached;
@@ -529,6 +515,16 @@ function redactPii(value, mask) {
529
515
  return result;
530
516
  }
531
517
 
518
+ function resolveApiKey(config) {
519
+ if (config.apiKey) return config.apiKey;
520
+ try {
521
+ if (typeof process !== "undefined" && process.env) {
522
+ return process.env.VEDATRACE_API_KEY || void 0;
523
+ }
524
+ } catch {
525
+ }
526
+ return void 0;
527
+ }
532
528
  const RUNTIME_FLUSH_INTERVALS = {
533
529
  node: 3e3,
534
530
  bun: 3e3,
@@ -539,14 +535,15 @@ const RUNTIME_FLUSH_INTERVALS = {
539
535
  };
540
536
  function vedatrace(config = {}) {
541
537
  const runtime = detectRuntime();
542
- const logger = new VedaTraceLogger(config);
543
- if (config.apiKey && (!config.transports || config.transports.length === 0)) {
538
+ const apiKey = resolveApiKey(config);
539
+ const logger = new VedaTraceLogger(apiKey ? { ...config, apiKey } : config);
540
+ if (apiKey && (!config.transports || config.transports.length === 0)) {
544
541
  const isBrowserEnv = isBrowser();
545
542
  const isServerlessEnv = isServerless();
546
543
  const isLongRunningEnv = isLongRunning();
547
544
  const HttpTransport = isBrowserEnv ? transports_index.VedaTraceHttpTransportBrowser : transports_index.VedaTraceHttpTransport;
548
545
  const httpConfig = {
549
- apiKey: config.apiKey,
546
+ apiKey,
550
547
  keepalive: isBrowserEnv || isServerlessEnv
551
548
  };
552
549
  if (config.endpoint) httpConfig.endpoint = config.endpoint;
@@ -616,7 +613,9 @@ function devVedatrace(config = {}) {
616
613
  exports.VedaTraceConsoleTransport = transports_index.VedaTraceConsoleTransport;
617
614
  exports.VedaTraceHttpTransport = transports_index.VedaTraceHttpTransport;
618
615
  exports.VedaTraceHttpTransportBrowser = transports_index.VedaTraceHttpTransportBrowser;
616
+ exports.VedaTraceTransportError = transports_index.VedaTraceTransportError;
619
617
  exports.BrowserLifecycle = BrowserLifecycle;
618
+ exports.SDK_VERSION = SDK_VERSION;
620
619
  exports.VedaTraceBatcher = VedaTraceBatcher;
621
620
  exports.VedaTraceLogger = VedaTraceLogger;
622
621
  exports.default = vedatrace;
package/dist/index.d.cts CHANGED
@@ -22,6 +22,12 @@ declare class VedaTraceBatcher {
22
22
  private pendingFlush;
23
23
  private context;
24
24
  private waitUntilFn;
25
+ /**
26
+ * Set when the ingestion endpoint rejects our credentials. Every subsequent
27
+ * batch would fail identically, so we stop rather than spend a request (and a
28
+ * retry storm) per flush for the life of the process. start() clears it.
29
+ */
30
+ private halted;
25
31
  constructor(transports: VedaTraceTransport[], config: BatcherConfig, immediateFlush?: boolean);
26
32
  setContext(ctx: VedaTraceEdgeContext): void;
27
33
  getContext(): VedaTraceEdgeContext | undefined;
@@ -29,14 +35,45 @@ declare class VedaTraceBatcher {
29
35
  private debouncedFlush;
30
36
  flush(): Promise<void>;
31
37
  private sendWithRetry;
38
+ /** Stop accepting logs after an unrecoverable authentication failure. */
39
+ private halt;
40
+ private reportError;
32
41
  private startFlushTimer;
33
42
  stop(): void;
34
43
  start(): void;
35
44
  private delay;
36
45
  getQueueSize(): number;
46
+ /** True once an auth failure has shut the batcher down. */
47
+ isHalted(): boolean;
37
48
  setExecutionContext(ctx: VedaTraceEdgeContext): void;
38
49
  }
39
50
 
51
+ /**
52
+ * Transport error types
53
+ *
54
+ * The batcher needs to tell three cases apart:
55
+ * - retryable (network blip, timeout, 429, 5xx) — back off and try again
56
+ * - permanent (400 malformed batch) — retrying wastes the caller's time
57
+ * - fatal (401/403 bad key or blocked origin) — every future batch will
58
+ * fail the same way, so stop instead of burning a request per flush
59
+ */
60
+ declare class VedaTraceTransportError extends Error {
61
+ readonly name = "VedaTraceTransportError";
62
+ readonly status: number | undefined;
63
+ readonly retryable: boolean;
64
+ readonly fatal: boolean;
65
+ /** Server-requested backoff from a `Retry-After` header, in milliseconds. */
66
+ readonly retryAfterMs: number | undefined;
67
+ constructor(message: string, options?: {
68
+ status?: number;
69
+ retryable?: boolean;
70
+ fatal?: boolean;
71
+ retryAfterMs?: number;
72
+ });
73
+ /** Build an error from an HTTP response status. */
74
+ static fromStatus(status: number, body: string, retryAfterMs?: number): VedaTraceTransportError;
75
+ }
76
+
40
77
  /**
41
78
  * Core VedaTrace Logger implementation
42
79
  * Supports Cloudflare Workers / Pages via withContext() integration
@@ -73,12 +110,17 @@ declare class VedaTraceLogger implements VedaTraceLoggerInterface {
73
110
  * Browser lifecycle management for VedaTrace
74
111
  *
75
112
  * Handles:
76
- * - visibilitychange: flush when page becomes hidden
77
- * - pagehide: final flush when user leaves the page
78
- * - beforeunload: backup flush before page unload
113
+ * - visibilitychange: flush when the page becomes hidden
114
+ * - pagehide: final flush when the page goes away
79
115
  *
80
- * Uses fetch with keepalive: true for final flush to ensure
81
- * the request completes after the tab is closed.
116
+ * `beforeunload` and `unload` are deliberately not used. Registering either one
117
+ * makes Chrome and Safari ineligible for the back/forward cache, so a logging
118
+ * SDK that listens for them measurably slows down every back-navigation in the
119
+ * host app. `visibilitychange` + `pagehide` covers every case they did,
120
+ * including Safari, and is the pair the browsers themselves recommend.
121
+ *
122
+ * The final flush goes out through the batcher's normal HTTP transport, which
123
+ * sets `keepalive: true` in the browser so the request outlives the document.
82
124
  */
83
125
 
84
126
  interface BrowserLifecycleConfig {
@@ -94,8 +136,6 @@ declare class BrowserLifecycle {
94
136
  private config;
95
137
  private boundVisibilityHandler;
96
138
  private boundPageHideHandler;
97
- private boundBeforeUnloadHandler;
98
- private boundUnloadHandler;
99
139
  private isAttached;
100
140
  private pendingFlush;
101
141
  constructor(config: BrowserLifecycleConfig);
@@ -103,21 +143,18 @@ declare class BrowserLifecycle {
103
143
  attach(): void;
104
144
  /** Stop listening for browser lifecycle events */
105
145
  detach(): void;
106
- /** Handle visibility change - flush when page becomes hidden */
146
+ /**
147
+ * Flush when the page becomes hidden.
148
+ *
149
+ * This is the one that actually matters: on mobile, a backgrounded tab is
150
+ * often discarded without ever firing pagehide, so "hidden" is the last
151
+ * reliable moment to ship what we have.
152
+ */
107
153
  private handleVisibilityChange;
108
- /** Handle pagehide event - primary flush handler for Safari */
154
+ /** Final flush as the page is torn down or frozen into the bfcache. */
109
155
  private handlePageHide;
110
- /** Handle beforeunload - backup flush mechanism */
111
- private handleBeforeUnload;
112
- /** Handle unload - fallback for older browsers */
113
- private handleUnload;
114
- /** Schedule a debounced flush (for visibility change) */
156
+ /** Flush at most once at a time; keepalive carries it past the document. */
115
157
  private scheduleFlush;
116
- /**
117
- * Final flush using keepalive fetch
118
- * For sending logs after the page context is destroyed
119
- */
120
- private finalFlush;
121
158
  /** Check if handlers are attached */
122
159
  isActive(): boolean;
123
160
  }
@@ -142,6 +179,21 @@ declare function isServerless(): boolean;
142
179
  declare function isLongRunning(): boolean;
143
180
  declare function isBrowser(): boolean;
144
181
 
182
+ /**
183
+ * SDK version, inlined at build time.
184
+ *
185
+ * This file is generated by `scripts/sync-version.mjs` from package.json and is
186
+ * checked in so type-checking and tests work without a build step. Do not edit
187
+ * it by hand — `bun run build` and `changeset version` both regenerate it, and
188
+ * a test asserts it matches package.json.
189
+ *
190
+ * It exists because the previous `process.env.npm_package_version` read was
191
+ * wrong twice over: npm only sets that variable for its own lifecycle scripts,
192
+ * so it was always undefined for consumers, and the bare `process` reference
193
+ * survived bundling and threw a ReferenceError in browsers.
194
+ */
195
+ declare const SDK_VERSION = "0.3.1";
196
+
145
197
  /**
146
198
  * VedaTrace SDK - Universal JavaScript logging
147
199
  *
@@ -182,4 +234,4 @@ declare function vedatrace(config?: VedaTraceConfig): VedaTraceLoggerInterface;
182
234
  */
183
235
  declare function devVedatrace(config?: Omit<VedaTraceConfig, "apiKey" | "transports">): VedaTraceLoggerInterface;
184
236
 
185
- export { BatcherConfig, BrowserLifecycle, InternalLogEntry, LogMetadata, RedactionConfig, RuntimeType$1 as RuntimeType, VedaTraceBatcher, VedaTraceConfig, VedaTraceEdgeContext, VedaTraceLogger, VedaTraceLoggerInterface, VedaTraceTransport, vedatrace as default, detectRuntime, devVedatrace, isBrowser, isEdgeRuntime, isLongRunning, isServerless, redact, vedatrace };
237
+ export { BatcherConfig, BrowserLifecycle, InternalLogEntry, LogMetadata, RedactionConfig, RuntimeType$1 as RuntimeType, SDK_VERSION, VedaTraceBatcher, VedaTraceConfig, VedaTraceEdgeContext, VedaTraceLogger, VedaTraceLoggerInterface, VedaTraceTransport, VedaTraceTransportError, vedatrace as default, detectRuntime, devVedatrace, isBrowser, isEdgeRuntime, isLongRunning, isServerless, redact, vedatrace };
package/dist/index.d.mts CHANGED
@@ -22,6 +22,12 @@ declare class VedaTraceBatcher {
22
22
  private pendingFlush;
23
23
  private context;
24
24
  private waitUntilFn;
25
+ /**
26
+ * Set when the ingestion endpoint rejects our credentials. Every subsequent
27
+ * batch would fail identically, so we stop rather than spend a request (and a
28
+ * retry storm) per flush for the life of the process. start() clears it.
29
+ */
30
+ private halted;
25
31
  constructor(transports: VedaTraceTransport[], config: BatcherConfig, immediateFlush?: boolean);
26
32
  setContext(ctx: VedaTraceEdgeContext): void;
27
33
  getContext(): VedaTraceEdgeContext | undefined;
@@ -29,14 +35,45 @@ declare class VedaTraceBatcher {
29
35
  private debouncedFlush;
30
36
  flush(): Promise<void>;
31
37
  private sendWithRetry;
38
+ /** Stop accepting logs after an unrecoverable authentication failure. */
39
+ private halt;
40
+ private reportError;
32
41
  private startFlushTimer;
33
42
  stop(): void;
34
43
  start(): void;
35
44
  private delay;
36
45
  getQueueSize(): number;
46
+ /** True once an auth failure has shut the batcher down. */
47
+ isHalted(): boolean;
37
48
  setExecutionContext(ctx: VedaTraceEdgeContext): void;
38
49
  }
39
50
 
51
+ /**
52
+ * Transport error types
53
+ *
54
+ * The batcher needs to tell three cases apart:
55
+ * - retryable (network blip, timeout, 429, 5xx) — back off and try again
56
+ * - permanent (400 malformed batch) — retrying wastes the caller's time
57
+ * - fatal (401/403 bad key or blocked origin) — every future batch will
58
+ * fail the same way, so stop instead of burning a request per flush
59
+ */
60
+ declare class VedaTraceTransportError extends Error {
61
+ readonly name = "VedaTraceTransportError";
62
+ readonly status: number | undefined;
63
+ readonly retryable: boolean;
64
+ readonly fatal: boolean;
65
+ /** Server-requested backoff from a `Retry-After` header, in milliseconds. */
66
+ readonly retryAfterMs: number | undefined;
67
+ constructor(message: string, options?: {
68
+ status?: number;
69
+ retryable?: boolean;
70
+ fatal?: boolean;
71
+ retryAfterMs?: number;
72
+ });
73
+ /** Build an error from an HTTP response status. */
74
+ static fromStatus(status: number, body: string, retryAfterMs?: number): VedaTraceTransportError;
75
+ }
76
+
40
77
  /**
41
78
  * Core VedaTrace Logger implementation
42
79
  * Supports Cloudflare Workers / Pages via withContext() integration
@@ -73,12 +110,17 @@ declare class VedaTraceLogger implements VedaTraceLoggerInterface {
73
110
  * Browser lifecycle management for VedaTrace
74
111
  *
75
112
  * Handles:
76
- * - visibilitychange: flush when page becomes hidden
77
- * - pagehide: final flush when user leaves the page
78
- * - beforeunload: backup flush before page unload
113
+ * - visibilitychange: flush when the page becomes hidden
114
+ * - pagehide: final flush when the page goes away
79
115
  *
80
- * Uses fetch with keepalive: true for final flush to ensure
81
- * the request completes after the tab is closed.
116
+ * `beforeunload` and `unload` are deliberately not used. Registering either one
117
+ * makes Chrome and Safari ineligible for the back/forward cache, so a logging
118
+ * SDK that listens for them measurably slows down every back-navigation in the
119
+ * host app. `visibilitychange` + `pagehide` covers every case they did,
120
+ * including Safari, and is the pair the browsers themselves recommend.
121
+ *
122
+ * The final flush goes out through the batcher's normal HTTP transport, which
123
+ * sets `keepalive: true` in the browser so the request outlives the document.
82
124
  */
83
125
 
84
126
  interface BrowserLifecycleConfig {
@@ -94,8 +136,6 @@ declare class BrowserLifecycle {
94
136
  private config;
95
137
  private boundVisibilityHandler;
96
138
  private boundPageHideHandler;
97
- private boundBeforeUnloadHandler;
98
- private boundUnloadHandler;
99
139
  private isAttached;
100
140
  private pendingFlush;
101
141
  constructor(config: BrowserLifecycleConfig);
@@ -103,21 +143,18 @@ declare class BrowserLifecycle {
103
143
  attach(): void;
104
144
  /** Stop listening for browser lifecycle events */
105
145
  detach(): void;
106
- /** Handle visibility change - flush when page becomes hidden */
146
+ /**
147
+ * Flush when the page becomes hidden.
148
+ *
149
+ * This is the one that actually matters: on mobile, a backgrounded tab is
150
+ * often discarded without ever firing pagehide, so "hidden" is the last
151
+ * reliable moment to ship what we have.
152
+ */
107
153
  private handleVisibilityChange;
108
- /** Handle pagehide event - primary flush handler for Safari */
154
+ /** Final flush as the page is torn down or frozen into the bfcache. */
109
155
  private handlePageHide;
110
- /** Handle beforeunload - backup flush mechanism */
111
- private handleBeforeUnload;
112
- /** Handle unload - fallback for older browsers */
113
- private handleUnload;
114
- /** Schedule a debounced flush (for visibility change) */
156
+ /** Flush at most once at a time; keepalive carries it past the document. */
115
157
  private scheduleFlush;
116
- /**
117
- * Final flush using keepalive fetch
118
- * For sending logs after the page context is destroyed
119
- */
120
- private finalFlush;
121
158
  /** Check if handlers are attached */
122
159
  isActive(): boolean;
123
160
  }
@@ -142,6 +179,21 @@ declare function isServerless(): boolean;
142
179
  declare function isLongRunning(): boolean;
143
180
  declare function isBrowser(): boolean;
144
181
 
182
+ /**
183
+ * SDK version, inlined at build time.
184
+ *
185
+ * This file is generated by `scripts/sync-version.mjs` from package.json and is
186
+ * checked in so type-checking and tests work without a build step. Do not edit
187
+ * it by hand — `bun run build` and `changeset version` both regenerate it, and
188
+ * a test asserts it matches package.json.
189
+ *
190
+ * It exists because the previous `process.env.npm_package_version` read was
191
+ * wrong twice over: npm only sets that variable for its own lifecycle scripts,
192
+ * so it was always undefined for consumers, and the bare `process` reference
193
+ * survived bundling and threw a ReferenceError in browsers.
194
+ */
195
+ declare const SDK_VERSION = "0.3.1";
196
+
145
197
  /**
146
198
  * VedaTrace SDK - Universal JavaScript logging
147
199
  *
@@ -182,4 +234,4 @@ declare function vedatrace(config?: VedaTraceConfig): VedaTraceLoggerInterface;
182
234
  */
183
235
  declare function devVedatrace(config?: Omit<VedaTraceConfig, "apiKey" | "transports">): VedaTraceLoggerInterface;
184
236
 
185
- export { BatcherConfig, BrowserLifecycle, InternalLogEntry, LogMetadata, RedactionConfig, RuntimeType$1 as RuntimeType, VedaTraceBatcher, VedaTraceConfig, VedaTraceEdgeContext, VedaTraceLogger, VedaTraceLoggerInterface, VedaTraceTransport, vedatrace as default, detectRuntime, devVedatrace, isBrowser, isEdgeRuntime, isLongRunning, isServerless, redact, vedatrace };
237
+ export { BatcherConfig, BrowserLifecycle, InternalLogEntry, LogMetadata, RedactionConfig, RuntimeType$1 as RuntimeType, SDK_VERSION, VedaTraceBatcher, VedaTraceConfig, VedaTraceEdgeContext, VedaTraceLogger, VedaTraceLoggerInterface, VedaTraceTransport, VedaTraceTransportError, vedatrace as default, detectRuntime, devVedatrace, isBrowser, isEdgeRuntime, isLongRunning, isServerless, redact, vedatrace };