@tracelog/lib 2.10.0-rc.113.17 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # TraceLog
2
2
 
3
- Lightweight web analytics library for tracking user behavior. Works standalone or with the TraceLog SaaS backend.
3
+ Lightweight web analytics library for tracking user behavior. Works standalone or with optional backend integrations.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Zero-config** Auto-captures clicks, scrolls, page views, sessions, performance metrics, and JavaScript errors
8
- - **Standalone-first** Works without any backend; opt in to TraceLog SaaS by passing a `projectId`
9
- - **Privacy-first** PII sanitization, client-side sampling, `data-tlog-ignore` attribute, sensitive URL param stripping
10
- - **Cross-tab sessions** BroadcastChannel sync with localStorage + sessionStorage recovery
11
- - **Event-driven** Subscribe via `on()` / `off()` for real-time consumption
12
- - **Lightweight** Single dependency (`web-vitals`), ~62KB gzipped
7
+ - **Zero-config** - Auto-captures clicks, scrolls, page views, sessions, and performance metrics
8
+ - **Standalone** - No backend required; optional integrations (TraceLog SaaS, custom API)
9
+ - **Privacy-first** - PII sanitization, client-side sampling, `data-tlog-ignore` attribute
10
+ - **Cross-tab sessions** - BroadcastChannel sync with localStorage recovery
11
+ - **Event-driven** - Subscribe via `on()`/`off()` for real-time events
12
+ - **Lightweight** - Single dependency (`web-vitals`), ~62KB gzipped
13
13
 
14
14
  ## Live Demo
15
15
 
@@ -20,7 +20,6 @@ Lightweight web analytics library for tracking user behavior. Works standalone o
20
20
  ## Installation
21
21
 
22
22
  ### NPM (Recommended)
23
-
24
23
  ```bash
25
24
  npm install @tracelog/lib
26
25
  ```
@@ -41,7 +40,6 @@ const { sessionId } = await tracelog.init({
41
40
  ```
42
41
 
43
42
  ### CDN (Script Tag)
44
-
45
43
  ```html
46
44
  <script src="https://cdn.jsdelivr.net/npm/@tracelog/lib@latest/dist/browser/tracelog.js"></script>
47
45
  <script>
@@ -50,7 +48,6 @@ const { sessionId } = await tracelog.init({
50
48
  ```
51
49
 
52
50
  ### CDN (ES Module)
53
-
54
51
  ```html
55
52
  <script type="module">
56
53
  import { tracelog } from 'https://cdn.jsdelivr.net/npm/@tracelog/lib@latest/dist/browser/tracelog.esm.js';
@@ -58,52 +55,70 @@ const { sessionId } = await tracelog.init({
58
55
  </script>
59
56
  ```
60
57
 
61
- ---
62
-
63
58
  ## Quick Start
64
59
 
65
60
  ### Initialization Order
66
61
 
67
- Set up listeners **before** calling `init()` so they receive the initial `SESSION_START` and `PAGE_VIEW` events that fire during init.
62
+ **Important:** Set up listeners and transformers **before** calling `init()` to capture all events from the start:
68
63
 
69
64
  ```typescript
70
- // 1. Obtain user consent first (your responsibility — see "User Consent" below)
71
- const hasConsent = await getUserConsent();
72
- if (!hasConsent) return;
65
+ // 1. Obtain user consent FIRST (your responsibility)
66
+ // See "User Consent Management" section below for implementation details
67
+ const hasConsent = await getUserConsent(); // Your consent management system
68
+
69
+ if (!hasConsent) {
70
+ console.log('User declined tracking');
71
+ return;
72
+ }
73
73
 
74
- // 2. Register listeners (before init)
74
+ // 2. Register event listeners SECOND (before init)
75
75
  tracelog.on('event', (event) => {
76
76
  console.log(event.type, event);
77
77
  });
78
78
 
79
- // 3. Identify the user (optional can be called before or after init)
80
- tracelog.identify('cust_123', { name: 'Maria Garcia', plan: 'pro' });
79
+ // 3. Configure transformers THIRD (if using custom backend)
80
+ tracelog.setTransformer('beforeSend', (event) => {
81
+ // Transform events before they're queued
82
+ return { ...event, custom_metadata: { app: 'v1' } };
83
+ });
81
84
 
82
- // 4. Initialize (starts tracking immediately)
83
- const { sessionId } = await tracelog.init({
85
+ // 4. Configure custom headers FOURTH (if using custom backend with auth)
86
+ tracelog.setCustomHeaders(() => ({
87
+ 'Authorization': `Bearer ${getAuthToken()}`
88
+ }));
89
+
90
+ // 5. Initialize FIFTH (starts tracking immediately)
91
+ await tracelog.init({
84
92
  integrations: {
85
- tracelog: { projectId: 'your-project-id' }
93
+ custom: { collectApiUrl: 'https://api.example.com' }
86
94
  }
87
95
  });
88
96
 
89
- // 5. Track custom events at any point after init
90
- tracelog.event('button_clicked', { buttonId: 'signup-cta', source: 'homepage' });
97
+ // 6. Identify user (after init, optional)
98
+ tracelog.identify('cust_123', { name: 'Maria Garcia', plan: 'pro' });
99
+
100
+ // 7. Track custom events (after init)
101
+ tracelog.event('button_clicked', {
102
+ buttonId: 'signup-cta',
103
+ source: 'homepage'
104
+ });
91
105
 
92
- // 6. On logout: reset identity (clears identity, regenerates UUID, opens a new session)
106
+ // 8. On logout: reset identity
93
107
  await tracelog.resetIdentity();
94
108
 
95
- // 7. Cleanup on consent revoke or app unmount
109
+ // 9. Cleanup (on consent revoke or app unmount)
96
110
  tracelog.destroy();
97
111
  ```
98
112
 
99
- **Auto-captured events** (no code required):
113
+ **Why this order?** You must obtain user consent before initializing. Events like `SESSION_START` and `PAGE_VIEW` fire during initialization. Registering listeners, transformers, and custom headers before init ensures you capture, transform, and send these initial events with proper authentication.
100
114
 
101
- - Page views & navigation (including SPA route changes)
115
+ **That's it!** TraceLog now automatically tracks:
116
+ - Page views & navigation (including SPA routes)
102
117
  - Click interactions
103
118
  - Scroll behavior
104
119
  - User sessions
105
120
  - Web Vitals (LCP, INP, CLS, FCP, TTFB)
106
- - JavaScript errors & unhandled promise rejections
121
+ - JavaScript errors
107
122
 
108
123
  ---
109
124
 
@@ -111,40 +126,53 @@ tracelog.destroy();
111
126
 
112
127
  | Method | Description |
113
128
  |--------|-------------|
114
- | `init(config?)` | Initialize tracking. Returns `Promise<{ sessionId }>`. See [Configuration](#configuration). |
115
- | `event(name, metadata?, options?)` | Track a custom event. `options.critical: true` drains the queue via `sendBeacon` right after tracking, so the batch (the critical event + anything already queued) survives an imminent navigation. Subject to `sendBeacon`'s 64KB cap — oversized batches are persisted to `localStorage` and recovered on next `init()` via their idempotency token; the backend deduplicates by `event.id`. |
116
- | `on(event, callback)` | Subscribe to events (`'event'` or `'queue'`). Local consumption independent of backend sends. |
117
- | `off(event, callback)` | Unsubscribe. Must pass the exact callback reference used in `on()`. |
118
- | `identify(userId, traits?)` | Associate the current visitor with a known user identity. `traits` accepts a `Record<string, string>`; non-string values are dropped silently. |
119
- | `resetIdentity()` | Flush pending events under the old identity, clear identity, regenerate the visitor UUID, and start a new session. Use for logout flows. |
120
- | `isInitialized()` | `true` after a successful `init()`, `false` otherwise (including during teardown). |
121
- | `getSessionId()` | Current session ID, or `null` if not initialized. |
122
- | `getUserId()` | Current visitor UUID, or `null` if not initialized. |
123
- | `destroy()` | Stop tracking, drain pending events via `sendBeacon`, and release all resources. |
129
+ | `init(config?)` | Initialize tracking (see [Configuration](#configuration)) |
130
+ | `event(name, metadata?, options?)` | Track custom events. `options.critical: true` drains the queue via `sendBeacon` right after tracking, so the batch (the critical event + anything already queued) survives an imminent navigation. Subject to `sendBeacon`'s 64KB cap — oversized batches are persisted to `localStorage` and recovered on next `init()` via their idempotency token; the backend deduplicates by `event.id`. See [API_REFERENCE.md](./API_REFERENCE.md#eventname-metadata-options) for the full contract. |
131
+ | `flushImmediately()` | Force an async `fetch` flush of all pending events. Returns `Promise<boolean>`. |
132
+ | `flushImmediatelySync()` | Force a `sendBeacon` flush. Use for custom unload handlers; the library already wires this to `pagehide`/`beforeunload`/`visibilitychange`. |
133
+ | `updateGlobalMetadata(metadata)` | Replace all global metadata |
134
+ | `mergeGlobalMetadata(metadata)` | Merge with existing global metadata |
135
+ | `on(event, callback)` | Subscribe to events (`'event'` or `'queue'`) |
136
+ | `off(event, callback)` | Unsubscribe from events |
137
+ | `setTransformer(hook, fn)` | Transform events before sending (see [Transformers](#transformers)) |
138
+ | `removeTransformer(hook)` | Remove a previously set transformer |
139
+ | `setCustomHeaders(provider)` | Add custom HTTP headers to requests (see [Custom Headers](#custom-headers)) |
140
+ | `removeCustomHeaders()` | Remove custom headers provider |
141
+ | `identify(userId, traits?)` | Associate visitor with a known user identity |
142
+ | `resetIdentity()` | Clear identity, regenerate UUID, start new session |
143
+ | `isInitialized()` | Check initialization status |
144
+ | `getSessionId()` | Get current session ID (or null) |
145
+ | `setQaMode(enabled)` | Enable/disable QA mode (console logging) |
146
+ | `destroy()` | Stop tracking and cleanup |
124
147
 
125
148
  **→ [Complete API Reference](./API_REFERENCE.md)**
126
149
 
127
150
  ---
128
151
 
129
- ## User Consent
152
+ ## User Consent Management
130
153
 
131
- TraceLog does not ship a consent manager. You are responsible for obtaining consent **before** calling `init()`.
154
+ TraceLog requires you to obtain user consent **before** calling `init()`. The library does not include a built-in consent management system.
132
155
 
133
156
  ```typescript
157
+ // Your responsibility: Obtain consent before initialization
134
158
  const userConsent = await showCookieBanner(); // Your consent solution
135
159
 
136
160
  if (userConsent.analytics) {
161
+ // Initialize only after consent
137
162
  await tracelog.init({
138
- integrations: { tracelog: { projectId: 'your-project-id' } }
163
+ integrations: {
164
+ tracelog: { projectId: 'your-project-id' }
165
+ }
139
166
  });
140
167
  } else {
141
- // User rejected don't initialize
168
+ // User rejected - don't initialize
169
+ console.log('Analytics consent denied');
142
170
  }
143
171
 
144
- // If consent is revoked later
172
+ // If user revokes consent later
145
173
  function handleConsentRevoke() {
146
- tracelog.destroy();
147
- localStorage.clear();
174
+ tracelog.destroy(); // Stop tracking immediately
175
+ localStorage.clear(); // Clear stored session data
148
176
  }
149
177
  ```
150
178
 
@@ -152,76 +180,140 @@ function handleConsentRevoke() {
152
180
 
153
181
  ## Configuration
154
182
 
155
- All configuration is optional. TraceLog works out-of-the-box with sensible defaults.
183
+ All configuration is **optional**. TraceLog works out-of-the-box with sensible defaults.
156
184
 
157
185
  ```typescript
158
186
  await tracelog.init({
159
187
  // Session
160
- sessionTimeout: 900000, // 15 min (default)
188
+ sessionTimeout: 900000, // 15 min (default)
161
189
 
162
190
  // Privacy
163
- samplingRate: 1.0, // 100% (default)
164
- errorSampling: 1.0, // 100% (default)
165
- sensitiveQueryParams: ['token'], // Added to the 15-param default deny-list
166
-
167
- // Throttles
168
- pageViewThrottleMs: 1000, // Min interval between page_view events
169
- clickThrottleMs: 300, // Min interval between click events per element
170
- maxSameEventPerMinute: 60, // Per-name custom-event rate cap
191
+ samplingRate: 1.0, // 100% (default)
192
+ sensitiveQueryParams: ['token'], // Add to defaults
171
193
 
172
- // Batch flush
173
- sendIntervalMs: 10000, // Default batch interval
174
- flushOnSpaNavigation: false, // Opt-in: flush after pushState / replaceState / popstate / hashchange
175
- flushOnPageHidden: true, // Flush when document.hidden becomes true (mobile Safari coverage)
194
+ // Flush behavior (defaults shown)
195
+ flushOnSpaNavigation: false, // Opt-in: flush after pushState/replaceState/popstate/hashchange (default false; per-route flushing multiplies request volume on SPAs)
196
+ flushOnPageHidden: true, // Flush when document.hidden becomes true (mobile Safari coverage)
176
197
 
177
- // Web Vitals
178
- webVitalsMode: 'needs-improvement', // 'all' | 'needs-improvement' | 'poor'
179
- webVitalsThresholds: { LCP: 2500 }, // Optional per-metric overrides
198
+ // Integrations (pick one, multiple, or none)
199
+ integrations: {
200
+ tracelog: { projectId: 'your-id' }, // TraceLog SaaS
201
+ custom: { collectApiUrl: 'https://api.com' }, // Custom backend
180
202
 
181
- // Global metadata appended to every event
182
- globalMetadata: {
183
- env: 'production',
184
- version: '1.2.0',
185
- appName: 'MyApp'
203
+ // Multi-integration: Send to multiple backends simultaneously
204
+ // tracelog: { projectId: 'proj-123' }, // Analytics dashboard
205
+ // custom: { collectApiUrl: 'https://warehouse.com' } // Data warehouse
206
+ // Events sent to BOTH independently with separate error handling
186
207
  },
187
208
 
188
- // Integration (omit for standalone mode)
189
- integrations: {
190
- tracelog: {
191
- projectId: 'your-project-id',
192
- shopify: false // Optional: enable Shopify cart attribute linking
193
- }
209
+ // Web Vitals filtering
210
+ webVitalsMode: 'needs-improvement', // 'all' | 'needs-improvement' | 'poor'
211
+
212
+ // Viewport tracking (element visibility)
213
+ viewport: {
214
+ elements: [{ selector: '.cta', id: 'hero-cta' }],
215
+ threshold: 0.5, // 50% visible
216
+ minDwellTime: 1000 // 1 second
194
217
  }
195
218
  });
196
219
  ```
197
220
 
198
- **→ [Full Configuration Reference](./API_REFERENCE.md#configuration)**
221
+ **→ [Full Configuration Guide](./API_REFERENCE.md#configuration)**
199
222
 
200
223
  ---
201
224
 
202
225
  ## Automatic Event Types
203
226
 
204
- | Event Type | What It Tracks |
205
- |-----------------|-------------------------------------------------|
206
- | `page_view` | Initial load, SPA route changes, hash changes |
207
- | `click` | User interactions with elements |
208
- | `session_start` | New session creation (server infers session end)|
209
- | `scroll` | Depth and direction per scrollable container |
210
- | `web_vitals` | Core Web Vitals (LCP, INP, CLS, FCP, TTFB) |
211
- | `error` | JavaScript errors, unhandled promise rejections |
227
+ TraceLog captures these events automatically (no code required):
228
+
229
+ | Event Type | What It Tracks |
230
+ |-------------------|---------------------------------------------|
231
+ | `page_view` | Navigation, SPA route changes |
232
+ | `click` | User interactions with elements |
233
+ | `session_start` | New session creation |
234
+ | `scroll` | Scroll depth, velocity, engagement |
235
+ | `web_vitals` | Core Web Vitals (LCP, INP, CLS, FCP, TTFB) |
236
+ | `error` | JavaScript errors, promise rejections |
237
+ | `viewport_visible`| Element visibility (requires `viewport` config) |
238
+
239
+ **Filtering Events:**
240
+
241
+ You can filter specific events before they're sent to your backend using the `beforeSend` transformer. This gives you complete control over what data is transmitted.
242
+
243
+ ```typescript
244
+ // Filter out high-volume events (scroll, web_vitals)
245
+ tracelog.setTransformer('beforeSend', (event) => {
246
+ // Skip scroll and web vitals events
247
+ if (['scroll', 'web_vitals'].includes(event.type)) {
248
+ return null; // Returning null excludes the event from being sent
249
+ }
250
+ return event; // Send all other events normally
251
+ });
252
+
253
+ await tracelog.init({
254
+ integrations: {
255
+ custom: { collectApiUrl: 'https://api.example.com' }
256
+ }
257
+ });
258
+ ```
259
+
260
+ **Advanced Filtering:**
261
+
262
+ ```typescript
263
+ // Conditional filtering based on custom logic
264
+ tracelog.setTransformer('beforeSend', (event) => {
265
+ // Only send errors in production
266
+ if (event.type === 'error' && process.env.NODE_ENV !== 'production') {
267
+ return null;
268
+ }
269
+
270
+ // Only send 10% of scroll events (sampling)
271
+ if (event.type === 'scroll' && Math.random() > 0.1) {
272
+ return null;
273
+ }
274
+
275
+ return event;
276
+ });
277
+ ```
278
+
279
+ **Multi-Integration Behavior:**
280
+
281
+ ```typescript
282
+ // Transformers ONLY apply to custom backends
283
+ // TraceLog SaaS always receives all events unmodified
284
+ tracelog.setTransformer('beforeSend', (event) => {
285
+ if (['scroll', 'web_vitals'].includes(event.type)) {
286
+ return null; // Filtered from custom backend only
287
+ }
288
+ return event;
289
+ });
290
+
291
+ await tracelog.init({
292
+ integrations: {
293
+ tracelog: { projectId: 'proj-123' }, // Gets ALL events (unfiltered)
294
+ custom: { collectApiUrl: 'https://warehouse.com' } // Gets filtered events
295
+ }
296
+ });
297
+ ```
298
+
299
+ **Important:** Transformers (`beforeSend`, `beforeBatch`) only apply to **custom backend integrations**. TraceLog SaaS always receives all events unmodified to maintain schema integrity and ensure complete analytics. This behavior is the same as the removed `disabledEvents` configuration.
212
300
 
213
- **Custom events:**
301
+ **Use Cases:**
302
+ - Reduce bandwidth and backend costs for custom backends
303
+ - Already using Sentry/Datadog for errors (filter out `error` events from custom backend)
304
+ - Data warehouse doesn't need scroll/vitals granularity
305
+ - Minimize custom backend data volume for privacy compliance
306
+ - Custom sampling logic per event type
214
307
 
308
+ **Note:** Filtered events are still captured locally. Use `tracelog.on('event')` to access all events client-side, even those excluded from backend transmission.
309
+
310
+ **Custom Events:**
215
311
  ```typescript
216
312
  tracelog.event('purchase_completed', {
217
313
  orderId: 'ord-123',
218
314
  total: 99.99,
219
315
  currency: 'USD'
220
316
  });
221
-
222
- // Right before a navigation — guarantee delivery via sendBeacon
223
- tracelog.event('purchase_completed', { orderId: 'ord-123' }, { critical: true });
224
- window.location.href = '/thanks';
225
317
  ```
226
318
 
227
319
  **→ [Event Types Reference](./API_REFERENCE.md#event-types)**
@@ -230,37 +322,441 @@ window.location.href = '/thanks';
230
322
 
231
323
  ## Global Metadata
232
324
 
233
- Set at `init()` time and attached to every event the library sends. Replace it by destroying and re-initializing TraceLog with a new config (typical at login / logout).
325
+ Global metadata is automatically attached to **every event** sent to your backend, making it ideal for user context, environment info, or app-wide properties.
326
+
327
+ ### Setting Initial Metadata
328
+
329
+ Configure global metadata during initialization:
234
330
 
235
331
  ```typescript
236
332
  await tracelog.init({
237
333
  globalMetadata: {
238
334
  env: 'production',
239
335
  version: '1.2.0',
240
- plan: user?.plan ?? 'anonymous'
336
+ appName: 'MyApp'
241
337
  }
242
338
  });
243
339
  ```
244
340
 
245
- **Validation rules:**
341
+ ### Updating Metadata at Runtime
342
+
343
+ **Replace all metadata** (previous keys removed):
344
+
345
+ ```typescript
346
+ // User login: Replace with user context
347
+ tracelog.updateGlobalMetadata({
348
+ userId: 'user-456',
349
+ plan: 'premium',
350
+ cohort: 'beta-testers'
351
+ });
352
+
353
+ // User logout: Clear all metadata
354
+ tracelog.updateGlobalMetadata({});
355
+ ```
356
+
357
+ **Merge with existing metadata** (preserves other keys):
358
+
359
+ ```typescript
360
+ // Add user ID while preserving env and version
361
+ tracelog.mergeGlobalMetadata({ userId: 'user-123' });
362
+
363
+ // Update version while preserving others
364
+ tracelog.mergeGlobalMetadata({ version: '1.3.0' });
365
+
366
+ // Add feature flags
367
+ tracelog.mergeGlobalMetadata({
368
+ feature_new_ui: true,
369
+ feature_dark_mode: false
370
+ });
371
+ ```
372
+
373
+ ### Use Cases
374
+
375
+ **User Authentication:**
376
+ ```typescript
377
+ // Login
378
+ tracelog.mergeGlobalMetadata({
379
+ userId: user.id,
380
+ email: user.email,
381
+ plan: user.subscription.plan
382
+ });
383
+
384
+ // Logout
385
+ tracelog.updateGlobalMetadata({});
386
+ ```
387
+
388
+ **A/B Testing:**
389
+ ```typescript
390
+ tracelog.mergeGlobalMetadata({
391
+ experiment_checkout: 'variant-b',
392
+ experiment_pricing: 'control'
393
+ });
394
+ ```
395
+
396
+ **Environment Context:**
397
+ ```typescript
398
+ tracelog.mergeGlobalMetadata({
399
+ build: process.env.BUILD_NUMBER,
400
+ region: user.location.region,
401
+ language: navigator.language
402
+ });
403
+ ```
404
+
405
+ ### Validation Rules
246
406
 
247
- - **Allowed types**: primitives (string, number, boolean), string arrays, nested objects (up to 10 levels)
248
- - **Disallowed**: functions, symbols, `undefined`, circular references
249
- - **Limits**: max 100 keys, 48 KB serialized size, 500 items per array, 1000 chars per string
407
+ - **Allowed Types**: Primitives (string, number, boolean), string arrays, nested objects (up to 10 levels)
408
+ - **NOT Allowed**: Functions, symbols, undefined, circular references
409
+ - **Limits**: Max 100 keys, 48KB serialized size, 500 items per array, 1000 chars per string
250
410
 
251
- **→ [Metadata Reference](./API_REFERENCE.md#globalmetadata)**
411
+ **→ [Metadata API Reference](./API_REFERENCE.md#global-metadata)**
412
+
413
+ ---
414
+
415
+ ## Transformers
416
+
417
+ Transform events dynamically at runtime before they're sent to integrations. Useful for adding custom logic, enrichment, or filtering.
418
+
419
+ **Important**: Transformers are **integration-specific** to protect TraceLog SaaS schema integrity:
420
+
421
+ | Integration | `beforeSend` | `beforeBatch` | Notes |
422
+ |-------------|--------------|---------------|-------|
423
+ | **Standalone (no backend)** | ✅ Applied | ⚠️ Not supported | Only local event emission; `beforeBatch` requires backend |
424
+ | **TraceLog SaaS (only)** | ❌ Silently ignored | ❌ Silently ignored | Schema protection |
425
+ | **Custom Backend (only)** | ✅ Applied | ✅ Applied | Full control |
426
+ | **Multi-Integration** | ⚠️ Custom only | ⚠️ Custom only | SaaS gets original events, custom gets transformed |
427
+
428
+ **Multi-Integration Behavior:**
429
+ - When using both TraceLog SaaS + Custom backend simultaneously
430
+ - SaaS receives **original events** (transformers not applied)
431
+ - Custom backend receives **transformed events**
432
+ - Independent error handling and retry per integration
433
+
434
+ **Event Listeners and Transformers:**
435
+
436
+ Event listeners (`tracelog.on('event', ...)`) receive **original events**, not transformed events. Transformers only affect data sent to backends.
437
+
438
+ ```typescript
439
+ tracelog.setTransformer('beforeSend', (data) => {
440
+ if ('type' in data) {
441
+ return { ...data, enrichedField: 'value' };
442
+ }
443
+ return data;
444
+ });
445
+
446
+ tracelog.on('event', (ev) => {
447
+ console.log(ev.enrichedField); // undefined - listeners receive original events
448
+ });
449
+ ```
450
+
451
+ **Workaround for GTM/Third-Party Relay:**
452
+
453
+ If you need to forward enriched events to GTM or other systems, apply the transformation in your listener:
454
+
455
+ ```typescript
456
+ // Define enrichment function once
457
+ const enrichEvent = (event) => ({
458
+ ...event,
459
+ appVersion: '1.0.0',
460
+ environment: 'production'
461
+ });
462
+
463
+ // Use in transformer (for backend)
464
+ tracelog.setTransformer('beforeSend', (data) => {
465
+ if ('type' in data) {
466
+ return enrichEvent(data);
467
+ }
468
+ return data;
469
+ });
470
+
471
+ // Use in listener (for GTM relay)
472
+ tracelog.on('event', (event) => {
473
+ const enrichedEvent = enrichEvent(event);
474
+ window.dataLayer?.push({ event: 'tracelog_event', ...enrichedEvent });
475
+ });
476
+ ```
477
+
478
+ ### Available Hooks
479
+
480
+ #### `beforeSend` - Per-Event Transformation
481
+
482
+ Transform individual events **before** deduplication, sampling, and queueing.
483
+
484
+ **Timing (depends on integration mode):**
485
+ - **Standalone mode (no backend)**: Runs in `EventManager.buildEventPayload()` before dedup/sampling/queueing
486
+ - **Custom-only mode**: Runs in `EventManager.buildEventPayload()` before dedup/sampling/queueing
487
+ - **Multi-integration mode (SaaS + Custom)**: Runs in `SenderManager` per-integration (SaaS skipped, Custom applied)
488
+ - **TraceLog SaaS-only mode**: Silently ignored (not applied)
489
+
490
+ ```typescript
491
+ import { tracelog } from '@tracelog/lib';
492
+ import type { EventData, EventsQueue } from '@tracelog/lib';
493
+
494
+ // Add custom metadata to all events
495
+ tracelog.setTransformer('beforeSend', (data: EventData | EventsQueue) => {
496
+ if ('type' in data) {
497
+ return {
498
+ ...data,
499
+ custom_event: {
500
+ ...data.custom_event,
501
+ metadata: {
502
+ ...data.custom_event?.metadata,
503
+ environment: 'production',
504
+ version: '1.0.0'
505
+ }
506
+ }
507
+ };
508
+ }
509
+ return data;
510
+ });
511
+
512
+ // Filter out sensitive events
513
+ tracelog.setTransformer('beforeSend', (data) => {
514
+ if ('type' in data && data.custom_event?.name === 'internal_event') {
515
+ return null; // Event will be dropped
516
+ }
517
+ return data;
518
+ });
519
+ ```
520
+
521
+ #### `beforeBatch` - Batch Transformation
522
+
523
+ Transform the entire batch before sending to backend. Runs once per batch (every 10s or 50 events).
524
+
525
+ ```typescript
526
+ // Add batch-level metadata
527
+ tracelog.setTransformer('beforeBatch', (data) => {
528
+ if ('events' in data) {
529
+ return {
530
+ ...data,
531
+ global_metadata: {
532
+ ...data.global_metadata,
533
+ batchSize: data.events.length,
534
+ batchTimestamp: Date.now()
535
+ }
536
+ };
537
+ }
538
+ return data;
539
+ });
540
+
541
+ // Filter batch based on conditions
542
+ tracelog.setTransformer('beforeBatch', (data) => {
543
+ if ('events' in data && data.events.length < 5) {
544
+ return null; // Don't send small batches
545
+ }
546
+ return data;
547
+ });
548
+ ```
549
+
550
+ ### Removing Transformers
551
+
552
+ ```typescript
553
+ // Remove specific transformer
554
+ tracelog.removeTransformer('beforeSend');
555
+ tracelog.removeTransformer('beforeBatch');
556
+ ```
557
+
558
+ ### Error Handling & Validation
559
+
560
+ Transformers are designed to be resilient and flexible:
561
+
562
+ **Input Validation:**
563
+ - **Function type check**: `setTransformer()` throws error if `fn` is not a function
564
+ - **Example**: `tracelog.setTransformer('beforeSend', null)` → Throws `Error: [TraceLog] Transformer must be a function, received: object`
565
+
566
+ **Error Handling:**
567
+ - **Exceptions**: Caught and logged, original event/batch used
568
+ - **Invalid return**: Logged warning, original event/batch used
569
+ - **`null` return**: Event/batch filtered out (intended behavior)
570
+
571
+ **Validation:**
572
+ - **Minimal checks only**: `beforeSend` requires `'type'` field, `beforeBatch` requires `'events'` array
573
+ - **Custom schemas supported**: All other fields optional for maximum flexibility with custom backends
574
+ - **Use case**: Transform data to match your backend's schema (e.g., data warehouses, custom APIs)
575
+
576
+ ```typescript
577
+ // Safe transformer - errors won't break tracking
578
+ tracelog.setTransformer('beforeSend', (data) => {
579
+ try {
580
+ // Complex transformation logic
581
+ return transformData(data);
582
+ } catch (error) {
583
+ console.error('Transformer error:', error);
584
+ return data; // Fallback to original
585
+ }
586
+ });
587
+
588
+ // Custom schema example - completely reshape for your backend
589
+ tracelog.setTransformer('beforeSend', (data) => {
590
+ if ('type' in data) {
591
+ return {
592
+ type: 'analytics_event',
593
+ eventName: data.custom_event?.name,
594
+ timestamp: Date.now(),
595
+ // Your custom fields - TraceLog won't reject this!
596
+ customField1: 'value',
597
+ customField2: 123
598
+ };
599
+ }
600
+ return data;
601
+ });
602
+ ```
603
+
604
+ ### Use Cases
605
+
606
+ **Data Enrichment:**
607
+ ```typescript
608
+ tracelog.setTransformer('beforeSend', (data) => {
609
+ if ('type' in data) {
610
+ return {
611
+ ...data,
612
+ custom_event: {
613
+ ...data.custom_event,
614
+ metadata: {
615
+ ...data.custom_event?.metadata,
616
+ userId: getCurrentUserId(),
617
+ sessionContext: getSessionContext()
618
+ }
619
+ }
620
+ };
621
+ }
622
+ return data;
623
+ });
624
+ ```
625
+
626
+ **Event Filtering:**
627
+ ```typescript
628
+ // Filter out bot traffic
629
+ tracelog.setTransformer('beforeBatch', (data) => {
630
+ if ('events' in data) {
631
+ const filteredEvents = data.events.filter(
632
+ event => !isBotUserAgent(navigator.userAgent)
633
+ );
634
+ return { ...data, events: filteredEvents };
635
+ }
636
+ return data;
637
+ });
638
+ ```
639
+
640
+ **PII Sanitization (Custom Backend):**
641
+ ```typescript
642
+ // Additional sanitization for custom backend
643
+ tracelog.setTransformer('beforeSend', (data) => {
644
+ if ('type' in data && data.custom_event?.metadata) {
645
+ const sanitized = { ...data.custom_event.metadata };
646
+ delete sanitized.email;
647
+ delete sanitized.phone;
648
+ return {
649
+ ...data,
650
+ custom_event: {
651
+ ...data.custom_event,
652
+ metadata: sanitized
653
+ }
654
+ };
655
+ }
656
+ return data;
657
+ });
658
+ ```
659
+
660
+ ---
661
+
662
+ ## Custom Headers
663
+
664
+ Add custom HTTP headers to requests sent to custom backends. Useful for authentication, tenant identification, or API versioning.
665
+
666
+ **Important**: Custom headers **only apply to custom backend integrations**. TraceLog SaaS always receives requests without custom headers.
667
+
668
+ ### Static Headers (Config)
669
+
670
+ Set fixed headers in configuration:
671
+
672
+ ```typescript
673
+ await tracelog.init({
674
+ integrations: {
675
+ custom: {
676
+ collectApiUrl: 'https://api.example.com/collect',
677
+ headers: {
678
+ 'X-Tenant-Id': 'tenant-123',
679
+ 'X-Brand': 'my-brand',
680
+ 'X-API-Version': '2.0'
681
+ }
682
+ }
683
+ }
684
+ });
685
+ ```
686
+
687
+ ### Dynamic Headers (Provider)
688
+
689
+ Set headers dynamically at runtime (e.g., auth tokens that expire):
690
+
691
+ ```typescript
692
+ // Set before or after init
693
+ tracelog.setCustomHeaders(() => ({
694
+ 'Authorization': `Bearer ${getAuthToken()}`,
695
+ 'X-Request-ID': crypto.randomUUID()
696
+ }));
697
+
698
+ await tracelog.init({
699
+ integrations: {
700
+ custom: { collectApiUrl: 'https://api.example.com/collect' }
701
+ }
702
+ });
703
+ ```
704
+
705
+ ### Static + Dynamic Headers
706
+
707
+ Combine both approaches. Dynamic headers override static on key collision:
708
+
709
+ ```typescript
710
+ await tracelog.init({
711
+ integrations: {
712
+ custom: {
713
+ collectApiUrl: 'https://api.example.com/collect',
714
+ headers: {
715
+ 'X-Brand': 'static-brand', // Static
716
+ 'X-Tenant-Id': 'tenant-123' // Static
717
+ }
718
+ }
719
+ }
720
+ });
721
+
722
+ // Dynamic provider overrides 'X-Brand'
723
+ tracelog.setCustomHeaders(() => ({
724
+ 'X-Brand': 'dynamic-brand', // Overrides static
725
+ 'Authorization': 'Bearer token' // New header
726
+ }));
727
+
728
+ // Result: { 'X-Tenant-Id': 'tenant-123', 'X-Brand': 'dynamic-brand', 'Authorization': 'Bearer token' }
729
+ ```
730
+
731
+ ### Removing Headers
732
+
733
+ ```typescript
734
+ // Remove dynamic provider (static headers from config remain)
735
+ tracelog.removeCustomHeaders();
736
+ ```
737
+
738
+ ### sendBeacon Limitation
739
+
740
+ ⚠️ Custom headers are **NOT applied** to `sendBeacon()` requests (page unload). The browser API doesn't support custom headers. For scenarios requiring headers on all requests:
741
+ - Ensure async sends complete before page unload
742
+ - Use short-lived tokens that don't require refresh per request
743
+
744
+ **→ [Custom Headers API Reference](./API_REFERENCE.md#setcustomheadersprovider-customheadersprovider-void)**
252
745
 
253
746
  ---
254
747
 
255
748
  ## Integration Modes
256
749
 
257
- ### 1. Standalone (no backend)
750
+ TraceLog supports multiple integration modes. Choose what fits your needs:
751
+
752
+ ### 1. Standalone (No Backend)
258
753
 
259
- Default when no `integrations` are configured. Events are captured, queued, and emitted locally no network requests.
754
+ **Default mode when no integrations configured.** Events captured and emitted locally without network requests.
260
755
 
261
756
  ```typescript
262
757
  await tracelog.init();
263
758
 
759
+ // Consume events locally
264
760
  tracelog.on('event', (event) => {
265
761
  myAnalytics.track(event);
266
762
  });
@@ -270,10 +766,15 @@ tracelog.on('queue', (batch) => {
270
766
  });
271
767
  ```
272
768
 
273
- Perfect for custom analytics pipelines, testing, or privacy-focused implementations where you want to ship events to your own destination via a listener.
769
+ **Behavior:**
770
+ - ✅ Events captured and queued normally
771
+ - ✅ `beforeSend` transformer applied (per-event transformation)
772
+ - ⚠️ `beforeBatch` transformer **NOT supported** (no SenderManager created)
773
+ - ✅ Events emitted to local listeners every 10 seconds or 50 events
774
+ - ❌ **NO network requests made** (no backends configured)
775
+ - ✅ Perfect for custom analytics pipelines, testing, or privacy-focused implementations
274
776
 
275
777
  ### 2. TraceLog SaaS
276
-
277
778
  ```typescript
278
779
  await tracelog.init({
279
780
  integrations: {
@@ -282,64 +783,126 @@ await tracelog.init({
282
783
  });
283
784
  ```
284
785
 
285
- **Domain requirement.** The SaaS endpoint is derived from the host page's domain (`https://{projectId}.{rootDomain}/collect`), so `init()` rejects when called from `localhost` or a raw IP address. For local development, omit `integrations.tracelog` to run in standalone mode, or test against a staging domain mapped via `/etc/hosts`.
786
+ ### 3. Custom Backend
787
+ ```typescript
788
+ await tracelog.init({
789
+ integrations: {
790
+ custom: {
791
+ collectApiUrl: 'https://api.example.com/collect',
792
+ allowHttp: false, // Only true for local testing
793
+ fetchCredentials: 'include' // Cookie policy: 'include' | 'same-origin' | 'omit'
794
+ }
795
+ }
796
+ });
797
+ ```
798
+
799
+ ### 4. Multi-Integration (TraceLog SaaS + Custom Backend)
800
+ ```typescript
801
+ await tracelog.init({
802
+ integrations: {
803
+ tracelog: { projectId: 'your-project-id' }, // Analytics dashboard
804
+ custom: { collectApiUrl: 'https://warehouse.com' } // Data warehouse
805
+ }
806
+ });
807
+
808
+ // Events sent to BOTH endpoints independently
809
+ // - Independent error handling per integration
810
+ // - Independent retry/persistence per integration
811
+ // - Parallel sending (non-blocking)
812
+ ```
286
813
 
287
- **→ [Integration Reference](./API_REFERENCE.md#integration-configuration)**
814
+ **→ [Integration Setup Guide](./API_REFERENCE.md#integration-configuration)**
288
815
 
289
816
  ---
290
817
 
291
818
  ## Error Handling & Reliability
292
819
 
293
- ### Automatic retry strategy
820
+ TraceLog implements intelligent error handling with automatic retries for transient failures:
294
821
 
295
- **Transient errors** (5xx, timeouts, network failures):
822
+ ### Automatic Retry Strategy
296
823
 
297
- - Up to 2 retry attempts (3 total)
298
- - Exponential backoff with jitter (200–300 ms, 400–500 ms)
299
- - Persisted to `localStorage` after exhaustion for next-page recovery
824
+ **Transient Errors** (5xx, timeouts, network failures):
825
+ - **Up to 2 retry attempts** per integration (3 total attempts)
826
+ - **Exponential backoff with jitter**: 200-300ms, 400-500ms
827
+ - **Independent retries** per integration (SaaS and Custom retry separately)
828
+ - **Persistence after exhaustion**: Events saved to localStorage for next-page recovery
300
829
 
301
- **Rate limit (429):**
830
+ **Rate Limit** (429):
831
+ - **No in-session retries** — arms a 60-second cooldown instead
832
+ - **Cooldown is mirrored to localStorage** and shared across tabs/windows on the same origin (prevents every fresh page load from hammering the server's 429 window)
833
+ - **Events persisted immediately** to localStorage; retried once the cooldown elapses
834
+ - TraceLog SaaS deduplicates retries server-side; custom backends should implement idempotency
302
835
 
303
- - No in-session retries arms a 60-second cooldown instead
304
- - Cooldown is mirrored to `localStorage` and shared across tabs/windows on the same origin (prevents every fresh page load from hammering the server during its 429 window)
305
- - Events are persisted immediately and retried once the cooldown elapses
306
- - The backend deduplicates retries via the batch idempotency token
836
+ **Permanent Errors** (4xx except 408, 429):
837
+ - **No retries** (immediate failure)
838
+ - **Events discarded** (not persisted)
839
+ - **Exception**: 408 Request Timeout is treated as transient
307
840
 
308
- **Permanent errors** (4xx except 408, 429):
841
+ ```typescript
842
+ // Multi-backend example with automatic retries
843
+ await tracelog.init({
844
+ integrations: {
845
+ tracelog: { projectId: 'project-id' },
846
+ custom: { collectApiUrl: 'https://api.example.com/collect' }
847
+ }
848
+ });
849
+
850
+ // If tracelog SaaS returns 500:
851
+ // - Retries 2 times with backoff (200-300ms, 400-500ms)
852
+ // - If all fail → persists to localStorage for next page
309
853
 
310
- - No retries events are discarded
311
- - 408 Request Timeout is treated as transient
854
+ // If custom backend succeeds:
855
+ // - Events removed from queue (optimistic removal)
856
+ // - Failed integration recovered on next page load
857
+ ```
312
858
 
313
- ### Error classification
859
+ ### Error Classification
314
860
 
315
- | Status | Type | Retries | Persistence |
316
- |-----------------------|-------------|------------------|-------------------|
317
- | **2xx** | Success | None | Cleared |
318
- | **4xx** (except 408/429) | Permanent | ❌ None | ❌ Discarded |
319
- | **408** | Transient | ✅ Up to 2 | ✅ After exhaustion |
320
- | **429** | Rate Limit | None (60s cooldown, shared across tabs) | ✅ Immediate |
321
- | **5xx** | Transient | ✅ Up to 2 | ✅ After exhaustion |
322
- | **Network error** | Transient | ✅ Up to 2 | ✅ After exhaustion |
323
- | **Timeout** | Transient | ✅ Up to 2 | ✅ After exhaustion |
861
+ | Status Code | Type | Retries | Persistence |
862
+ |-------------|------|---------|-------------|
863
+ | **2xx** | Success | None | Cleared |
864
+ | **4xx** (except 408, 429) | Permanent | ❌ None | ❌ Discarded |
865
+ | **408** Request Timeout | Transient | ✅ Up to 2 | ✅ After exhaustion |
866
+ | **429** Too Many Requests | Rate Limited (60s cooldown, shared across tabs) | ❌ None | ✅ Immediate |
867
+ | **5xx** | Transient | ✅ Up to 2 | ✅ After exhaustion |
868
+ | **Network Error** | Transient | ✅ Up to 2 | ✅ After exhaustion |
869
+ | **Timeout** | Transient | ✅ Up to 2 | ✅ After exhaustion |
324
870
 
325
- ### Recovery on page load
871
+ ### Optimistic Queue Management
326
872
 
327
- Failed events are recovered automatically on the next `init()`:
873
+ **Multi-Integration Behavior:**
874
+ - Events removed from queue if **AT LEAST ONE** integration succeeds
875
+ - Failed integrations persist independently for next-page recovery
876
+ - Successful integration doesn't retry (performance optimization)
328
877
 
878
+ **Example Scenario:**
329
879
  ```typescript
330
- // Page 1: events fail to send (5xx after retries) persisted with idempotency token
331
- // Page 2: init() recovers and resends; backend deduplicates by idempotency token
880
+ // SaaS succeeds immediatelyno retry needed
881
+ // Custom fails with 503 retries 2 times persists for recovery
882
+ // Events removed from queue (SaaS succeeded)
883
+ // Next page load → only Custom integration recovers persisted events
332
884
  ```
333
885
 
334
- Multi-tab protection: a 1-second window prevents two tabs from re-sending the same persisted batch simultaneously.
886
+ ### Recovery on Page Load
335
887
 
336
- **Circuit breaker.** After `MAX_CONSECUTIVE_NETWORK_FAILURES` consecutive network-level failures (DNS, connection refused), the sender opens its circuit and skips further requests until `CIRCUIT_BREAKER_COOLDOWN_MS` elapses. A single probe request is then allowed (half-open state) before fully closing.
888
+ Failed events automatically recovered on next `init()`:
889
+
890
+ ```typescript
891
+ // Page 1: Events fail to send (5xx error after retries)
892
+ // → Persisted to localStorage per-integration
893
+
894
+ // Page 2: User navigates to new page
895
+ await tracelog.init({ /* same config */ });
896
+ // ✅ Automatically recovers and resends persisted events
897
+ // ✅ Independent recovery per integration
898
+ // ✅ Multi-tab protection (1s window prevents duplicates)
899
+ ```
337
900
 
338
901
  **→ [Full Error Handling Reference](./API_REFERENCE.md#error-handling)**
339
902
 
340
- ### Session continuity across external redirects
903
+ ### Session Continuity (External Redirects)
341
904
 
342
- TraceLog preserves sessions across external redirects (payment processors, OAuth flows, etc.) with zero developer action. Session data is mirrored to `sessionStorage` alongside `localStorage`, so when a user returns from an external site and `localStorage` is empty, the session is recovered from `sessionStorage` transparently.
905
+ TraceLog automatically preserves sessions across external redirects (payment processors, OAuth flows, etc.) with zero developer action. Session data is mirrored to `sessionStorage` alongside `localStorage`, so when a user returns from an external site and `localStorage` is empty, the session is recovered from `sessionStorage` transparently.
343
906
 
344
907
  ```typescript
345
908
  // No special handling needed before redirect
@@ -351,7 +914,7 @@ tracelog.event('purchase', { orderId: '12345', amount: 99.99 });
351
914
  // Same session as before the redirect
352
915
  ```
353
916
 
354
- - Automatic no API calls or developer action required
917
+ - Automatic: no API calls or developer action required
355
918
  - `sessionStorage` mirror survives same-tab navigation (cleared on tab close)
356
919
  - Session timeout still applies (expired sessions are not recovered)
357
920
 
@@ -359,26 +922,26 @@ tracelog.event('purchase', { orderId: '12345', amount: 99.99 });
359
922
 
360
923
  ## Privacy & Security
361
924
 
362
- TraceLog is privacy-first by design:
925
+ TraceLog is **privacy-first** by design:
363
926
 
364
- - ✅ **PII sanitization** auto-redacts emails, phones, credit cards, IBANs, API keys, bearer tokens, and connection-string passwords from click text and error messages
365
- - ✅ **Input protection** never captures values from `<input>`, `<textarea>`, `<select>`
366
- - ✅ **URL filtering** removes 15 default sensitive query params (token, password, auth, secret, api_key, …) plus any you add via `sensitiveQueryParams`
367
- - ✅ **Element exclusion** use `data-tlog-ignore` on any container to exclude its contents from click tracking
368
- - ✅ **Client-side controls** sampling, dedup, and validation all happen in the browser
927
+ - ✅ **PII Sanitization** - Auto-redacts emails, phones, credit cards, API keys
928
+ - ✅ **Input Protection** - Never captures `<input>`, `<textarea>`, `<select>` values
929
+ - ✅ **URL Filtering** - Removes sensitive query params (15 defaults + custom)
930
+ - ✅ **Element Exclusion** - Use `data-tlog-ignore` to exclude sensitive areas
931
+ - ✅ **Client-Side Controls** - All sampling and validation happens in browser
369
932
 
933
+ **Example:**
370
934
  ```html
371
- <!-- Exclude sensitive forms entirely -->
935
+ <!-- Exclude sensitive forms -->
372
936
  <div data-tlog-ignore>
373
937
  <input type="password" name="password">
374
938
  <input type="text" name="credit_card">
375
939
  </div>
376
940
  ```
377
941
 
378
- **Your responsibilities:**
379
-
380
- - Obtain user consent before calling `init()` (GDPR / CCPA / LOPD)
381
- - Avoid PII in custom event metadata (TraceLog only sanitizes element text and error messages)
942
+ **Your Responsibilities:**
943
+ - Get user consent before calling `init()` (GDPR/CCPA)
944
+ - Sanitize custom event metadata (avoid PII)
382
945
  - Call `destroy()` on consent revoke
383
946
 
384
947
  **→ [Complete Security Guide](./SECURITY.md)**
@@ -387,20 +950,30 @@ TraceLog is privacy-first by design:
387
950
 
388
951
  ## QA Mode
389
952
 
390
- QA mode logs custom events to the browser console so you can verify tracking implementation without inspecting the network tab.
953
+ Enable QA mode for debugging and development:
391
954
 
392
- ### URL activation
955
+ ### URL Activation
956
+ ```bash
957
+ # Enable
958
+ ?tlog_mode=qa
393
959
 
394
- ```text
395
- ?tlog_mode=qa # Enable (persists in sessionStorage for the tab)
396
- ?tlog_mode=qa_off # Disable
960
+ # Disable
961
+ ?tlog_mode=qa_off
962
+ ```
963
+
964
+ ### Programmatic API
965
+ ```typescript
966
+ tracelog.setQaMode(true); // Enable
967
+ tracelog.setQaMode(false); // Disable
397
968
  ```
398
969
 
399
- **Effects in QA mode:**
970
+ **Features:**
971
+ - Custom events logged to browser console
972
+ - Strict validation (throws errors instead of warnings)
973
+ - Session state visible in console
974
+ - Persistent across page reloads (sessionStorage)
400
975
 
401
- - Custom events logged to console with their name and metadata
402
- - Strict validation: invalid custom-event payloads throw instead of being silently dropped
403
- - Persists across navigations within the same tab (cleared on tab close)
976
+ **→ [QA Mode Documentation](./API_REFERENCE.md#setqamodeenabled-boolean-void)**
404
977
 
405
978
  ---
406
979
 
@@ -411,7 +984,7 @@ QA mode logs custom events to the browser console so you can verify tracking imp
411
984
  - Safari 12+
412
985
  - Edge 79+
413
986
 
414
- **SSR/SSG compatible** safe to import in Angular Universal, Next.js, Nuxt, SvelteKit. All methods no-op in Node.js.
987
+ **SSR/SSG Compatible:** Safe to import in Angular Universal, Next.js, Nuxt, SvelteKit (no-ops in Node.js).
415
988
 
416
989
  ---
417
990
 
@@ -419,9 +992,9 @@ QA mode logs custom events to the browser console so you can verify tracking imp
419
992
 
420
993
  ```bash
421
994
  npm install # Install dependencies
422
- npm run build:all # Build ESM + CJS + browser bundles
995
+ npm run build:all # Build ESM + CJS + Browser bundles
423
996
  npm run check # Lint + format validation
424
- npm test # Run all tests
997
+ npm run test # Run all tests
425
998
  npm run test:coverage # Generate coverage report
426
999
  ```
427
1000
 
@@ -433,10 +1006,10 @@ npm run test:coverage # Generate coverage report
433
1006
 
434
1007
  | Document | Description |
435
1008
  |----------|-------------|
436
- | **[API Reference](./API_REFERENCE.md)** | Complete API documentation: methods, config options, event types |
437
- | **[Best Practices](./BEST_PRACTICES.md)** | Patterns, anti-patterns, optimization tips |
438
- | **[Security Guide](./SECURITY.md)** | Privacy, GDPR compliance, security checklist |
439
- | **[Changelog](./CHANGELOG.md)** | Release history |
1009
+ | **[API Reference](./API_REFERENCE.md)** | Complete API documentation with all methods, config options, and event types |
1010
+ | **[Best Practices](./BEST_PRACTICES.md)** | Patterns, anti-patterns, and optimization tips |
1011
+ | **[Security Guide](./SECURITY.md)** | Privacy, GDPR compliance, and security best practices |
1012
+ | **[Changelog](./CHANGELOG.md)** | Release history and migration guides |
440
1013
  | **[Handlers](./src/handlers/README.md)** | Event capture implementation details |
441
1014
  | **[Managers](./src/managers/README.md)** | Core component architecture |
442
1015